| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047 | #define MODULE "httpd"#include "common.h"#include "fw.h"#include "httpd.h"#include "config.h"#include "boardinfo_esp.h"#include "lwip/sockets.h"#include "lwip/inet.h"#include <incbin.h>#include <unzipLIB.h>#define HTTPD_PRIORITY	4static 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 32static 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(httpd_req_t *req){    /* Get the client address */    union {	struct sockaddr sa;	struct sockaddr_in sin;	struct sockaddr_in6 sin6;	struct sockaddr_storage ss;    } sa = { };    char addrbuf[64];    char *p = addrbuf;    const char * const endp = addrbuf + sizeof addrbuf;    int sock = httpd_req_to_sockfd(req);    socklen_t sa_len = sizeof sa;    if (getpeername(sock, &sa.sa, &sa_len))	sa.sa.sa_family = AF_UNSPEC;    /* lwip lacks getnameinfo() */    switch (sa.sa.sa_family) {    case AF_INET:	inet_ntop(AF_INET, &sa.sin.sin_addr, p, endp - p);	p = strchr(p, '\0');	p += sprintf(p, ":%u", ntohs(sa.sin.sin_port));	break;    case AF_INET6:	*p++ = '[';	inet_ntop(AF_INET6, &sa.sin6.sin6_addr, p, endp - p);	p = strchr(p, '\0');	p += sprintf(p, "]:%u", ntohs(sa.sin6.sin6_port));	break;    case AF_UNSPEC:	strcpy(p, "[not a socket?]");	break;    default:	p += sprintf(p, "[AF %u?]", sa.sa.sa_family);	break;    }    /* sa.sin.sin_port == sa.sin6.sin6_port */    printf("[HTTP] %s %s %s\n",	   addrbuf, 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");}#if 0static esp_err_t httpd_send_ok(httpd_req_t *req){    return HTTP_ERR(req, 200, "OK");}#endifstatic esp_err_t httpd_err_enomem(httpd_req_t *req){    return HTTP_ERR(req, 503, "Out of memory");}static esp_err_t httpd_err_not_post(httpd_req_t *req){    return HTTP_ERR(req, 405, "Only POST allowed");}#define HTTPD_ASSERT_POST(req)			\    do {					\	if ((req)->method != HTTP_POST)		\	    return httpd_err_not_post(req);	\    } while (0)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;    HTTPD_ASSERT_POST(req);    /* XXX: use httpd_fopen_read() here */    rv = firmware_update_start((read_func_t)httpd_req_recv, (token_t)req, false);    if (!rv)	rv = firmware_update_wait(portMAX_DELAY);    return httpd_update_done(req, "Firmware", rv);}static esp_err_t httpd_set_config(httpd_req_t *req, const char *query){    FILE *f;    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);}static inline bool is_eol(int c){    return c == EOF || c == '\0' || c == '\r' || c == '\n';}static esp_err_t httpd_set_board_rev(httpd_req_t *req){    FILE *f = NULL;    static const char rev_prefix[] = "max80.hw.ver";    static const char rev_valid[] = "MAX80 v";    char *rev_str;    int err = Z_DATA_ERROR;    enum sbr_parse_state {	ps_start,	ps_prefix,	ps_string,	ps_skipline    };    enum sbr_parse_state state;    const char *match_ptr = NULL;    char *p = NULL;    int c;    HTTPD_ASSERT_POST(req);    rev_str = malloc(sizeof board_info.version_str);    if (!rev_str)	return httpd_err_enomem(req);    f = httpd_fopen_read(req);    if (!f) {	free(rev_str);	return HTTP_ERR(req, 500, "Unable to get request handle");    }    state = ps_start;    do {	bool eol;	c = getc(f);	eol = is_eol(c);	switch (state) {	case ps_start:	    match_ptr = rev_prefix;	    state = ps_prefix;	    /* fall through */	case ps_prefix:	    if (eol) {		state = ps_start;	    } else if (*match_ptr && c == *match_ptr) {		match_ptr++;	    } else if (!*match_ptr && c == '=') {		p = rev_str;		state = ps_string;	    } else {		state = ps_skipline;	    }	    break;	case ps_string:	    if (eol) {		*p = '\0';		if (!memcmp(rev_str, rev_valid, sizeof rev_valid - 1)) {		    /* Otherwise input truncated or invalid */		    printf("[HTTP] setting board revision: %s\n", rev_str);		    if (!board_info_set(rev_str)) {			setvar_str(status_max80_hw_ver, board_info.version_str);			err = Z_OK;		    } else {			err = FWUPDATE_ERR_CONFIG_SAVE;		    }		}		state = ps_start;	    } else if (p - rev_str >= sizeof board_info.version_str - 1) {		state = ps_skipline;	    } else {		*p++ = c;	    }	    break;	case ps_skipline:	    if (eol)		state = ps_start;	    break;	}    } while (c != EOF);    fclose(f);    free(rev_str);    return httpd_update_done(req, "board revision", err);}#define MIN_STATUS_REF 1	/* Minimum refresh time in s */static void httpd_get_status_extra(FILE *f, httpd_req_t *req){    char timebuf[64];    size_t len;    struct timeval tv;    unsigned long statref;    statref = Max(getvar_uint(config_http_status_refresh), 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;	    setvar_uint(config_http_status_refresh, statref);	    read_config(NULL, true); /* Save changed config */	}    }    fprintf(f, "http.status.refresh=%lu\n", statref);    fprintf(f, "TZ=%s\n", notempty(getenv("TZ")));    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, "%06lu",	     (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_sysvars(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);	setvar_str(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 const char *get_lang(void){    const char *lang = getvar_str(config_LANG);    if (!lang)	lang = fallback_language;    return lang;}static esp_err_t httpd_get_lang(httpd_req_t *req){    const char *lang = get_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", get_lang(),		       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 (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);    if (STRING_MATCHES(file, filelen, "setboardrev"))	return httpd_set_board_rev(req);    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, "\"%08lx:%08lx\"",	     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: %lu\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");}
 |