| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881 | 
							- #define MODULE "httpd"
 
- #include "common.h"
 
- #include "fw.h"
 
- #include "httpd.h"
 
- #include "config.h"
 
- #include <incbin.h>
 
- #include <unzipLIB.h>
 
- #define HTTPD_PRIORITY	4
 
- static httpd_handle_t httpd;
 
- static const char fallback_language[] = "en"; /* For unknown language */
 
- static const char redir_filename[] = "_redir"; /* For a directory "file" */
 
- #define MAX_LANG_LEN 16
 
- /* Looping version of httpd_send(); this is a hidden function in the server */
 
- static esp_err_t httpd_send_all(httpd_req_t *req, const void *buf, size_t len)
 
- {
 
-     const char *p = buf;
 
-     while (len) {
 
- 	int sent = httpd_send(req, p, len);
 
- 	if (sent <= 0)
 
- 	    return ESP_ERR_HTTPD_RESP_SEND;
 
- 	p += sent;
 
- 	len -= sent;
 
-     }
 
-     return ESP_OK;
 
- }
 
- /* Create a file pointer from an http request */
 
- static ssize_t httpd_io_read(void *cookie, char *buf, size_t n)
 
- {
 
-     int rv = httpd_req_recv(cookie, buf, n);
 
-     return rv < 0 ? -1 : rv;
 
- }
 
- static ssize_t httpd_io_write(void *cookie, const char *buf, size_t n)
 
- {
 
-     return httpd_resp_send_chunk(cookie, buf, n) ? 0 : n;
 
- }
 
- static int httpd_io_close_write(void *cookie)
 
- {
 
-     return httpd_resp_send_chunk(cookie, NULL, 0) ? -1 : 0;
 
- }
 
- static FILE *httpd_fopen_read(httpd_req_t *req)
 
- {
 
-     static const cookie_io_functions_t http_io_read_funcs = {
 
- 	.read  = httpd_io_read,
 
- 	.write = NULL,		/* Not writeable */
 
- 	.seek  = NULL,		/* Not seekable */
 
- 	.close = NULL,
 
-     };
 
-     return fopencookie((void *)req, "r", http_io_read_funcs);
 
- }
 
- static FILE *httpd_fopen_write(httpd_req_t *req)
 
- {
 
-     static const cookie_io_functions_t http_io_write_funcs = {
 
- 	.read  = httpd_io_read,
 
- 	.write = httpd_io_write,
 
- 	.seek  = NULL,		/* Not seekable */
 
- 	.close = httpd_io_close_write
 
-     };
 
-     return fopencookie((void *)req, "r+", http_io_write_funcs);
 
- }
 
- #define TIMEBUF_LEN 32
 
- static const char *http_date(const struct tm *when)
 
- {
 
-     static char timebuf[32];
 
-     strftime(timebuf, sizeof timebuf,
 
- 	     "%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)
 
- {
 
-     /*
 
-      * This is a variation on Zeller's congruence with a table lookup
 
-      * for the month. The table contains the number of days since March 1,
 
-      * mod 7 (for Jan and Feb, from March 1 of the *previous year*.)
 
-      *
 
-      * Sample test cases:
 
-      *    Wed Mar  1 0000
 
-      *    Thu Jan  1 1970
 
-      *	  Wed Apr 27 2022
 
-      *    Mon Feb 28 2000
 
-      *    Wed Mar  1 2000
 
-      *    Sun Feb 28 2100
 
-      *    Mon Mar  1 2100
 
-      */
 
-     static const uint8_t md[12]	= { 5, 1, 0, 3, 5, 1, 3, 6, 2, 4, 0, 2 };
 
-     unsigned int c, y, m, d;
 
-     y = tm->tm_year + 1900;
 
-     m = tm->tm_mon;
 
-     d = tm->tm_mday;
 
-     if (m < 2)
 
- 	y--;			/* Jan, Feb */
 
-     c = y/100;
 
-     /*
 
-      * 2 represents the base date of Tue Feb 29 0000
 
-      *
 
-      * 0 = Sun, 6 = Sat
 
-      */
 
-     tm->tm_wday = (d + md[m] + y + (y >> 2) - c + (c >> 2) + 2) % 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 const char text_plain[] = "text/plain; charset=\"UTF-8\"";
 
- static void httpd_print_request(const httpd_req_t *req)
 
- {
 
-     printf("[HTTP] %s %s\n", http_method_str(req->method), req->uri);
 
- }
 
- static char *httpd_req_get_hdr(httpd_req_t *req, const char *field, size_t *lenp)
 
- {
 
-     size_t len = httpd_req_get_hdr_value_len(req, field);
 
-     char *val  = NULL;
 
-     if (len) {
 
- 	val = malloc(len+1);
 
- 	if (val) {
 
- 	    httpd_req_get_hdr_value_str(req, field, val, len+1);
 
- 	    val[len] = '\0';
 
- 	}
 
-     }
 
-     if (lenp)
 
- 	*lenp = len;
 
-     return val;
 
- }
 
- enum hsp_flags {
 
-     HSP_CRLF         = 1,    /* Append CR LF to the body */
 
-     HSP_CLOSE        = 2,    /* Add a Connection: close header */
 
-     HSP_REFERER      = 4,    /* Use referer as body (for redirects) */
 
-     HSP_UNCACHE      = 8     /* Wipe caches, please... */
 
- };
 
- static esp_err_t httpd_send_plain(httpd_req_t *req,
 
- 				  unsigned int rcode,
 
- 				  const char *body, size_t blen,
 
- 				  enum hsp_flags flags, unsigned int refresh)
 
- {
 
-     char *header = NULL;
 
-     esp_err_t err;
 
-     int hlen;
 
-     const char *now = http_now();
 
-     MSG("http_send_plain %u \"%.*s\"\n", rcode, (int)blen, body);
 
-     if (rcode > 499)
 
- 	flags |= HSP_CLOSE;
 
-     const char *closer = flags & HSP_CLOSE ? "Connection: close\r\n" : "";
 
-     bool redirect = rcode >= 300 && rcode <= 399;
 
-     char *referer = NULL;
 
-     char *refresher_buf = NULL;
 
-     if (refresh || (flags & HSP_REFERER)) {
 
- 	size_t referer_len;
 
- 	referer = httpd_req_get_hdr(req, "Referer", &referer_len);
 
- 	/* "Effective" referer */
 
- 	const char * const ereferer = referer ? referer : "/";
 
- 	if (refresh) {
 
- 	    asprintf(&refresher_buf, "Refresh: %u;url=%s\r\n",
 
- 		     refresh, ereferer);
 
- 	}
 
- 	if (flags & HSP_REFERER) {
 
- 	    body = ereferer;
 
- 	    blen = referer ? referer_len : 1;
 
- 	}
 
-     }
 
-     const char * const refresher = refresher_buf ? refresher_buf : "";
 
-     const char * const uncacher = (flags & HSP_UNCACHE)
 
-       ? "Clear-Site-Data: \"cache\"\r\n" : "";
 
-     if (redirect) {
 
- 	/* \0 -> \n so don't include it */
 
- 	size_t blenadj = sizeof("3xx Redirect \r");
 
- 	/* Always CR LF */
 
- 	flags |= HSP_CRLF;
 
- 	/* Drop any CR LF already in the redirect string */
 
- 	for (size_t bchk = 0; bchk < blen; bchk++) {
 
- 	    if (body[bchk] == '\r' || body[bchk] == '\n') {
 
- 		blen = bchk;
 
- 		break;
 
- 	    }
 
- 	}
 
- 	hlen = asprintf(&header,
 
- 			"HTTP/1.1 %u\r\n"
 
- 			"Content-Type: %s\r\n"
 
- 			"Content-Length: %zu\r\n"
 
- 			"Date %s\r\n"
 
- 			"Location: %.*s\r\n"
 
- 			"%s%s%s"
 
- 			"\r\n"
 
- 			"%3u Redirect ",
 
- 			rcode, text_plain, blen + blenadj,
 
- 			now, (int)blen, body,
 
- 			closer, refresher, uncacher,
 
- 			rcode);
 
-     } else {
 
- 	size_t blenadj = (flags & HSP_CRLF) ? 2 : 0;
 
- 	hlen = asprintf(&header,
 
- 			"HTTP/1.1 %u\r\n"
 
- 			"Content-Type: %s\r\n"
 
- 			"Content-Length: %zu\r\n"
 
- 			"Cache-Control: no-cache\r\n"
 
- 			"Date: %s\r\n"
 
- 			"%s%s%s"
 
- 			"\r\n",
 
- 			rcode, text_plain, blen + blenadj, now,
 
- 			closer, refresher, uncacher);
 
-     }
 
-     if (refresher_buf)
 
- 	free(refresher_buf);
 
-     if (referer)
 
- 	free(referer);
 
-     if (!header)
 
- 	return ESP_ERR_NO_MEM;
 
-     err = httpd_send_all(req, header, hlen);
 
-     if (!err && blen) {
 
- 	err = httpd_send_all(req, body, blen);
 
-     }
 
-     if (!err && (flags & HSP_CRLF)) {
 
- 	err = httpd_send_all(req, "\r\n", 2);
 
-     }
 
-     if (header)
 
- 	free(header);
 
-     return err;
 
- }
 
- #define SL(s) (s), (sizeof(s)-1)
 
- #define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), SL(s), HSP_CRLF, 0)
 
- static esp_err_t httpd_err_enoent(httpd_req_t *req)
 
- {
 
-     return HTTP_ERR(req, 404, "URI not found");
 
- }
 
- static esp_err_t httpd_send_ok(httpd_req_t *req)
 
- {
 
-     return HTTP_ERR(req, 200, "OK");
 
- }
 
- static esp_err_t httpd_err_enomem(httpd_req_t *req)
 
- {
 
-     return HTTP_ERR(req, 503, "Out of memory");
 
- }
 
- static esp_err_t httpd_update_done(httpd_req_t *req, const char *what, int err)
 
- {
 
-     char *response = NULL;
 
-     int len;
 
-     unsigned int reboot_time = reboot_delayed();
 
-     if (err) {
 
- 	len = asprintf(&response,
 
- 		       "%s update failed: %s\r\n"
 
- 		       "Rebooting in %u seconds\r\n",
 
- 		       what, firmware_errstr(err), reboot_time);
 
-     } else {
 
- 	len = asprintf(&response,
 
- 		       "%s update complete\r\n"
 
- 		       "Rebooting in %u seconds\r\n",
 
- 		       what, reboot_time);
 
-     }
 
-     if (!response)
 
- 	len = 0;
 
-     esp_err_t rv = httpd_send_plain(req, err ? 400 : 200, response, len,
 
- 				    HSP_CLOSE|HSP_UNCACHE, reboot_time+5);
 
-     if (response)
 
- 	free(response);
 
-     return rv;
 
- }
 
- static esp_err_t httpd_firmware_update(httpd_req_t *req)
 
- {
 
-     int rv;
 
-     /* XXX: use httpd_fopen_read() here */
 
-     rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
 
-     return httpd_update_done(req, "Firmware", rv);
 
- }
 
- static esp_err_t httpd_set_config(httpd_req_t *req, const char *query)
 
- {
 
-     FILE *f;
 
-     size_t qlen;
 
-     int rv1 = 0;
 
-     if (query) {
 
- 	rv1 = set_config_url_string(query);
 
-     }
 
-     int rv2 = 0;
 
-     f = NULL;
 
-     if (req->content_len) {
 
- 	f = httpd_fopen_read(req);
 
- 	if (!f)
 
- 	    return HTTP_ERR(req, 500, "Unable to get request handle");
 
-     }
 
-     rv2 = read_config(f, true);
 
-     if (f)
 
- 	fclose(f);
 
-     return httpd_update_done(req, "Configuration", rv1 ? rv1 : rv2);
 
- }
 
- #define MIN_STATUS_REF 1	/* Minimum refresh time in s */
 
- static void httpd_get_status_extra(FILE *f, httpd_req_t *req)
 
- {
 
-     static const char refresh_time_config[] = "http.status.refresh";
 
-     char timebuf[64];
 
-     size_t len;
 
-     struct timeval tv;
 
-     unsigned long statref;
 
-     statref = Max(getenv_ul(refresh_time_config, 0), MIN_STATUS_REF);
 
-     if (httpd_req_get_url_query_str(req, timebuf, sizeof timebuf) == ESP_OK &&
 
- 	*timebuf) {
 
- 	char *ep;
 
- 	unsigned long newstatref = strtoul(timebuf, &ep, 10);
 
- 	if (!*ep && newstatref >= MIN_STATUS_REF && newstatref != statref) {
 
- 	    statref = newstatref;
 
- 	    setenv_config(refresh_time_config, timebuf);
 
- 	    read_config(NULL, true); /* Save changed config */
 
- 	}
 
-     }
 
-     fprintf(f, "%s=%lu\n", refresh_time_config, statref);
 
-     gettimeofday(&tv,NULL);
 
-     const struct tm *tm = localtime(&tv.tv_sec);
 
-     len = strftime(timebuf, sizeof timebuf, "localtime=%Y-%m-%d %H:%M:%S.", tm);
 
-     snprintf(timebuf+len, sizeof timebuf - len, "%06u",
 
- 	     (unsigned long)tv.tv_usec);
 
-     len += 3;
 
-     len += strftime(timebuf+len, sizeof timebuf - len, " %z (%Z)\n", tm);
 
-     fwrite(timebuf, 1, len, f);
 
- }
 
- static esp_err_t httpd_get_config_status(httpd_req_t *req, bool status)
 
- {
 
-     FILE *f = httpd_fopen_write(req);
 
-     if (!f)
 
- 	return HTTP_ERR(req, 500, "Unable to get request handle");
 
-     httpd_resp_set_type(req, text_plain);
 
-     httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
 
-     if (status)
 
- 	httpd_get_status_extra(f, req);
 
-     int rv = write_env(f, status);
 
-     fclose(f);
 
-     return rv ? ESP_FAIL : ESP_OK;
 
- }
 
- static esp_err_t httpd_set_lang(httpd_req_t *req, const char *query)
 
- {
 
-     if (query) {
 
- 	int qlen = strlen(query);
 
- 	setenv_config("LANG", qlen && qlen <= MAX_LANG_LEN ? query : NULL);
 
- 	read_config(NULL, true);	/* Save configuration */
 
-     }
 
-     /*
 
-      * 303 = "See other": proper return code saying "this is not what
 
-      * you asked for, this is *about* what you asked for"; see spec
 
-      * but this is exactly what we want here.
 
-      */
 
-     return httpd_send_plain(req, 303, NULL, 0, HSP_REFERER, 0);
 
- }
 
- static esp_err_t httpd_get_lang(httpd_req_t *req)
 
- {
 
-     const char *lang = getenv_def("LANG", "");
 
-     return httpd_send_plain(req, 200, lang, strlen(lang), HSP_CRLF, 0);
 
- }
 
- static esp_err_t httpd_lang_redirect(httpd_req_t *req)
 
- {
 
-     char lang_buf[sizeof req->uri + MAX_LANG_LEN];
 
-     int len = snprintf(lang_buf, sizeof lang_buf, "/lang/%s%s",
 
- 		       getenv_def("LANG", fallback_language),
 
- 		       req->uri + (sizeof("/sys/lang")-1));
 
-     return httpd_send_plain(req, 302, lang_buf, len, 0, 0);
 
- }
 
- #define STRING_MATCHES(str, len, what) \
 
-     (((len) == sizeof(what)-1) && !memcmp((str), (what), sizeof(what)-1))
 
- #define STRING_MATCHES_PREFIX(str, len, what) \
 
-     (((len) >= sizeof(what)-1) && !memcmp((str), (what), sizeof(what)-1))
 
- static esp_err_t httpd_sys_handler(httpd_req_t *req)
 
- {
 
-     httpd_print_request(req);
 
-     if (req->method == HTTP_POST &&
 
- 	!httpd_req_get_hdr_value_len(req, "Content-Length")) {
 
- 	return HTTP_ERR(req, 411, "Length required");
 
-     }
 
-     const char *query = strchrnul(req->uri, '?');
 
-     if (query < req->uri+5 || memcmp(req->uri, "/sys/", 5))
 
- 	return httpd_err_enoent(req); /* This should never happen */
 
-     const char *file = req->uri + 5;
 
-     size_t filelen = query - file;
 
-     query = *query == '?' ? query+1 : NULL;
 
-     if (STRING_MATCHES_PREFIX(file, filelen, "lang"))
 
- 	return httpd_lang_redirect(req);
 
-     if (STRING_MATCHES(file, filelen, "getstatus"))
 
- 	return httpd_get_config_status(req, true);
 
-     if (STRING_MATCHES(file, filelen, "getconfig"))
 
- 	return httpd_get_config_status(req, false);
 
-     if (req->method == HTTP_POST && STRING_MATCHES(file, filelen, "fwupdate"))
 
- 	return httpd_firmware_update(req);
 
-     if (STRING_MATCHES(file, filelen, "setconfig"))
 
- 	return httpd_set_config(req, query);
 
-     if (STRING_MATCHES(file, filelen, "getlang"))
 
- 	return httpd_get_lang(req);
 
-     if (STRING_MATCHES(file, filelen, "setlang"))
 
- 	return httpd_set_lang(req, query);
 
-     return httpd_err_enoent(req);
 
- }
 
- 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 */
 
- #define MT_REDIR	2	/* It is a redirect */
 
- 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"  },
 
-     { ".capt",  5, 0,          "application/captive+json"  },
 
-     { "_redir", 6, MT_REDIR,   NULL                        },
 
-     { NULL,     0, MT_CHARSET, "text/plain"                }  /* default */
 
- };
 
- static esp_err_t httpd_static_handler(httpd_req_t *req)
 
- {
 
-     size_t buffer_size = UNZ_BUFSIZE;
 
-     const char *uri, *enduri;
 
-     bool is_dir;
 
-     char *buffer = NULL;
 
-     ZIPFILE *zip = NULL;
 
-     unzFile unz  = NULL;
 
-     bool file_open = false;
 
-     int err = 0;
 
-     size_t len;
 
-     httpd_print_request(req);
 
-     uri = req->uri;
 
-     while (*uri == '/')
 
- 	uri++;			/* Skip leading slashes */
 
-     const char *first_slash = NULL, *last_slash = NULL;
 
-     for (enduri = uri; *enduri; enduri++) {
 
- 	if (*enduri == '/') {
 
- 	    last_slash = enduri;
 
- 	    if (!first_slash)
 
- 		first_slash = enduri;
 
- 	}
 
-     }
 
-     if (enduri == uri) {
 
- 	is_dir = true;
 
-     } else if (last_slash == enduri-1) {
 
- 	is_dir = true;
 
- 	enduri--;		/* Drop terminal slash */
 
- 	if (first_slash == last_slash)
 
- 	    first_slash = NULL;
 
-     } else {
 
- 	is_dir = false;	/* Try the plain filename first */
 
-     }
 
-     const size_t filename_buffer_size =
 
- 	(enduri - uri) + 2 + sizeof redir_filename;
 
-     if (buffer_size < filename_buffer_size)
 
- 	buffer_size = filename_buffer_size;
 
-     buffer = malloc(buffer_size);
 
-     zip = malloc(sizeof *zip);
 
-     if (!buffer || !zip) {
 
- 	err = httpd_err_enomem(req);
 
- 	goto out;
 
-     }
 
-     char * const filebase = buffer + 1;
 
-     char * const endbase  = mempcpy(filebase, uri, enduri - uri);
 
-     filebase[-1] = '/';
 
-     unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
 
- 		  zip, NULL, NULL, NULL, NULL);
 
-     if (!unz) {
 
- 	MSG("[HTTP] unzOpen failed!\n");
 
- 	err = HTTP_ERR(req, 500, "Cannot open content archive");
 
- 	goto out;
 
-     }
 
-     char * const filename = filebase; /* Separate for future needs */
 
-     char *endfile = endbase;
 
-     unsigned int m;
 
-     bool found = false;
 
-     for (m = is_dir ? 2 : 0; m < 3; m++) {
 
- 	char *sx;
 
- 	switch (m) {
 
- 	default:		/* filename = /url */
 
- 	    endfile = endbase;
 
- 	    break;
 
- 	case 1:			/* filename = /url_redir */
 
- 	    sx = endbase;
 
- 	    endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
 
- 	    break;
 
- 	case 2:			/* filename = /url/_redir */
 
- 	    sx = endbase - (endbase[-1] == '/');
 
- 	    *sx++ = '/';
 
- 	    endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
 
- 	    break;
 
- 	}
 
- 	*endfile = '\0';
 
- 	if (!filename)
 
- 	    continue;
 
- 	filename[-1] = '/';
 
- 	MSG("trying to open: %s... ", filename);
 
- 	if (unzLocateFile(unz, filename, 1) == UNZ_OK) {
 
- 	    CMSG("found\n");
 
- 	    found = true;
 
- 	    break;
 
- 	} else {
 
- 	    CMSG("not found\n");
 
- 	}
 
-     }
 
-     if (!found) {
 
- 	err = httpd_err_enoent(req);
 
- 	goto out;
 
-     }
 
-     size_t filelen = endfile - filename;
 
-     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(endfile - len, mime_type->ext, len))
 
- 	    break;
 
- 	mime_type++;
 
-     }
 
-     MSG("found %s ext %s type %s\n",
 
- 	filename,
 
- 	mime_type->ext ? mime_type->ext : "(none)",
 
- 	mime_type->mime ? mime_type->mime : "(none)");
 
-     unz_file_info fileinfo;
 
-     memset(&fileinfo, 0, sizeof fileinfo);
 
-     unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
 
-     MSG("len %u compressed %u\n",
 
- 	fileinfo.uncompressed_size,
 
- 	fileinfo.compressed_size);
 
-     /*
 
-      * Is it a redirect?
 
-      */
 
-     if (mime_type->flags & MT_REDIR) {
 
- 	if (fileinfo.uncompressed_size > buffer_size ||
 
- 	    unzOpenCurrentFile(unz) != UNZ_OK) {
 
- 	    err = HTTP_ERR(req, 500, "Cannot open file in archive");
 
- 	    goto out;
 
- 	}
 
- 	file_open = true;
 
- 	len = fileinfo.uncompressed_size;
 
- 	if (unzReadCurrentFile(unz, buffer, len) != len) {
 
- 	    err = ESP_ERR_HTTPD_RESULT_TRUNC;
 
- 	    goto out;
 
- 	}
 
- 	MSG("redirect: %.*s\n", (int)len, buffer);
 
- 	err = httpd_send_plain(req, 302, buffer, len, 0, 0);
 
- 	goto out;
 
-     }
 
-     /*
 
-      * 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 (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"
 
- 		   "Cache-Control: max-age=10, immutable\r\n"
 
- 		   "ETag: %s\r\n",
 
- 		   response,
 
- 		   http_now(),
 
- 		   etag);
 
-     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"
 
- 			"Connection: close\r\n"
 
- 			"Last-Modified: %s\r\n",
 
- 			mime_type->mime, mime_extra,
 
- 			fileinfo.uncompressed_size,
 
- 			http_dos_date(fileinfo.dosDate));
 
-     }
 
-     if (len >= buffer_size-2) {
 
- 	err = HTTP_ERR(req, 500, "buffer_size too small");
 
- 	goto out;
 
-     }
 
-     buffer[len++] = '\r';
 
-     buffer[len++] = '\n';
 
-     err = httpd_send_all(req, buffer, len);
 
-     if (skip_body || err) {
 
- 	/* No need to spend time uncompressing the file content */
 
- 	goto out;
 
-     }
 
-     if (unzOpenCurrentFile(unz) != UNZ_OK) {
 
- 	err = HTTP_ERR(req, 400, "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_all(req, buffer, chunk);
 
- 	if (err)
 
- 	    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;
 
- }
 
- /*
 
-  * Match a URL against a path prefix. To keep httpd from refusing to
 
-  * register subpaths, the root template does not include the leading
 
-  * '/', but uri is required to have it. Do not include a trailing /
 
-  * in prefix; it is implied.
 
-  */
 
- static bool httpd_uri_match_prefix(const char *template, const char *uri,
 
- 				   size_t len)
 
- {
 
- #if 0
 
-     printf("[HTTP] matching URI \"%.*s\" against template \"%s\"\n",
 
- 	   len, uri, template);
 
- #endif
 
-     if (!len-- || *uri++ != '/')
 
- 	return false;
 
-     /* Previous template character (leading '/' implied) */
 
-     unsigned char tp = '/';
 
-     while (1) {
 
- 	unsigned char t = *template++;
 
- 	unsigned char u;
 
- 	if (!len-- || !(u = *uri++)) {
 
- 	    return !t;
 
- 	} else if (!t) {
 
- 	    return tp == '/' || u == '/';
 
- 	} else if (t != u) {
 
- 	    return false;
 
- 	}
 
- 	tp = t;
 
-     }
 
- }
 
- /* Do not include leading or trailing /; most specific prefix first */
 
- static const httpd_uri_t uri_handlers[] = {
 
-     {
 
- 	.uri      = "sys",
 
- 	.method   = HTTP_GET,
 
- 	.handler  = httpd_sys_handler,
 
- 	.user_ctx = NULL
 
-     },
 
-     {
 
- 	.uri      = "",
 
- 	.method   = HTTP_GET,
 
- 	.handler  = httpd_static_handler,
 
- 	.user_ctx = NULL
 
-     },
 
-     {
 
- 	.uri      = "",
 
- 	.method   = HTTP_HEAD,
 
- 	.handler  = httpd_static_handler,
 
- 	.user_ctx = NULL
 
-     },
 
-     {
 
- 	.uri      = "sys",
 
- 	.method   = HTTP_POST,
 
- 	.handler  = httpd_sys_handler,
 
- 	.user_ctx = NULL
 
-     },
 
- };
 
- void my_httpd_stop(void)
 
- {
 
-     if (httpd) {
 
- 	esp_unregister_shutdown_handler(my_httpd_stop);
 
- 	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    = HTTPD_PRIORITY;
 
-     config.max_open_sockets = 10;
 
-     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_prefix;
 
-     if (httpd_start(&server, &config) != ESP_OK)
 
-       return;
 
-     esp_register_shutdown_handler(my_httpd_stop);
 
-     httpd = server;
 
-     for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++) {
 
- 	const httpd_uri_t * const handler = &uri_handlers[i];
 
- 	if (httpd_register_uri_handler(httpd, handler))
 
- 	    printf("[HTTP] failed to register URI handler: %s %s\n",
 
- 		   http_method_str(handler->method), handler->uri);
 
-     }
 
-     printf("[HTTP] httpd started\n");
 
- }
 
 
  |