httpd.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. #define MODULE "httpd"
  2. #include "common.h"
  3. #include "fw.h"
  4. #include "httpd.h"
  5. #include "config.h"
  6. #include <incbin.h>
  7. #include <unzipLIB.h>
  8. #define HTTPD_PRIORITY 4
  9. static httpd_handle_t httpd;
  10. static const char fallback_language[] = "en"; /* For unknown language */
  11. static const char redir_filename[] = "_redir"; /* For a directory "file" */
  12. #define MAX_LANG_LEN 16
  13. /* Looping version of httpd_send(); this is a hidden function in the server */
  14. static esp_err_t httpd_send_all(httpd_req_t *req, const void *buf, size_t len)
  15. {
  16. const char *p = buf;
  17. while (len) {
  18. int sent = httpd_send(req, p, len);
  19. if (sent <= 0)
  20. return ESP_ERR_HTTPD_RESP_SEND;
  21. p += sent;
  22. len -= sent;
  23. }
  24. return ESP_OK;
  25. }
  26. /* Create a file pointer from an http request */
  27. static ssize_t httpd_io_read(void *cookie, char *buf, size_t n)
  28. {
  29. int rv = httpd_req_recv(cookie, buf, n);
  30. return rv < 0 ? -1 : rv;
  31. }
  32. static ssize_t httpd_io_write(void *cookie, const char *buf, size_t n)
  33. {
  34. return httpd_resp_send_chunk(cookie, buf, n) ? 0 : n;
  35. }
  36. static int httpd_io_close_write(void *cookie)
  37. {
  38. return httpd_resp_send_chunk(cookie, NULL, 0) ? -1 : 0;
  39. }
  40. static FILE *httpd_fopen_read(httpd_req_t *req)
  41. {
  42. static const cookie_io_functions_t http_io_read_funcs = {
  43. .read = httpd_io_read,
  44. .write = NULL, /* Not writeable */
  45. .seek = NULL, /* Not seekable */
  46. .close = NULL,
  47. };
  48. return fopencookie((void *)req, "r", http_io_read_funcs);
  49. }
  50. static FILE *httpd_fopen_write(httpd_req_t *req)
  51. {
  52. static const cookie_io_functions_t http_io_write_funcs = {
  53. .read = httpd_io_read,
  54. .write = httpd_io_write,
  55. .seek = NULL, /* Not seekable */
  56. .close = httpd_io_close_write
  57. };
  58. return fopencookie((void *)req, "r+", http_io_write_funcs);
  59. }
  60. #define TIMEBUF_LEN 32
  61. static const char *http_date(const struct tm *when)
  62. {
  63. static char timebuf[32];
  64. strftime(timebuf, sizeof timebuf,
  65. "%a, %d %b %Y %H:%M:%S GMT", when);
  66. return timebuf;
  67. }
  68. static const char *http_now(void)
  69. {
  70. time_t t = time(NULL);
  71. return http_date(gmtime(&t));
  72. }
  73. static struct tm *set_weekday(struct tm *tm)
  74. {
  75. /*
  76. * This is a variation on Zeller's congruence with a table lookup
  77. * for the month. The table contains the number of days since March 1,
  78. * mod 7 (for Jan and Feb, from March 1 of the *previous year*.)
  79. *
  80. * Sample test cases:
  81. * Wed Mar 1 0000
  82. * Thu Jan 1 1970
  83. * Wed Apr 27 2022
  84. * Mon Feb 28 2000
  85. * Wed Mar 1 2000
  86. * Sun Feb 28 2100
  87. * Mon Mar 1 2100
  88. */
  89. static const uint8_t md[12] = { 5, 1, 0, 3, 5, 1, 3, 6, 2, 4, 0, 2 };
  90. unsigned int c, y, m, d;
  91. y = tm->tm_year + 1900;
  92. m = tm->tm_mon;
  93. d = tm->tm_mday;
  94. if (m < 2)
  95. y--; /* Jan, Feb */
  96. c = y/100;
  97. /*
  98. * 2 represents the base date of Tue Feb 29 0000
  99. *
  100. * 0 = Sun, 6 = Sat
  101. */
  102. tm->tm_wday = (d + md[m] + y + (y >> 2) - c + (c >> 2) + 2) % 7;
  103. return tm;
  104. }
  105. static const char *http_dos_date(uint32_t dos_date)
  106. {
  107. struct tm tm;
  108. tm.tm_sec = (dos_date << 1) & 63;
  109. tm.tm_min = (dos_date >> 5) & 63;
  110. tm.tm_hour = (dos_date >> 11) & 31;
  111. tm.tm_mday = (dos_date >> 16) & 31;
  112. tm.tm_mon = ((dos_date >> 21) & 15) - 1;
  113. tm.tm_year = (dos_date >> 25) + 80;
  114. tm.tm_isdst = 0; /* Times are stored in GMT */
  115. return http_date(set_weekday(&tm));
  116. }
  117. static const char text_plain[] = "text/plain; charset=\"UTF-8\"";
  118. static void httpd_print_request(const httpd_req_t *req)
  119. {
  120. printf("[HTTP] %s %s\n", http_method_str(req->method), req->uri);
  121. }
  122. static char *httpd_req_get_hdr(httpd_req_t *req, const char *field, size_t *lenp)
  123. {
  124. size_t len = httpd_req_get_hdr_value_len(req, field);
  125. char *val = NULL;
  126. if (len) {
  127. val = malloc(len+1);
  128. if (val) {
  129. httpd_req_get_hdr_value_str(req, field, val, len+1);
  130. val[len] = '\0';
  131. }
  132. }
  133. if (lenp)
  134. *lenp = len;
  135. return val;
  136. }
  137. enum hsp_flags {
  138. HSP_CRLF = 1, /* Append CR LF to the body */
  139. HSP_CLOSE = 2, /* Add a Connection: close header */
  140. HSP_REFERER = 4, /* Use referer as body (for redirects) */
  141. HSP_UNCACHE = 8 /* Wipe caches, please... */
  142. };
  143. static esp_err_t httpd_send_plain(httpd_req_t *req,
  144. unsigned int rcode,
  145. const char *body, size_t blen,
  146. enum hsp_flags flags, unsigned int refresh)
  147. {
  148. char *header = NULL;
  149. esp_err_t err;
  150. int hlen;
  151. const char *now = http_now();
  152. MSG("http_send_plain %u \"%.*s\"\n", rcode, (int)blen, body);
  153. if (rcode > 499)
  154. flags |= HSP_CLOSE;
  155. const char *closer = flags & HSP_CLOSE ? "Connection: close\r\n" : "";
  156. bool redirect = rcode >= 300 && rcode <= 399;
  157. char *referer = NULL;
  158. char *refresher_buf = NULL;
  159. if (refresh || (flags & HSP_REFERER)) {
  160. size_t referer_len;
  161. referer = httpd_req_get_hdr(req, "Referer", &referer_len);
  162. /* "Effective" referer */
  163. const char * const ereferer = referer ? referer : "/";
  164. if (refresh) {
  165. asprintf(&refresher_buf, "Refresh: %u;url=%s\r\n",
  166. refresh, ereferer);
  167. }
  168. if (flags & HSP_REFERER) {
  169. body = ereferer;
  170. blen = referer ? referer_len : 1;
  171. }
  172. }
  173. const char * const refresher = refresher_buf ? refresher_buf : "";
  174. const char * const uncacher = (flags & HSP_UNCACHE)
  175. ? "Clear-Site-Data: \"cache\"\r\n" : "";
  176. if (redirect) {
  177. /* \0 -> \n so don't include it */
  178. size_t blenadj = sizeof("3xx Redirect \r");
  179. /* Always CR LF */
  180. flags |= HSP_CRLF;
  181. /* Drop any CR LF already in the redirect string */
  182. for (size_t bchk = 0; bchk < blen; bchk++) {
  183. if (body[bchk] == '\r' || body[bchk] == '\n') {
  184. blen = bchk;
  185. break;
  186. }
  187. }
  188. hlen = asprintf(&header,
  189. "HTTP/1.1 %u\r\n"
  190. "Content-Type: %s\r\n"
  191. "Content-Length: %zu\r\n"
  192. "Date %s\r\n"
  193. "Location: %.*s\r\n"
  194. "%s%s%s"
  195. "\r\n"
  196. "%3u Redirect ",
  197. rcode, text_plain, blen + blenadj,
  198. now, (int)blen, body,
  199. closer, refresher, uncacher,
  200. rcode);
  201. } else {
  202. size_t blenadj = (flags & HSP_CRLF) ? 2 : 0;
  203. hlen = asprintf(&header,
  204. "HTTP/1.1 %u\r\n"
  205. "Content-Type: %s\r\n"
  206. "Content-Length: %zu\r\n"
  207. "Cache-Control: no-cache\r\n"
  208. "Date: %s\r\n"
  209. "%s%s%s"
  210. "\r\n",
  211. rcode, text_plain, blen + blenadj, now,
  212. closer, refresher, uncacher);
  213. }
  214. if (refresher_buf)
  215. free(refresher_buf);
  216. if (referer)
  217. free(referer);
  218. if (!header)
  219. return ESP_ERR_NO_MEM;
  220. err = httpd_send_all(req, header, hlen);
  221. if (!err && blen) {
  222. err = httpd_send_all(req, body, blen);
  223. }
  224. if (!err && (flags & HSP_CRLF)) {
  225. err = httpd_send_all(req, "\r\n", 2);
  226. }
  227. if (header)
  228. free(header);
  229. return err;
  230. }
  231. #define SL(s) (s), (sizeof(s)-1)
  232. #define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), SL(s), HSP_CRLF, 0)
  233. static esp_err_t httpd_err_enoent(httpd_req_t *req)
  234. {
  235. return HTTP_ERR(req, 404, "URI not found");
  236. }
  237. static esp_err_t httpd_send_ok(httpd_req_t *req)
  238. {
  239. return HTTP_ERR(req, 200, "OK");
  240. }
  241. static esp_err_t httpd_err_enomem(httpd_req_t *req)
  242. {
  243. return HTTP_ERR(req, 503, "Out of memory");
  244. }
  245. static esp_err_t httpd_update_done(httpd_req_t *req, const char *what, int err)
  246. {
  247. char *response = NULL;
  248. int len;
  249. unsigned int reboot_time = reboot_delayed();
  250. if (err) {
  251. len = asprintf(&response,
  252. "%s update failed: %s\r\n"
  253. "Rebooting in %u seconds\r\n",
  254. what, firmware_errstr(err), reboot_time);
  255. } else {
  256. len = asprintf(&response,
  257. "%s update complete\r\n"
  258. "Rebooting in %u seconds\r\n",
  259. what, reboot_time);
  260. }
  261. if (!response)
  262. len = 0;
  263. esp_err_t rv = httpd_send_plain(req, err ? 400 : 200, response, len,
  264. HSP_CLOSE|HSP_UNCACHE, reboot_time+5);
  265. if (response)
  266. free(response);
  267. return rv;
  268. }
  269. static esp_err_t httpd_firmware_update(httpd_req_t *req)
  270. {
  271. int rv;
  272. /* XXX: use httpd_fopen_read() here */
  273. rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
  274. return httpd_update_done(req, "Firmware", rv);
  275. }
  276. static esp_err_t httpd_set_config(httpd_req_t *req, const char *query)
  277. {
  278. FILE *f;
  279. size_t qlen;
  280. int rv1 = 0;
  281. if (query) {
  282. rv1 = set_config_url_string(query);
  283. }
  284. int rv2 = 0;
  285. f = NULL;
  286. if (req->content_len) {
  287. f = httpd_fopen_read(req);
  288. if (!f)
  289. return HTTP_ERR(req, 500, "Unable to get request handle");
  290. }
  291. rv2 = read_config(f, true);
  292. if (f)
  293. fclose(f);
  294. return httpd_update_done(req, "Configuration", rv1 ? rv1 : rv2);
  295. }
  296. static esp_err_t httpd_get_config_status(httpd_req_t *req, bool status)
  297. {
  298. FILE *f = httpd_fopen_write(req);
  299. if (!f)
  300. return HTTP_ERR(req, 500, "Unable to get request handle");
  301. httpd_resp_set_type(req, text_plain);
  302. httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
  303. int rv = write_env(f, status);
  304. fclose(f);
  305. return rv ? ESP_FAIL : ESP_OK;
  306. }
  307. static esp_err_t httpd_get_status(httpd_req_t *req)
  308. {
  309. char timebuf[64];
  310. time_t now = time(NULL);
  311. const struct tm *tm = localtime(&now);
  312. strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M:%S %z (%Z)", tm);
  313. setenv("status.localtime", timebuf, 1);
  314. return httpd_get_config_status(req, true);
  315. }
  316. static esp_err_t httpd_set_lang(httpd_req_t *req, const char *query)
  317. {
  318. if (query) {
  319. int qlen = strlen(query);
  320. setenv_config("LANG", qlen && qlen <= MAX_LANG_LEN ? query : NULL);
  321. read_config(NULL, true); /* Save configuration */
  322. }
  323. /*
  324. * 303 = "See other": proper return code saying "this is not what
  325. * you asked for, this is *about* what you asked for"; see spec
  326. * but this is exactly what we want here.
  327. */
  328. return httpd_send_plain(req, 303, NULL, 0, HSP_REFERER, 0);
  329. }
  330. static esp_err_t httpd_get_lang(httpd_req_t *req)
  331. {
  332. const char *lang = getenv_def("LANG", "");
  333. return httpd_send_plain(req, 200, lang, strlen(lang), HSP_CRLF, 0);
  334. }
  335. static esp_err_t httpd_lang_redirect(httpd_req_t *req)
  336. {
  337. char lang_buf[sizeof("/lang/") + MAX_LANG_LEN];
  338. int len = snprintf(lang_buf, sizeof lang_buf, "/lang/%s",
  339. getenv_def("LANG", fallback_language));
  340. return httpd_send_plain(req, 302, lang_buf, len, 0, 0);
  341. }
  342. #define STRING_MATCHES(str, len, what) \
  343. (((len) == sizeof(what)-1) && !memcmp((str), (what), sizeof(what)-1))
  344. static esp_err_t httpd_sys_handler(httpd_req_t *req)
  345. {
  346. httpd_print_request(req);
  347. if (req->method == HTTP_POST &&
  348. !httpd_req_get_hdr_value_len(req, "Content-Length")) {
  349. return HTTP_ERR(req, 411, "Length required");
  350. }
  351. const char *query = strchrnul(req->uri, '?');
  352. if (query < req->uri+5 || memcmp(req->uri, "/sys/", 5))
  353. return httpd_err_enoent(req); /* This should never happen */
  354. const char *file = req->uri + 5;
  355. size_t filelen = query - file;
  356. query = *query == '?' ? query+1 : NULL;
  357. if (STRING_MATCHES(file, filelen, "lang"))
  358. return httpd_lang_redirect(req);
  359. if (STRING_MATCHES(file, filelen, "getstatus"))
  360. return httpd_get_status(req);
  361. if (STRING_MATCHES(file, filelen, "getconfig"))
  362. return httpd_get_config_status(req, false);
  363. if (req->method == HTTP_POST && STRING_MATCHES(file, filelen, "fwupdate"))
  364. return httpd_firmware_update(req);
  365. if (STRING_MATCHES(file, filelen, "setconfig"))
  366. return httpd_set_config(req, query);
  367. if (STRING_MATCHES(file, filelen, "getlang"))
  368. return httpd_get_lang(req);
  369. if (STRING_MATCHES(file, filelen, "setlang"))
  370. return httpd_set_lang(req, query);
  371. return httpd_err_enoent(req);
  372. }
  373. INCBIN_EXTERN(wwwzip);
  374. struct mime_type {
  375. const char *ext;
  376. uint16_t ext_len;
  377. uint16_t flags;
  378. const char *mime;
  379. };
  380. #define MT_CHARSET 1 /* Add charset to Content-Type */
  381. #define MT_REDIR 2 /* It is a redirect */
  382. static const struct mime_type mime_types[] = {
  383. { ".html", 5, MT_CHARSET, "text/html" },
  384. { ".xhtml", 6, MT_CHARSET, "text/html" },
  385. { ".css", 4, MT_CHARSET, "text/css" },
  386. { ".webp", 5, 0, "image/webp" },
  387. { ".jpg", 4, 0, "image/jpeg" },
  388. { ".png", 4, 0, "image/png" },
  389. { ".ico", 4, 0, "image/png" }, /* favicon.ico */
  390. { ".svg", 4, MT_CHARSET, "image/svg+xml" },
  391. { ".otf", 4, 0, "font/otf" },
  392. { ".ttf", 4, 0, "font/ttf" },
  393. { ".woff", 5, 0, "font/woff" },
  394. { ".woff2", 6, 0, "font/woff2" },
  395. { ".pdf", 4, 0, "application/pdf" },
  396. { ".js", 3, MT_CHARSET, "text/javascript" },
  397. { ".mjs", 4, MT_CHARSET, "text/javascript" },
  398. { ".json", 5, MT_CHARSET, "application/json" },
  399. { ".xml", 4, MT_CHARSET, "text/xml" },
  400. { ".bin", 4, 0, "application/octet-stream" },
  401. { ".fw", 3, 0, "application/octet-stream" },
  402. { "_redir", 6, MT_REDIR, NULL },
  403. { NULL, 0, MT_CHARSET, "text/plain" } /* default */
  404. };
  405. static esp_err_t httpd_static_handler(httpd_req_t *req)
  406. {
  407. size_t buffer_size = UNZ_BUFSIZE;
  408. const char *uri, *enduri;
  409. bool is_dir;
  410. char *buffer = NULL;
  411. ZIPFILE *zip = NULL;
  412. unzFile unz = NULL;
  413. bool file_open = false;
  414. int err = 0;
  415. size_t len;
  416. httpd_print_request(req);
  417. uri = req->uri;
  418. while (*uri == '/')
  419. uri++; /* Skip leading slashes */
  420. const char *first_slash = NULL, *last_slash = NULL;
  421. for (enduri = uri; *enduri; enduri++) {
  422. if (*enduri == '/') {
  423. last_slash = enduri;
  424. if (!first_slash)
  425. first_slash = enduri;
  426. }
  427. }
  428. if (enduri == uri) {
  429. is_dir = true;
  430. } else if (last_slash == enduri-1) {
  431. is_dir = true;
  432. enduri--; /* Drop terminal slash */
  433. if (first_slash == last_slash)
  434. first_slash = NULL;
  435. } else {
  436. is_dir = false; /* Try the plain filename first */
  437. }
  438. const size_t filename_buffer_size =
  439. (enduri - uri) + 2 + sizeof redir_filename;
  440. if (buffer_size < filename_buffer_size)
  441. buffer_size = filename_buffer_size;
  442. buffer = malloc(buffer_size);
  443. zip = malloc(sizeof *zip);
  444. if (!buffer || !zip) {
  445. err = httpd_err_enomem(req);
  446. goto out;
  447. }
  448. char * const filebase = buffer + 1;
  449. char * const endbase = mempcpy(filebase, uri, enduri - uri);
  450. filebase[-1] = '/';
  451. unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
  452. zip, NULL, NULL, NULL, NULL);
  453. if (!unz) {
  454. MSG("[HTTP] unzOpen failed!\n");
  455. err = HTTP_ERR(req, 500, "Cannot open content archive");
  456. goto out;
  457. }
  458. char * const filename = filebase; /* Separate for future needs */
  459. char *endfile = endbase;
  460. unsigned int m;
  461. bool found = false;
  462. for (m = is_dir ? 2 : 0; m < 3; m++) {
  463. char *sx;
  464. switch (m) {
  465. default: /* filename = /url */
  466. endfile = endbase;
  467. break;
  468. case 1: /* filename = /url_redir */
  469. sx = endbase;
  470. endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
  471. break;
  472. case 2: /* filename = /url/_redir */
  473. sx = endbase - (endbase[-1] == '/');
  474. *sx++ = '/';
  475. endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
  476. break;
  477. }
  478. *endfile = '\0';
  479. if (!filename)
  480. continue;
  481. filename[-1] = '/';
  482. MSG("trying to open: %s... ", filename);
  483. if (unzLocateFile(unz, filename, 1) == UNZ_OK) {
  484. CMSG("found\n");
  485. found = true;
  486. break;
  487. } else {
  488. CMSG("not found\n");
  489. }
  490. }
  491. if (!found) {
  492. err = httpd_err_enoent(req);
  493. goto out;
  494. }
  495. size_t filelen = endfile - filename;
  496. const struct mime_type *mime_type = mime_types;
  497. /* The default entry with length 0 will always match */
  498. while (mime_type->ext_len) {
  499. len = mime_type->ext_len;
  500. if (len <= filelen && !memcmp(endfile - len, mime_type->ext, len))
  501. break;
  502. mime_type++;
  503. }
  504. MSG("found %s ext %s type %s\n",
  505. filename,
  506. mime_type->ext ? mime_type->ext : "(none)",
  507. mime_type->mime ? mime_type->mime : "(none)");
  508. unz_file_info fileinfo;
  509. memset(&fileinfo, 0, sizeof fileinfo);
  510. unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
  511. MSG("len %u compressed %u\n",
  512. fileinfo.uncompressed_size,
  513. fileinfo.compressed_size);
  514. /*
  515. * Is it a redirect?
  516. */
  517. if (mime_type->flags & MT_REDIR) {
  518. if (fileinfo.uncompressed_size > buffer_size ||
  519. unzOpenCurrentFile(unz) != UNZ_OK) {
  520. err = HTTP_ERR(req, 500, "Cannot open file in archive");
  521. goto out;
  522. }
  523. file_open = true;
  524. len = fileinfo.uncompressed_size;
  525. if (unzReadCurrentFile(unz, buffer, len) != len) {
  526. err = ESP_ERR_HTTPD_RESULT_TRUNC;
  527. goto out;
  528. }
  529. MSG("redirect: %.*s\n", (int)len, buffer);
  530. err = httpd_send_plain(req, 302, buffer, len, 0, 0);
  531. goto out;
  532. }
  533. /*
  534. * Hopefully the combination of date and CRC
  535. * is strong enough to quality for a "strong" ETag
  536. */
  537. char etag[16+3+1];
  538. snprintf(etag, sizeof etag, "\"%08x:%08x\"",
  539. fileinfo.dosDate, fileinfo.crc);
  540. bool skip_body = req->method == HTTP_HEAD || !fileinfo.uncompressed_size;
  541. bool skip_meta = false;
  542. const char *response = "200 OK";
  543. if (httpd_req_get_hdr_value_str(req, "If-None-Match",
  544. buffer, buffer_size) == ESP_OK &&
  545. strstr(buffer, etag)) {
  546. skip_body = skip_meta = true;
  547. response = "304 Not Modified";
  548. }
  549. len = snprintf(buffer, buffer_size-2,
  550. "HTTP/1.1 %s\r\n"
  551. "Date: %s\r\n"
  552. "Cache-Control: max-age=10, immutable\r\n"
  553. "ETag: %s\r\n",
  554. response,
  555. http_now(),
  556. etag);
  557. if (len < buffer_size-2 && !skip_meta) {
  558. const char *mime_extra =
  559. mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "";
  560. len += snprintf(buffer + len, buffer_size-2 - len,
  561. "Content-Type: %s%s\r\n"
  562. "Content-Length: %u\r\n"
  563. "Allow: GET, HEAD\r\n"
  564. "Connection: close\r\n"
  565. "Last-Modified: %s\r\n",
  566. mime_type->mime, mime_extra,
  567. fileinfo.uncompressed_size,
  568. http_dos_date(fileinfo.dosDate));
  569. }
  570. if (len >= buffer_size-2) {
  571. err = HTTP_ERR(req, 500, "buffer_size too small");
  572. goto out;
  573. }
  574. buffer[len++] = '\r';
  575. buffer[len++] = '\n';
  576. err = httpd_send_all(req, buffer, len);
  577. if (skip_body || err) {
  578. /* No need to spend time uncompressing the file content */
  579. goto out;
  580. }
  581. if (unzOpenCurrentFile(unz) != UNZ_OK) {
  582. err = HTTP_ERR(req, 400, "Cannot open file in archive");
  583. goto out;
  584. }
  585. file_open = true;
  586. len = fileinfo.uncompressed_size;
  587. while (len) {
  588. size_t chunk = len;
  589. if (chunk > buffer_size)
  590. chunk = buffer_size;
  591. if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
  592. err = ESP_ERR_HTTPD_RESULT_TRUNC;
  593. goto out;
  594. }
  595. err = httpd_send_all(req, buffer, chunk);
  596. if (err)
  597. goto out;
  598. len -= chunk;
  599. }
  600. err = ESP_OK; /* All good! */
  601. out:
  602. if (file_open)
  603. unzCloseCurrentFile(unz);
  604. if (unz)
  605. unzClose(unz);
  606. if (zip)
  607. free(zip);
  608. if (buffer)
  609. free(buffer);
  610. return err;
  611. }
  612. /*
  613. * Match a URL against a path prefix. To keep httpd from refusing to
  614. * register subpaths, the root template does not include the leading
  615. * '/', but uri is required to have it. Do not include a trailing /
  616. * in prefix; it is implied.
  617. */
  618. static bool httpd_uri_match_prefix(const char *template, const char *uri,
  619. size_t len)
  620. {
  621. #if 0
  622. printf("[HTTP] matching URI \"%.*s\" against template \"%s\"\n",
  623. len, uri, template);
  624. #endif
  625. if (!len-- || *uri++ != '/')
  626. return false;
  627. /* Previous template character (leading '/' implied) */
  628. unsigned char tp = '/';
  629. while (1) {
  630. unsigned char t = *template++;
  631. unsigned char u;
  632. if (!len-- || !(u = *uri++)) {
  633. return !t;
  634. } else if (!t) {
  635. return tp == '/' || u == '/';
  636. } else if (t != u) {
  637. return false;
  638. }
  639. tp = t;
  640. }
  641. }
  642. /* Do not include leading or trailing /; most specific prefix first */
  643. static const httpd_uri_t uri_handlers[] = {
  644. {
  645. .uri = "sys",
  646. .method = HTTP_GET,
  647. .handler = httpd_sys_handler,
  648. .user_ctx = NULL
  649. },
  650. {
  651. .uri = "",
  652. .method = HTTP_GET,
  653. .handler = httpd_static_handler,
  654. .user_ctx = NULL
  655. },
  656. {
  657. .uri = "",
  658. .method = HTTP_HEAD,
  659. .handler = httpd_static_handler,
  660. .user_ctx = NULL
  661. },
  662. {
  663. .uri = "sys",
  664. .method = HTTP_POST,
  665. .handler = httpd_sys_handler,
  666. .user_ctx = NULL
  667. },
  668. };
  669. void my_httpd_stop(void)
  670. {
  671. if (httpd) {
  672. esp_unregister_shutdown_handler(my_httpd_stop);
  673. httpd_stop(httpd);
  674. httpd = NULL;
  675. }
  676. }
  677. void my_httpd_start(void)
  678. {
  679. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  680. httpd_handle_t server;
  681. if (httpd)
  682. return;
  683. config.task_priority = HTTPD_PRIORITY;
  684. config.max_open_sockets = 10;
  685. printf("[HTTP] Default stack size: %zu\n", config.stack_size);
  686. config.stack_size <<= 2;
  687. printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
  688. config.uri_match_fn = httpd_uri_match_prefix;
  689. if (httpd_start(&server, &config) != ESP_OK)
  690. return;
  691. esp_register_shutdown_handler(my_httpd_stop);
  692. httpd = server;
  693. for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++) {
  694. const httpd_uri_t * const handler = &uri_handlers[i];
  695. if (httpd_register_uri_handler(httpd, handler))
  696. printf("[HTTP] failed to register URI handler: %s %s\n",
  697. http_method_str(handler->method), handler->uri);
  698. }
  699. printf("[HTTP] httpd started\n");
  700. }