|
@@ -7,8 +7,96 @@
|
|
|
#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(time_t when, char *timebuf)
|
|
|
+{
|
|
|
+ strftime(timebuf, TIMEBUF_LEN,
|
|
|
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&when));
|
|
|
+
|
|
|
+ return timebuf;
|
|
|
+}
|
|
|
+
|
|
|
+static const char *http_now(void)
|
|
|
+{
|
|
|
+ static char timebuf[TIMEBUF_LEN];
|
|
|
+ return http_date(time(NULL), timebuf);
|
|
|
+}
|
|
|
+
|
|
|
+static const char *http_dos_date(uint32_t dos_date)
|
|
|
+{
|
|
|
+ static char timebuf[TIMEBUF_LEN];
|
|
|
+ struct tm tm;
|
|
|
+ time_t t;
|
|
|
+
|
|
|
+ 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 = -1;
|
|
|
+
|
|
|
+ return http_date(mktime(&tm), timebuf);
|
|
|
+}
|
|
|
+
|
|
|
+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,
|
|
|
+ "<!DOCTYPE html>\r\n"
|
|
|
+ "<html>\r\n"
|
|
|
+ "<head>\r\n"
|
|
|
+ "<title>Error %u: %s</title>\r\n"
|
|
|
+ "</head>\r\n"
|
|
|
+ "<body>\r\n"
|
|
|
+ "<h1>Error %u</h1>\r\n"
|
|
|
+ "<p>%s</p>\r\n"
|
|
|
+ "</body>\r\n"
|
|
|
+ "</html>\r\n",
|
|
|
+ errcode, msg, errcode, msg);
|
|
|
+
|
|
|
+ if (!body)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ hlen = asprintf(&header,
|
|
|
+ "HTTP/1.1 %u %s\r\n"
|
|
|
+ "Content-Type: text/html; 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;
|
|
@@ -19,15 +107,15 @@ 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, "Length required");
|
|
|
+ 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_resp_send_err(req, 409, "Firmware update already in progress");
|
|
|
+ return httpd_error(req, 409, "Firmware update already in progress");
|
|
|
else if (rv)
|
|
|
- return httpd_resp_send_err(req, 500, "Firmware update failed");
|
|
|
+ return httpd_error(req, 500, "Firmware update failed");
|
|
|
|
|
|
len = asprintf(&response,
|
|
|
"<!DOCTYPE html>\r\n"
|
|
@@ -43,6 +131,8 @@ 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, "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);
|
|
@@ -112,12 +202,12 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
buffer = malloc(buffer_size);
|
|
|
zip = malloc(sizeof *zip);
|
|
|
if (!buffer || !zip) {
|
|
|
- httpd_resp_send_err(req, 503, "Out of memory");
|
|
|
+ err = httpd_error(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");
|
|
|
+ err = httpd_error(req, 414, "URI too long");
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -128,7 +218,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
zip, NULL, NULL, NULL, NULL);
|
|
|
if (!unz) {
|
|
|
MSG("[HTTP] unzOpen failed!\n");
|
|
|
- err = httpd_resp_send_err(req, 500, "Cannot open content archive");
|
|
|
+ err = httpd_error(req, 500, "Cannot open content archive");
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -136,7 +226,8 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
if (add_index) {
|
|
|
if (p > buffer)
|
|
|
*p++ = '/';
|
|
|
- p = mempcpy(p, index_filename, sizeof index_filename);
|
|
|
+ memcpy(p, index_filename, sizeof index_filename);
|
|
|
+ p += sizeof index_filename - 1; /* Point to final NUL */
|
|
|
}
|
|
|
|
|
|
MSG("trying to open: %s\n", buffer);
|
|
@@ -144,7 +235,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
break;
|
|
|
|
|
|
if (add_index) {
|
|
|
- err = httpd_resp_send_404(req);
|
|
|
+ err = httpd_error(req, 404, "File not found");
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -170,37 +261,64 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
|
|
|
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.
|
|
|
+ * 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");
|
|
|
+ }
|
|
|
|
|
|
- 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) {
|
|
|
+ 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 (err != len || req->method == HTTP_HEAD ||
|
|
|
- !fileinfo.uncompressed_size)
|
|
|
+ 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");
|