httpd.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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. /* Looping version of httpd_send(); this is a hidden function in the server */
  10. static esp_err_t httpd_send_all(httpd_req_t *req, const void *buf, size_t len)
  11. {
  12. const char *p = buf;
  13. while (len) {
  14. int sent = httpd_send(req, p, len);
  15. if (sent <= 0)
  16. return ESP_ERR_HTTPD_RESP_SEND;
  17. p += sent;
  18. len -= sent;
  19. }
  20. return ESP_OK;
  21. }
  22. /* Create a file pointer from an http request */
  23. static ssize_t httpd_io_read(void *cookie, char *buf, size_t n)
  24. {
  25. int rv = httpd_req_recv(cookie, buf, n);
  26. return rv < 0 ? -1 : rv;
  27. }
  28. static ssize_t httpd_io_write(void *cookie, const char *buf, size_t n)
  29. {
  30. return httpd_resp_send_chunk(cookie, buf, n) ? 0 : n;
  31. }
  32. static int httpd_io_close_write(void *cookie)
  33. {
  34. return httpd_resp_send_chunk(cookie, NULL, 0) ? -1 : 0;
  35. }
  36. static FILE *httpd_fopen_read(httpd_req_t *req)
  37. {
  38. static const cookie_io_functions_t http_io_read_funcs = {
  39. .read = httpd_io_read,
  40. .write = NULL, /* Not writeable */
  41. .seek = NULL, /* Not seekable */
  42. .close = NULL,
  43. };
  44. return fopencookie((void *)req, "r", http_io_read_funcs);
  45. }
  46. static FILE *httpd_fopen_write(httpd_req_t *req)
  47. {
  48. static const cookie_io_functions_t http_io_write_funcs = {
  49. .read = httpd_io_read,
  50. .write = httpd_io_write,
  51. .seek = NULL, /* Not seekable */
  52. .close = httpd_io_close_write
  53. };
  54. return fopencookie((void *)req, "r+", http_io_write_funcs);
  55. }
  56. #define TIMEBUF_LEN 32
  57. static const char *http_date(const struct tm *when)
  58. {
  59. static char timebuf[32];
  60. strftime(timebuf, sizeof timebuf,
  61. "%a, %d %b %Y %H:%M:%S GMT", when);
  62. return timebuf;
  63. }
  64. static const char *http_now(void)
  65. {
  66. time_t t = time(NULL);
  67. return http_date(gmtime(&t));
  68. }
  69. static struct tm *set_weekday(struct tm *tm)
  70. {
  71. /*
  72. * This is a variation on Zeller's congruence with a table lookup
  73. * for the month. The table contains the number of days since March 1,
  74. * mod 7 (for Jan and Feb, from March 1 of the *previous year*.)
  75. *
  76. * Sample test cases:
  77. * Wed Mar 1 0000
  78. * Thu Jan 1 1970
  79. * Wed Apr 27 2022
  80. * Mon Feb 28 2000
  81. * Wed Mar 1 2000
  82. * Sun Feb 28 2100
  83. * Mon Mar 1 2100
  84. */
  85. static const uint8_t md[12] = { 5, 1, 0, 3, 5, 1, 3, 6, 2, 4, 0, 2 };
  86. unsigned int c, y, m, d;
  87. y = tm->tm_year + 1900;
  88. m = tm->tm_mon;
  89. d = tm->tm_mday;
  90. if (m < 2)
  91. y--; /* Jan, Feb */
  92. c = y/100;
  93. /*
  94. * 2 represents the base date of Tue Feb 29 0000
  95. *
  96. * 0 = Sun, 6 = Sat
  97. */
  98. tm->tm_wday = (d + md[m] + y + (y >> 2) - c + (c >> 2) + 2) % 7;
  99. return tm;
  100. }
  101. static const char *http_dos_date(uint32_t dos_date)
  102. {
  103. struct tm tm;
  104. tm.tm_sec = (dos_date << 1) & 63;
  105. tm.tm_min = (dos_date >> 5) & 63;
  106. tm.tm_hour = (dos_date >> 11) & 31;
  107. tm.tm_mday = (dos_date >> 16) & 31;
  108. tm.tm_mon = ((dos_date >> 21) & 15) - 1;
  109. tm.tm_year = (dos_date >> 25) + 80;
  110. tm.tm_isdst = 0; /* Times are stored in GMT */
  111. return http_date(set_weekday(&tm));
  112. }
  113. static esp_err_t httpd_send_plain(httpd_req_t *req,
  114. unsigned int rcode,
  115. const char *body, size_t blen,
  116. bool close_conn)
  117. {
  118. char *header = NULL;
  119. esp_err_t err;
  120. int hlen = asprintf(&header,
  121. "HTTP/1.1 %u\r\n"
  122. "Content-Type: text/plain; charset=\"UTF-8\"\r\n"
  123. "Content-Length: %zu\r\n"
  124. "Date: %s\r\n"
  125. "%s"
  126. "\r\n",
  127. rcode, blen, http_now(),
  128. close_conn ? "Connection: close\r\n" : "");
  129. if (!header)
  130. return ESP_ERR_NO_MEM;
  131. err = httpd_send_all(req, header, hlen);
  132. if (!err && blen) {
  133. err = httpd_send_all(req, body, blen);
  134. }
  135. if (header)
  136. free(header);
  137. return err;
  138. }
  139. #define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), s "\r\n", \
  140. sizeof(s) + 1, (e) > 399)
  141. static esp_err_t httpd_err_404(httpd_req_t *req)
  142. {
  143. return HTTP_ERR(req, 404, "URI not found");
  144. }
  145. static esp_err_t httpd_send_ok(httpd_req_t *req)
  146. {
  147. return HTTP_ERR(req, 200, "OK");
  148. }
  149. static esp_err_t httpd_update_done(httpd_req_t *req, const char *what)
  150. {
  151. char *response = NULL;
  152. int len = asprintf(&response,
  153. "%s complete\r\n"
  154. "Rebooting in %u seconds\r\n",
  155. what, reboot_delayed());
  156. if (!response)
  157. len = 0;
  158. esp_err_t err = httpd_send_plain(req, 200, response, len, true);
  159. if (response)
  160. free(response);
  161. return err;
  162. }
  163. static esp_err_t httpd_firmware_update(httpd_req_t *req)
  164. {
  165. int rv;
  166. /* XXX: use httpd_fopen_read() here */
  167. rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
  168. if (rv == FWUPDATE_ERR_IN_PROGRESS)
  169. return HTTP_ERR(req, 409, "Firmware update already in progress");
  170. else if (rv)
  171. return HTTP_ERR(req, 500, "Firmware update failed");
  172. return httpd_update_done(req, "Firmware update");
  173. }
  174. static esp_err_t httpd_set_config(httpd_req_t *req)
  175. {
  176. FILE *f = httpd_fopen_read(req);
  177. if (!f)
  178. return HTTP_ERR(req, 500, "Unable to get request handle");
  179. int rv = read_config(f);
  180. fclose(f);
  181. if (rv || save_config())
  182. return HTTP_ERR(req, 503, "Unable to update configuration");
  183. return httpd_update_done(req, "Configuration update");
  184. }
  185. static esp_err_t httpd_sys_post_handler(httpd_req_t *req)
  186. {
  187. printf("[POST] len = %zu uri = \"%s\"\n",
  188. req->content_len, req->uri);
  189. if (!req->content_len)
  190. return HTTP_ERR(req, 411, "Length required");
  191. if (!strcmp(req->uri, "/sys/fwupdate"))
  192. return httpd_firmware_update(req);
  193. if (!strcmp(req->uri, "/sys/setconfig"))
  194. return httpd_set_config(req);
  195. return httpd_err_404(req);
  196. }
  197. static esp_err_t httpd_get_config(httpd_req_t *req)
  198. {
  199. FILE *f = httpd_fopen_write(req);
  200. if (!f)
  201. return HTTP_ERR(req, 500, "Unable to get request handle");
  202. httpd_resp_set_type(req, "text/plain; charset=\"UTF-8\"");
  203. int rv = write_config(f);
  204. fclose(f);
  205. return rv ? ESP_FAIL : ESP_OK;
  206. }
  207. static esp_err_t httpd_sys_get_handler(httpd_req_t *req)
  208. {
  209. if (!strcmp(req->uri, "/sys/getconfig"))
  210. return httpd_get_config(req);
  211. return httpd_err_404(req);
  212. }
  213. INCBIN_EXTERN(wwwzip);
  214. struct mime_type {
  215. const char *ext;
  216. uint16_t ext_len;
  217. uint16_t flags;
  218. const char *mime;
  219. };
  220. #define MT_CHARSET 1 /* Add charset to Content-Type */
  221. static const struct mime_type mime_types[] = {
  222. { ".html", 5, MT_CHARSET, "text/html" },
  223. { ".xhtml", 6, MT_CHARSET, "text/html" },
  224. { ".css", 4, MT_CHARSET, "text/css" },
  225. { ".webp", 5, 0, "image/webp" },
  226. { ".jpg", 4, 0, "image/jpeg" },
  227. { ".png", 4, 0, "image/png" },
  228. { ".ico", 4, 0, "image/png" }, /* favicon.ico */
  229. { ".svg", 4, MT_CHARSET, "image/svg+xml" },
  230. { ".otf", 4, 0, "font/otf" },
  231. { ".ttf", 4, 0, "font/ttf" },
  232. { ".woff", 5, 0, "font/woff" },
  233. { ".woff2", 6, 0, "font/woff2" },
  234. { ".pdf", 4, 0, "application/pdf" },
  235. { ".js", 3, MT_CHARSET, "text/javascript" },
  236. { ".mjs", 4, MT_CHARSET, "text/javascript" },
  237. { ".json", 5, MT_CHARSET, "application/json" },
  238. { ".xml", 4, MT_CHARSET, "text/xml" },
  239. { ".bin", 4, 0, "application/octet-stream" },
  240. { ".fw", 3, 0, "application/octet-stream" },
  241. { NULL, 0, MT_CHARSET, "text/plain" } /* default */
  242. };
  243. static esp_err_t httpd_static_handler(httpd_req_t *req)
  244. {
  245. static const char index_filename[] = "index.html";
  246. const size_t buffer_size = UNZ_BUFSIZE;
  247. const char *uri, *enduri;
  248. bool add_index;
  249. char *buffer = NULL;
  250. ZIPFILE *zip = NULL;
  251. unzFile unz = NULL;
  252. bool file_open = false;
  253. int err = 0;
  254. size_t len;
  255. uri = req->uri;
  256. while (*uri == '/')
  257. uri++; /* Skip leading slashes */
  258. enduri = strchr(uri, '\0');
  259. if (enduri == uri) {
  260. add_index = true;
  261. } else if (enduri[-1] == '/') {
  262. add_index = true;
  263. enduri--; /* Drop terminal slash */
  264. } else {
  265. add_index = false; /* Try the plain filename first */
  266. }
  267. MSG("requesting: /%.*s\n", enduri - uri, uri);
  268. buffer = malloc(buffer_size);
  269. zip = malloc(sizeof *zip);
  270. if (!buffer || !zip) {
  271. err = HTTP_ERR(req, 503, "Out of memory");
  272. goto out;
  273. }
  274. if (enduri - uri + 1 + sizeof index_filename >= buffer_size) {
  275. err = HTTP_ERR(req, 414, "URI too long");
  276. goto out;
  277. }
  278. char *p = mempcpy(buffer, uri, enduri - uri);
  279. *p = '\0';
  280. unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
  281. zip, NULL, NULL, NULL, NULL);
  282. if (!unz) {
  283. MSG("[HTTP] unzOpen failed!\n");
  284. err = HTTP_ERR(req, 500, "Cannot open content archive");
  285. goto out;
  286. }
  287. while (1) {
  288. if (add_index) {
  289. if (p > buffer)
  290. *p++ = '/';
  291. memcpy(p, index_filename, sizeof index_filename);
  292. p += sizeof index_filename - 1; /* Point to final NUL */
  293. }
  294. MSG("trying to open: %s\n", buffer);
  295. if (unzLocateFile(unz, buffer, 1) == UNZ_OK)
  296. break;
  297. if (add_index) {
  298. err = httpd_err_404(req);
  299. goto out;
  300. }
  301. add_index = true; /* Try again with the index filename */
  302. }
  303. /* Note: p points to the end of the filename string */
  304. size_t filelen = p - buffer;
  305. const struct mime_type *mime_type = mime_types;
  306. /* The default entry with length 0 will always match */
  307. while (mime_type->ext_len) {
  308. len = mime_type->ext_len;
  309. if (len < filelen && !memcmp(p - len, mime_type->ext, len))
  310. break;
  311. mime_type++;
  312. }
  313. unz_file_info fileinfo;
  314. memset(&fileinfo, 0, sizeof fileinfo);
  315. unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
  316. /*
  317. * Hopefully the combination of date and CRC
  318. * is strong enough to quality for a "strong" ETag
  319. */
  320. char etag[16+3+1];
  321. snprintf(etag, sizeof etag, "\"%08x:%08x\"",
  322. fileinfo.dosDate, fileinfo.crc);
  323. bool skip_body = req->method == HTTP_HEAD || !fileinfo.uncompressed_size;
  324. bool skip_meta = false;
  325. const char *response = "200 OK";
  326. if (httpd_req_get_hdr_value_str(req, "If-None-Match",
  327. buffer, buffer_size) == ESP_OK &&
  328. strstr(buffer, etag)) {
  329. skip_body = skip_meta = true;
  330. response = "304 Not Modified";
  331. }
  332. len = snprintf(buffer, buffer_size-2,
  333. "HTTP/1.1 %s\r\n"
  334. "Date: %s\r\n"
  335. "ETag: %s\r\n",
  336. response,
  337. http_now(),
  338. etag);
  339. if (len < buffer_size-2 && !skip_meta) {
  340. const char *mime_extra =
  341. mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "";
  342. len += snprintf(buffer + len, buffer_size-2 - len,
  343. "Content-Type: %s%s\r\n"
  344. "Content-Length: %u\r\n"
  345. "Allow: GET, HEAD\r\n"
  346. "Last-Modified: %s\r\n",
  347. mime_type->mime, mime_extra,
  348. fileinfo.uncompressed_size,
  349. http_dos_date(fileinfo.dosDate));
  350. }
  351. if (len >= buffer_size-2) {
  352. err = HTTP_ERR(req, 500, "buffer_size too small");
  353. goto out;
  354. }
  355. buffer[len++] = '\r';
  356. buffer[len++] = '\n';
  357. err = httpd_send_all(req, buffer, len);
  358. if (skip_body || err) {
  359. /* No need to spend time uncompressing the file content */
  360. goto out;
  361. }
  362. if (unzOpenCurrentFile(unz) != UNZ_OK) {
  363. err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
  364. goto out;
  365. }
  366. file_open = true;
  367. len = fileinfo.uncompressed_size;
  368. while (len) {
  369. size_t chunk = len;
  370. if (chunk > buffer_size)
  371. chunk = buffer_size;
  372. if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
  373. err = ESP_ERR_HTTPD_RESULT_TRUNC;
  374. goto out;
  375. }
  376. err = httpd_send_all(req, buffer, chunk);
  377. if (err)
  378. goto out;
  379. len -= chunk;
  380. }
  381. err = ESP_OK; /* All good! */
  382. out:
  383. if (file_open)
  384. unzCloseCurrentFile(unz);
  385. if (unz)
  386. unzClose(unz);
  387. if (zip)
  388. free(zip);
  389. if (buffer)
  390. free(buffer);
  391. return err;
  392. }
  393. /*
  394. * Match a URL against a path prefix. To keep httpd from refusing to
  395. * register subpaths, the root template does not include the leading
  396. * '/', but uri is required to have it. Do not include a trailing /
  397. * in prefix; it is implied.
  398. */
  399. static bool httpd_uri_match_prefix(const char *template, const char *uri,
  400. size_t len)
  401. {
  402. #if 0
  403. printf("[HTTP] matching URI \"%.*s\" against template \"%s\"\n",
  404. len, uri, template);
  405. #endif
  406. if (!len-- || *uri++ != '/')
  407. return false;
  408. /* Previous template character (leading '/' implied) */
  409. unsigned char tp = '/';
  410. while (1) {
  411. unsigned char t = *template++;
  412. unsigned char u;
  413. if (!len-- || !(u = *uri++)) {
  414. return !t;
  415. } else if (!t) {
  416. return tp == '/' || u == '/';
  417. } else if (t != u) {
  418. return false;
  419. }
  420. tp = t;
  421. }
  422. }
  423. /* Do not include leading or trailing /; most specific prefix first */
  424. static const httpd_uri_t uri_handlers[] = {
  425. {
  426. .uri = "sys",
  427. .method = HTTP_GET,
  428. .handler = httpd_sys_get_handler,
  429. .user_ctx = NULL
  430. },
  431. {
  432. .uri = "",
  433. .method = HTTP_GET,
  434. .handler = httpd_static_handler,
  435. .user_ctx = NULL
  436. },
  437. {
  438. .uri = "",
  439. .method = HTTP_HEAD,
  440. .handler = httpd_static_handler,
  441. .user_ctx = NULL
  442. },
  443. {
  444. .uri = "sys",
  445. .method = HTTP_POST,
  446. .handler = httpd_sys_post_handler,
  447. .user_ctx = NULL
  448. },
  449. };
  450. void my_httpd_stop(void)
  451. {
  452. if (httpd) {
  453. httpd_stop(httpd);
  454. httpd = NULL;
  455. }
  456. }
  457. void my_httpd_start(void)
  458. {
  459. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  460. httpd_handle_t server;
  461. if (httpd)
  462. return;
  463. config.task_priority = 2;
  464. config.max_open_sockets = 8;
  465. printf("[HTTP] Default stack size: %zu\n", config.stack_size);
  466. config.stack_size <<= 2;
  467. printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
  468. config.uri_match_fn = httpd_uri_match_prefix;
  469. if (httpd_start(&server, &config) != ESP_OK)
  470. return;
  471. httpd = server;
  472. for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++) {
  473. const httpd_uri_t * const handler = &uri_handlers[i];
  474. if (httpd_register_uri_handler(httpd, handler))
  475. printf("[HTTP] failed to register URI handler: %s %s\n",
  476. http_method_str(handler->method), handler->uri);
  477. }
  478. printf("[HTTP] httpd started\n");
  479. }