httpd.c 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #define MODULE "httpd"
  2. #include "common.h"
  3. #include "fw.h"
  4. #include "httpd.h"
  5. #include <incbin.h>
  6. #include <unzipLIB.h>
  7. static httpd_handle_t httpd;
  8. esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
  9. {
  10. char *response;
  11. esp_err_t err;
  12. int rv, len;
  13. printf("[POST] len = %zu uri = \"%s\"\n",
  14. req->content_len, req->uri);
  15. if (!req->content_len) {
  16. return httpd_resp_send_err(req, 411, "Length required");
  17. }
  18. rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
  19. if (rv == FWUPDATE_ERR_IN_PROGRESS)
  20. return httpd_resp_send_err(req, 409, "Firmware update already in progress");
  21. else if (rv)
  22. return httpd_resp_send_err(req, 500, "Firmware update failed");
  23. len = asprintf(&response,
  24. "<!DOCTYPE html>\r\n"
  25. "<html>\r\n"
  26. "<head>\r\n"
  27. "<title>Firmware update completed</title>\r\n"
  28. "</head>\r\n"
  29. "<body>\r\n"
  30. "<h1>Firmware update completed</h1>\r\n"
  31. "<p>Rebooting in %u seconds</p>\r\n"
  32. "</body>\r\n"
  33. "</html>\r\n",
  34. reboot_delayed());
  35. /* 200 and text/html are the response defaults, no need to set */
  36. httpd_resp_set_hdr(req, "Connection", "close");
  37. err = httpd_resp_send(req, response, len);
  38. free(response);
  39. return err;
  40. }
  41. INCBIN(wwwzip, "data/www.zip");
  42. struct mime_type {
  43. const char *ext;
  44. uint16_t ext_len;
  45. uint16_t flags;
  46. const char *mime;
  47. };
  48. #define MT_CHARSET 1 /* Add charset to Content-Type */
  49. static const struct mime_type mime_types[] = {
  50. { ".html", 5, MT_CHARSET, "text/html" },
  51. { ".xhtml", 6, MT_CHARSET, "text/html" },
  52. { ".css", 4, MT_CHARSET, "text/css" },
  53. { ".webp", 5, 0, "image/webp" },
  54. { ".jpg", 4, 0, "image/jpeg" },
  55. { ".png", 4, 0, "image/png" },
  56. { ".ico", 4, 0, "image/png" }, /* favicon.ico */
  57. { ".svg", 4, MT_CHARSET, "image/svg+xml" },
  58. { ".pdf", 4, 0, "application/pdf" },
  59. { ".js", 3, MT_CHARSET, "text/javascript" },
  60. { ".mjs", 4, MT_CHARSET, "text/javascript" },
  61. { ".json", 5, MT_CHARSET, "application/json" },
  62. { ".xml", 4, MT_CHARSET, "text/xml" },
  63. { ".bin", 4, 0, "application/octet-stream" },
  64. { ".fw", 3, 0, "application/octet-stream" },
  65. { NULL, 0, MT_CHARSET, "text/plain" } /* default */
  66. };
  67. static esp_err_t httpd_static_handler(httpd_req_t *req)
  68. {
  69. static const char index_filename[] = "index.html";
  70. const size_t buffer_size = UNZ_BUFSIZE;
  71. const char *uri, *enduri;
  72. bool add_index;
  73. char *buffer = NULL;
  74. ZIPFILE *zip = NULL;
  75. unzFile unz = NULL;
  76. bool file_open = false;
  77. int err = 0;
  78. size_t len;
  79. uri = req->uri;
  80. while (*uri == '/')
  81. uri++; /* Skip leading slashes */
  82. enduri = strchr(uri, '\0');
  83. if (enduri == uri) {
  84. add_index = true;
  85. } else if (enduri[-1] == '/') {
  86. add_index = true;
  87. enduri--; /* Drop terminal slash */
  88. } else {
  89. add_index = false; /* Try the plain filename first */
  90. }
  91. MSG("requesting: /%.*s\n", enduri - uri, uri);
  92. buffer = malloc(buffer_size);
  93. zip = malloc(sizeof *zip);
  94. if (!buffer || !zip) {
  95. httpd_resp_send_err(req, 503, "Out of memory");
  96. goto out;
  97. }
  98. if (enduri - uri + 1 + sizeof index_filename >= buffer_size) {
  99. err = httpd_resp_send_err(req, 414, "URI too long");
  100. goto out;
  101. }
  102. char *p = mempcpy(buffer, uri, enduri - uri);
  103. *p = '\0';
  104. unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
  105. zip, NULL, NULL, NULL, NULL);
  106. if (!unz) {
  107. MSG("[HTTP] unzOpen failed!\n");
  108. err = httpd_resp_send_err(req, 500, "Cannot open content archive");
  109. goto out;
  110. }
  111. while (1) {
  112. if (add_index) {
  113. if (p > buffer)
  114. *p++ = '/';
  115. p = mempcpy(p, index_filename, sizeof index_filename);
  116. }
  117. MSG("trying to open: %s\n", buffer);
  118. if (unzLocateFile(unz, buffer, 1) == UNZ_OK)
  119. break;
  120. if (add_index) {
  121. err = httpd_resp_send_404(req);
  122. goto out;
  123. }
  124. add_index = true; /* Try again with the index filename */
  125. }
  126. /* Note: p points to the end of the filename string */
  127. size_t filelen = p - buffer;
  128. const struct mime_type *mime_type = mime_types;
  129. /* The default entry with length 0 will always match */
  130. while (mime_type->ext_len) {
  131. len = mime_type->ext_len;
  132. if (len < filelen && !memcmp(p - len, mime_type->ext, len))
  133. break;
  134. mime_type++;
  135. }
  136. unz_file_info fileinfo;
  137. memset(&fileinfo, 0, sizeof fileinfo);
  138. unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
  139. /*
  140. * This is kind of brain dead, but it seems like the only sane
  141. * way to not have to build the whole response in memory even
  142. * though the length is known a priori.
  143. */
  144. len = snprintf(buffer, buffer_size,
  145. "HTTP/1.1 200 OK\r\n"
  146. "Content-Type: %s%s\r\n"
  147. "Content-Length: %u\r\n"
  148. "Allow: GET, HEAD\r\n"
  149. "Etag: \"%08x:%08x\"\r\n"
  150. "Connection: close\r\n"
  151. "\r\n",
  152. mime_type->mime,
  153. mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "",
  154. fileinfo.uncompressed_size,
  155. /*
  156. * Hopefully the combination of date and CRC
  157. * is strong enough to quality for a "strong" ETag
  158. */
  159. fileinfo.dosDate, fileinfo.crc);
  160. if (len >= buffer_size) {
  161. err = httpd_resp_send_err(req, 500, "buffer_size too small");
  162. goto out;
  163. }
  164. err = httpd_send(req, buffer, len);
  165. if (err != len || req->method == HTTP_HEAD ||
  166. !fileinfo.uncompressed_size)
  167. goto out;
  168. if (unzOpenCurrentFile(unz) != UNZ_OK) {
  169. err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
  170. goto out;
  171. }
  172. file_open = true;
  173. len = fileinfo.uncompressed_size;
  174. while (len) {
  175. size_t chunk = len;
  176. if (chunk > buffer_size)
  177. chunk = buffer_size;
  178. if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
  179. err = ESP_ERR_HTTPD_RESULT_TRUNC;
  180. goto out;
  181. }
  182. err = httpd_send(req, buffer, chunk);
  183. if (err != chunk)
  184. goto out;
  185. len -= chunk;
  186. }
  187. err = ESP_OK; /* All good! */
  188. out:
  189. if (file_open)
  190. unzCloseCurrentFile(unz);
  191. if (unz)
  192. unzClose(unz);
  193. if (zip)
  194. free(zip);
  195. if (buffer)
  196. free(buffer);
  197. return err;
  198. }
  199. static const httpd_uri_t uri_handlers[] = {
  200. {
  201. .uri = "/*",
  202. .method = HTTP_GET,
  203. .handler = httpd_static_handler,
  204. .user_ctx = NULL
  205. },
  206. {
  207. .uri = "/*",
  208. .method = HTTP_HEAD,
  209. .handler = httpd_static_handler,
  210. .user_ctx = NULL
  211. },
  212. {
  213. .uri = "/fwupdate/?",
  214. .method = HTTP_POST,
  215. .handler = httpd_firmware_upgrade_handler,
  216. .user_ctx = NULL
  217. }
  218. };
  219. void my_httpd_stop(void)
  220. {
  221. if (httpd) {
  222. httpd_stop(httpd);
  223. httpd = NULL;
  224. }
  225. }
  226. void my_httpd_start(void)
  227. {
  228. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  229. httpd_handle_t server;
  230. if (httpd)
  231. return;
  232. config.task_priority = 2;
  233. printf("[HTTP] Default stack size: %zu\n", config.stack_size);
  234. config.stack_size <<= 2;
  235. printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
  236. config.uri_match_fn = httpd_uri_match_wildcard;
  237. if (httpd_start(&server, &config) != ESP_OK)
  238. return;
  239. httpd = server;
  240. for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++)
  241. httpd_register_uri_handler(httpd, &uri_handlers[i]);
  242. printf("[HTTP] httpd started\n");
  243. }