123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- #define MODULE "httpd"
- #include "common.h"
- #include "fw.h"
- #include "httpd.h"
- #include <incbin.h>
- #include <unzipLIB.h>
- /*
- * Allow the client to cache static content for this many seconds;
- * this improves responsiveness signficantly.
- */
- #define HTTPD_STATIC_CACHE_AGE 300 /* 5 min */
- static httpd_handle_t httpd;
- #define TIMEBUF_LEN 32
- static const char *http_date(const struct tm *when)
- {
- static char timebuf[TIMEBUF_LEN];
- strftime(timebuf, TIMEBUF_LEN,
- "%a, %d %b %Y %H:%M:%S GMT", when);
- return timebuf;
- }
- static const char *http_now(void)
- {
- time_t t = time(NULL);
- return http_date(gmtime(&t));
- }
- static struct tm *set_weekday(struct tm *tm)
- {
- /*
- * A variation on Zeller's congruence.
- */
- unsigned int c, y, m, d;
- y = tm->tm_year + 1900;
- m = tm->tm_mon + 2;
- d = tm->tm_mday;
- if (m < 4) {
- m += 12;
- y--;
- }
- c = y/100;
- tm->tm_wday = (d + ((13*m)/5) + y + (y >> 2) - c + (c >> 2) + 6) % 7;
- return tm;
- }
- static const char *http_dos_date(uint32_t dos_date)
- {
- struct tm tm;
- tm.tm_sec = (dos_date << 1) & 63;
- tm.tm_min = (dos_date >> 5) & 63;
- tm.tm_hour = (dos_date >> 11) & 31;
- tm.tm_mday = (dos_date >> 16) & 31;
- tm.tm_mon = ((dos_date >> 21) & 15) - 1;
- tm.tm_year = (dos_date >> 25) + 80;
- tm.tm_isdst = 0; /* Times are stored in GMT */
- return http_date(set_weekday(&tm));
- }
- static esp_err_t httpd_error(httpd_req_t *req,
- unsigned int errcode, const char *msg)
- {
- char *header = NULL;
- char *body = NULL;
- int hlen, blen;
- int rv = ESP_ERR_NO_MEM;
- blen = asprintf(&body, "%u %s\r\n", errcode, msg);
- if (!body)
- goto out;
- hlen = asprintf(&header,
- "HTTP/1.1 %u %s\r\n"
- "Content-Type: text/plain; charset=\"UTF-8\"\r\n"
- "Content-Length: %d\r\n"
- "Date: %s\r\n"
- "Cache-Control: no-cache\r\n"
- "Connection: close\r\n"
- "\r\n",
- errcode, msg, blen, http_now());
- if (!header)
- goto out;
- rv = httpd_send(req, header, hlen);
- if (!rv)
- rv = httpd_send(req, body, blen);
- out:
- if (header)
- free(header);
- if (body)
- free(body);
- return rv;
- }
- esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
- {
- char *response;
- esp_err_t err;
- int rv, len;
- printf("[POST] len = %zu uri = \"%s\"\n",
- req->content_len, req->uri);
- if (!req->content_len) {
- return httpd_error(req, 411, "Length required");
- }
- rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
- if (rv == FWUPDATE_ERR_IN_PROGRESS)
- return httpd_error(req, 409, "Firmware update already in progress");
- else if (rv)
- return httpd_error(req, 500, "Firmware update failed");
- len = asprintf(&response,
- "Firmware update completed\r\n"
- "Rebooting in %u seconds\r\n",
- reboot_delayed());
- /* 200 is default, no need to set */
- httpd_resp_set_type(req, "text/plain; charset=\"UTF-8\"");
- httpd_resp_set_hdr(req, "Date", http_now());
- httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
- httpd_resp_set_hdr(req, "Connection", "close");
- err = httpd_resp_send(req, response, len);
- free(response);
- return err;
- }
- INCBIN_EXTERN(wwwzip);
- struct mime_type {
- const char *ext;
- uint16_t ext_len;
- uint16_t flags;
- const char *mime;
- };
- #define MT_CHARSET 1 /* Add charset to Content-Type */
- static const struct mime_type mime_types[] = {
- { ".html", 5, MT_CHARSET, "text/html" },
- { ".xhtml", 6, MT_CHARSET, "text/html" },
- { ".css", 4, MT_CHARSET, "text/css" },
- { ".webp", 5, 0, "image/webp" },
- { ".jpg", 4, 0, "image/jpeg" },
- { ".png", 4, 0, "image/png" },
- { ".ico", 4, 0, "image/png" }, /* favicon.ico */
- { ".svg", 4, MT_CHARSET, "image/svg+xml" },
- { ".otf", 4, 0, "font/otf" },
- { ".ttf", 4, 0, "font/ttf" },
- { ".woff", 5, 0, "font/woff" },
- { ".woff2", 6, 0, "font/woff2" },
- { ".pdf", 4, 0, "application/pdf" },
- { ".js", 3, MT_CHARSET, "text/javascript" },
- { ".mjs", 4, MT_CHARSET, "text/javascript" },
- { ".json", 5, MT_CHARSET, "application/json" },
- { ".xml", 4, MT_CHARSET, "text/xml" },
- { ".bin", 4, 0, "application/octet-stream" },
- { ".fw", 3, 0, "application/octet-stream" },
- { NULL, 0, MT_CHARSET, "text/plain" } /* default */
- };
- static esp_err_t httpd_static_handler(httpd_req_t *req)
- {
- static const char index_filename[] = "index.html";
- const size_t buffer_size = UNZ_BUFSIZE;
- const char *uri, *enduri;
- bool add_index;
- char *buffer = NULL;
- ZIPFILE *zip = NULL;
- unzFile unz = NULL;
- bool file_open = false;
- int err = 0;
- size_t len;
- uri = req->uri;
- while (*uri == '/')
- uri++; /* Skip leading slashes */
- enduri = strchr(uri, '\0');
- if (enduri == uri) {
- add_index = true;
- } else if (enduri[-1] == '/') {
- add_index = true;
- enduri--; /* Drop terminal slash */
- } else {
- add_index = false; /* Try the plain filename first */
- }
- MSG("requesting: /%.*s\n", enduri - uri, uri);
- buffer = malloc(buffer_size);
- zip = malloc(sizeof *zip);
- if (!buffer || !zip) {
- err = httpd_error(req, 503, "Out of memory");
- goto out;
- }
- if (enduri - uri + 1 + sizeof index_filename >= buffer_size) {
- err = httpd_error(req, 414, "URI too long");
- goto out;
- }
- char *p = mempcpy(buffer, uri, enduri - uri);
- *p = '\0';
- unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
- zip, NULL, NULL, NULL, NULL);
- if (!unz) {
- MSG("[HTTP] unzOpen failed!\n");
- err = httpd_error(req, 500, "Cannot open content archive");
- goto out;
- }
- while (1) {
- if (add_index) {
- if (p > buffer)
- *p++ = '/';
- memcpy(p, index_filename, sizeof index_filename);
- p += sizeof index_filename - 1; /* Point to final NUL */
- }
- MSG("trying to open: %s\n", buffer);
- if (unzLocateFile(unz, buffer, 1) == UNZ_OK)
- break;
- if (add_index) {
- err = httpd_error(req, 404, "File not found");
- goto out;
- }
- add_index = true; /* Try again with the index filename */
- }
- /* Note: p points to the end of the filename string */
- size_t filelen = p - buffer;
- const struct mime_type *mime_type = mime_types;
- /* The default entry with length 0 will always match */
- while (mime_type->ext_len) {
- len = mime_type->ext_len;
- if (len < filelen && !memcmp(p - len, mime_type->ext, len))
- break;
- mime_type++;
- }
- unz_file_info fileinfo;
- memset(&fileinfo, 0, sizeof fileinfo);
- unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
- /*
- * Hopefully the combination of date and CRC
- * is strong enough to quality for a "strong" ETag
- */
- char etag[16+3+1];
- snprintf(etag, sizeof etag, "\"%08x:%08x\"",
- fileinfo.dosDate, fileinfo.crc);
- bool skip_body = req->method == HTTP_HEAD || !fileinfo.uncompressed_size;
- bool skip_meta = false;
- const char *response = "200 OK";
- if (req->method == HTTP_GET &&
- httpd_req_get_hdr_value_str(req, "If-None-Match",
- buffer, buffer_size) == ESP_OK &&
- strstr(buffer, etag)) {
- skip_body = skip_meta = true;
- response = "304 Not Modified";
- }
- len = snprintf(buffer, buffer_size-2,
- "HTTP/1.1 %s\r\n"
- "Date: %s\r\n"
- "ETag: %s\r\n"
- "Cache-Control: max-age=%d\r\n",
- response,
- http_now(),
- etag,
- HTTPD_STATIC_CACHE_AGE);
- if (len < buffer_size-2 && !skip_meta) {
- const char *mime_extra =
- mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "";
- len += snprintf(buffer + len, buffer_size-2 - len,
- "Content-Type: %s%s\r\n"
- "Content-Length: %u\r\n"
- "Allow: GET, HEAD\r\n"
- "Last-Modified: %s\r\n"
- "%s",
- mime_type->mime, mime_extra,
- fileinfo.uncompressed_size,
- http_dos_date(fileinfo.dosDate),
- skip_body ? "" : "Connection: close\r\n");
- }
- if (len >= buffer_size-2) {
- err = httpd_resp_send_err(req, 500, "buffer_size too small");
- goto out;
- }
- buffer[len++] = '\r';
- buffer[len++] = '\n';
- err = httpd_send(req, buffer, len);
- if (skip_body || err != len) {
- /* No need to spend time uncompressing the file content */
- goto out;
- }
- if (unzOpenCurrentFile(unz) != UNZ_OK) {
- err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
- goto out;
- }
- file_open = true;
- len = fileinfo.uncompressed_size;
- while (len) {
- size_t chunk = len;
- if (chunk > buffer_size)
- chunk = buffer_size;
- if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
- err = ESP_ERR_HTTPD_RESULT_TRUNC;
- goto out;
- }
- err = httpd_send(req, buffer, chunk);
- if (err != chunk)
- goto out;
- len -= chunk;
- }
- err = ESP_OK; /* All good! */
- out:
- if (file_open)
- unzCloseCurrentFile(unz);
- if (unz)
- unzClose(unz);
- if (zip)
- free(zip);
- if (buffer)
- free(buffer);
- return err;
- }
- static const httpd_uri_t uri_handlers[] = {
- {
- .uri = "/*",
- .method = HTTP_GET,
- .handler = httpd_static_handler,
- .user_ctx = NULL
- },
- {
- .uri = "/*",
- .method = HTTP_HEAD,
- .handler = httpd_static_handler,
- .user_ctx = NULL
- },
- {
- .uri = "/fwupdate/?",
- .method = HTTP_POST,
- .handler = httpd_firmware_upgrade_handler,
- .user_ctx = NULL
- }
- };
- void my_httpd_stop(void)
- {
- if (httpd) {
- httpd_stop(httpd);
- httpd = NULL;
- }
- }
- void my_httpd_start(void)
- {
- httpd_config_t config = HTTPD_DEFAULT_CONFIG();
- httpd_handle_t server;
- if (httpd)
- return;
- config.task_priority = 2;
- printf("[HTTP] Default stack size: %zu\n", config.stack_size);
- config.stack_size <<= 2;
- printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
- config.uri_match_fn = httpd_uri_match_wildcard;
- if (httpd_start(&server, &config) != ESP_OK)
- return;
- httpd = server;
- for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++)
- httpd_register_uri_handler(httpd, &uri_handlers[i]);
- printf("[HTTP] httpd started\n");
- }
|