httpd.c 24 KB

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