httpd.c 20 KB

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