#define MODULE "httpd" #include "common.h" #include "fw.h" #include "httpd.h" #include #include static httpd_handle_t httpd; 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_resp_send_err(req, 411, "Length required"); } rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req); if (rv == FWUPDATE_ERR_IN_PROGRESS) return httpd_resp_send_err(req, 409, "Firmware update already in progress"); else if (rv) return httpd_resp_send_err(req, 500, "Firmware update failed"); len = asprintf(&response, "\r\n" "\r\n" "\r\n" "Firmware update completed\r\n" "\r\n" "\r\n" "

Firmware update completed

\r\n" "

Rebooting in %u seconds

\r\n" "\r\n" "\r\n", reboot_delayed()); /* 200 and text/html are the response defaults, no need to set */ httpd_resp_set_hdr(req, "Connection", "close"); err = httpd_resp_send(req, response, len); free(response); return err; } INCBIN(wwwzip, "data/www.zip"); 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" }, { ".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) { httpd_resp_send_err(req, 503, "Out of memory"); goto out; } if (enduri - uri + 1 + sizeof index_filename >= buffer_size) { err = httpd_resp_send_err(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_resp_send_err(req, 500, "Cannot open content archive"); goto out; } while (1) { if (add_index) { if (p > buffer) *p++ = '/'; p = mempcpy(p, index_filename, sizeof index_filename); } MSG("trying to open: %s\n", buffer); if (unzLocateFile(unz, buffer, 1) == UNZ_OK) break; if (add_index) { err = httpd_resp_send_404(req); 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); /* * This is kind of brain dead, but it seems like the only sane * way to not have to build the whole response in memory even * though the length is known a priori. */ len = snprintf(buffer, buffer_size, "HTTP/1.1 200 OK\r\n" "Content-Type: %s%s\r\n" "Content-Length: %u\r\n" "Allow: GET, HEAD\r\n" "Etag: \"%08x:%08x\"\r\n" "Connection: close\r\n" "\r\n", mime_type->mime, mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "", fileinfo.uncompressed_size, /* * Hopefully the combination of date and CRC * is strong enough to quality for a "strong" ETag */ fileinfo.dosDate, fileinfo.crc); if (len >= buffer_size) { err = httpd_resp_send_err(req, 500, "buffer_size too small"); goto out; } err = httpd_send(req, buffer, len); if (err != len || req->method == HTTP_HEAD || !fileinfo.uncompressed_size) 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"); }