|
@@ -4,6 +4,9 @@
|
|
|
#include "fw.h"
|
|
|
#include "httpd.h"
|
|
|
|
|
|
+#include <incbin.h>
|
|
|
+#include <unzipLIB.h>
|
|
|
+
|
|
|
static httpd_handle_t httpd;
|
|
|
|
|
|
esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
|
|
@@ -16,7 +19,7 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
|
|
|
req->content_len, req->uri);
|
|
|
|
|
|
if (!req->content_len) {
|
|
|
- return httpd_resp_send_err(req, 411, NULL);
|
|
|
+ return httpd_resp_send_err(req, 411, "Length required");
|
|
|
}
|
|
|
|
|
|
rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
|
|
@@ -40,51 +43,203 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
-static esp_err_t httpd_get_handler(httpd_req_t *req)
|
|
|
+INCBIN(wwwzip, "data/www.zip");
|
|
|
+
|
|
|
+struct mime_type {
|
|
|
+ const char *ext;
|
|
|
+ size_t ext_len;
|
|
|
+ const char *mime;
|
|
|
+};
|
|
|
+
|
|
|
+static const struct mime_type mime_types[] = {
|
|
|
+ { ".html", 5, "text/html" },
|
|
|
+ { ".css", 4, "text/css" },
|
|
|
+ { ".txt", 4, "text/plain; charset=UTF-8" },
|
|
|
+ { ".bin", 4, "application/octet-stream" },
|
|
|
+ { ".fw", 3, "application/octet-stream" },
|
|
|
+ { NULL, 0, "text/html" } /* Default */
|
|
|
+};
|
|
|
+
|
|
|
+static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
{
|
|
|
- char *response;
|
|
|
- int len;
|
|
|
- esp_err_t err;
|
|
|
+ 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;
|
|
|
|
|
|
- len = asprintf(&response,
|
|
|
- "<!DOCTYPE html>\n"
|
|
|
- "<html>\n"
|
|
|
- "<head>\n"
|
|
|
- "<title>Hello, World!</title>\n"
|
|
|
- "</head>\n"
|
|
|
- "<body>\n"
|
|
|
- "<p>Hello, World!</p>\n"
|
|
|
- "<p>GET uri = <tt>%s</tt></p>\n"
|
|
|
- "</pre>\n"
|
|
|
- "</body>\n"
|
|
|
- "</html>\n",
|
|
|
- req->uri);
|
|
|
+ uri = req->uri;
|
|
|
+ while (*uri == '/')
|
|
|
+ uri++; /* Skip leading slashes */
|
|
|
+
|
|
|
+ enduri = strchr(uri, '\0');
|
|
|
+ if (enduri == uri) {
|
|
|
+ uri = index_filename; /* Empty string */
|
|
|
+ } else if (enduri[0] == '/') {
|
|
|
+ add_index = true;
|
|
|
+ enduri--; /* Drop terminal slash */
|
|
|
+ }
|
|
|
|
|
|
- /* 200 and text/html are the response defaults, no need to set */
|
|
|
- err = httpd_resp_send(req, response, len);
|
|
|
- free(response);
|
|
|
+ buffer = malloc(buffer_size);
|
|
|
+ zip = malloc(sizeof *zip);
|
|
|
+ if (!buffer || !zip) {
|
|
|
+ httpd_resp_send_err(req, 503, "Out of memory");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (enduri - req->uri + 1 + sizeof index_filename >= buffer_size) {
|
|
|
+ err = httpd_resp_send_err(req, 414, "URI too long");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ char *p = mempcpy(buffer, req->uri, enduri - req->uri);
|
|
|
+ *p = '\0';
|
|
|
+
|
|
|
+ int found;
|
|
|
+ while (1) {
|
|
|
+ if (add_index) {
|
|
|
+ if (p > buffer)
|
|
|
+ *p++ = '/';
|
|
|
+ p = mempcpy(p, index_filename, sizeof index_filename);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 file */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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 extension "" 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 = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
|
|
|
+ zip, NULL, NULL, NULL, NULL);
|
|
|
+ if (!unz) {
|
|
|
+ err = httpd_resp_send_err(req, 500, "Cannot open content archive");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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\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,
|
|
|
+ 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_get = {
|
|
|
- .uri = "/",
|
|
|
- .method = HTTP_GET,
|
|
|
- .handler = httpd_get_handler,
|
|
|
- .user_ctx = NULL
|
|
|
-};
|
|
|
-
|
|
|
-static const httpd_uri_t uri_firmware_upgrade = {
|
|
|
- .uri = "/firmware-upgrade",
|
|
|
- .method = HTTP_POST,
|
|
|
- .handler = httpd_firmware_upgrade_handler,
|
|
|
- .user_ctx = NULL
|
|
|
+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 = "/firmware-upgrade",
|
|
|
+ .method = HTTP_POST,
|
|
|
+ .handler = httpd_firmware_upgrade_handler,
|
|
|
+ .user_ctx = NULL
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
void my_httpd_stop(void)
|
|
@@ -114,8 +269,9 @@ void my_httpd_start(void)
|
|
|
return;
|
|
|
|
|
|
httpd = server;
|
|
|
- httpd_register_uri_handler(httpd, &uri_get);
|
|
|
- httpd_register_uri_handler(httpd, &uri_firmware_upgrade);
|
|
|
+ for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++)
|
|
|
+ httpd_register_uri_handler(httpd, &uri_handlers[i]);
|
|
|
+
|
|
|
esp_register_shutdown_handler(my_httpd_stop);
|
|
|
printf("[HTTP] httpd started\n");
|
|
|
}
|