httpd.c 25 KB

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