Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

558 lines
16 KiB

8 years ago
8 years ago
8 years ago
5 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
  1. /*
  2. WEBSERVER MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if WEB_SUPPORT
  6. #include "system.h"
  7. #include "utils.h"
  8. #include "ota.h"
  9. #include <ESPAsyncTCP.h>
  10. #include <ESPAsyncWebServer.h>
  11. #include <Hash.h>
  12. #include <FS.h>
  13. #include <AsyncJson.h>
  14. #include <ArduinoJson.h>
  15. #if WEB_EMBEDDED
  16. #if WEBUI_IMAGE == WEBUI_IMAGE_SMALL
  17. #include "static/index.small.html.gz.h"
  18. #elif WEBUI_IMAGE == WEBUI_IMAGE_LIGHT
  19. #include "static/index.light.html.gz.h"
  20. #elif WEBUI_IMAGE == WEBUI_IMAGE_SENSOR
  21. #include "static/index.sensor.html.gz.h"
  22. #elif WEBUI_IMAGE == WEBUI_IMAGE_RFBRIDGE
  23. #include "static/index.rfbridge.html.gz.h"
  24. #elif WEBUI_IMAGE == WEBUI_IMAGE_RFM69
  25. #include "static/index.rfm69.html.gz.h"
  26. #elif WEBUI_IMAGE == WEBUI_IMAGE_LIGHTFOX
  27. #include "static/index.lightfox.html.gz.h"
  28. #elif WEBUI_IMAGE == WEBUI_IMAGE_THERMOSTAT
  29. #include "static/index.thermostat.html.gz.h"
  30. #elif WEBUI_IMAGE == WEBUI_IMAGE_FULL
  31. #include "static/index.all.html.gz.h"
  32. #endif
  33. #endif // WEB_EMBEDDED
  34. #if WEB_SSL_ENABLED
  35. #include "static/server.cer.h"
  36. #include "static/server.key.h"
  37. #endif // WEB_SSL_ENABLED
  38. // -----------------------------------------------------------------------------
  39. AsyncWebServer * _server;
  40. char _last_modified[50];
  41. std::vector<uint8_t> * _webConfigBuffer;
  42. bool _webConfigSuccess = false;
  43. std::vector<web_request_callback_f> _web_request_callbacks;
  44. std::vector<web_body_callback_f> _web_body_callbacks;
  45. constexpr const size_t WEB_CONFIG_BUFFER_MAX = 4096;
  46. // -----------------------------------------------------------------------------
  47. // HOOKS
  48. // -----------------------------------------------------------------------------
  49. void _onReset(AsyncWebServerRequest *request) {
  50. webLog(request);
  51. if (!webAuthenticate(request)) {
  52. return request->requestAuthentication(getSetting("hostname").c_str());
  53. }
  54. deferredReset(100, CUSTOM_RESET_HTTP);
  55. request->send(200);
  56. }
  57. void _onDiscover(AsyncWebServerRequest *request) {
  58. webLog(request);
  59. const String device = getBoardName();
  60. const String hostname = getSetting("hostname");
  61. StaticJsonBuffer<JSON_OBJECT_SIZE(4)> jsonBuffer;
  62. JsonObject &root = jsonBuffer.createObject();
  63. root["app"] = APP_NAME;
  64. root["version"] = APP_VERSION;
  65. root["device"] = device.c_str();
  66. root["hostname"] = hostname.c_str();
  67. AsyncResponseStream *response = request->beginResponseStream("application/json", root.measureLength() + 1);
  68. root.printTo(*response);
  69. request->send(response);
  70. }
  71. void _onGetConfig(AsyncWebServerRequest *request) {
  72. webLog(request);
  73. if (!webAuthenticate(request)) {
  74. return request->requestAuthentication(getSetting("hostname").c_str());
  75. }
  76. AsyncResponseStream *response = request->beginResponseStream("application/json");
  77. char buffer[100];
  78. snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
  79. response->addHeader("Content-Disposition", buffer);
  80. response->addHeader("X-XSS-Protection", "1; mode=block");
  81. response->addHeader("X-Content-Type-Options", "nosniff");
  82. response->addHeader("X-Frame-Options", "deny");
  83. response->printf("{\n\"app\": \"%s\"", APP_NAME);
  84. response->printf(",\n\"version\": \"%s\"", APP_VERSION);
  85. response->printf(",\n\"backup\": \"1\"");
  86. #if NTP_SUPPORT
  87. response->printf(",\n\"timestamp\": \"%s\"", ntpDateTime().c_str());
  88. #endif
  89. // Write the keys line by line (not sorted)
  90. unsigned long count = settingsKeyCount();
  91. for (unsigned int i=0; i<count; i++) {
  92. String key = settingsKeyName(i);
  93. String value = getSetting(key);
  94. response->printf(",\n\"%s\": \"%s\"", key.c_str(), value.c_str());
  95. }
  96. response->printf("\n}");
  97. request->send(response);
  98. }
  99. void _onPostConfig(AsyncWebServerRequest *request) {
  100. webLog(request);
  101. if (!webAuthenticate(request)) {
  102. return request->requestAuthentication(getSetting("hostname").c_str());
  103. }
  104. request->send(_webConfigSuccess ? 200 : 400);
  105. }
  106. void _onPostConfigFile(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  107. if (!webAuthenticate(request)) {
  108. return request->requestAuthentication(getSetting("hostname").c_str());
  109. }
  110. // No buffer
  111. if (final && (index == 0)) {
  112. _webConfigSuccess = settingsRestoreJson((char*) data);
  113. return;
  114. }
  115. // Buffer start => reset
  116. if (index == 0) if (_webConfigBuffer) delete _webConfigBuffer;
  117. // init buffer if it doesn't exist
  118. if (!_webConfigBuffer) {
  119. _webConfigBuffer = new std::vector<uint8_t>();
  120. _webConfigSuccess = false;
  121. }
  122. // Copy
  123. if (len > 0) {
  124. if ((_webConfigBuffer->size() + len) > std::min(WEB_CONFIG_BUFFER_MAX, getFreeHeap() - sizeof(std::vector<uint8_t>))) {
  125. delete _webConfigBuffer;
  126. _webConfigBuffer = nullptr;
  127. request->send(500);
  128. return;
  129. }
  130. _webConfigBuffer->reserve(_webConfigBuffer->size() + len);
  131. _webConfigBuffer->insert(_webConfigBuffer->end(), data, data + len);
  132. }
  133. // Ending
  134. if (final) {
  135. _webConfigBuffer->push_back(0);
  136. _webConfigSuccess = settingsRestoreJson((char*) _webConfigBuffer->data());
  137. delete _webConfigBuffer;
  138. }
  139. }
  140. #if WEB_EMBEDDED
  141. void _onHome(AsyncWebServerRequest *request) {
  142. webLog(request);
  143. if (!webAuthenticate(request)) {
  144. return request->requestAuthentication(getSetting("hostname").c_str());
  145. }
  146. if (request->header("If-Modified-Since").equals(_last_modified)) {
  147. request->send(304);
  148. } else {
  149. #if WEB_SSL_ENABLED
  150. // Chunked response, we calculate the chunks based on free heap (in multiples of 32)
  151. // This is necessary when a TLS connection is open since it sucks too much memory
  152. DEBUG_MSG_P(PSTR("[MAIN] Free heap: %d bytes\n"), getFreeHeap());
  153. size_t max = (getFreeHeap() / 3) & 0xFFE0;
  154. AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [max](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
  155. // Get the chunk based on the index and maxLen
  156. size_t len = webui_image_len - index;
  157. if (len > maxLen) len = maxLen;
  158. if (len > max) len = max;
  159. if (len > 0) memcpy_P(buffer, webui_image + index, len);
  160. DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / webui_image_len), max);
  161. if (len == 0) DEBUG_MSG_P(PSTR("\n"));
  162. // Return the actual length of the chunk (0 for end of file)
  163. return len;
  164. });
  165. #else
  166. AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", webui_image, webui_image_len);
  167. #endif
  168. response->addHeader("Content-Encoding", "gzip");
  169. response->addHeader("Last-Modified", _last_modified);
  170. response->addHeader("X-XSS-Protection", "1; mode=block");
  171. response->addHeader("X-Content-Type-Options", "nosniff");
  172. response->addHeader("X-Frame-Options", "deny");
  173. request->send(response);
  174. }
  175. }
  176. #endif
  177. #if WEB_SSL_ENABLED
  178. int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
  179. #if WEB_EMBEDDED
  180. if (strcmp(filename, "server.cer") == 0) {
  181. uint8_t * nbuf = (uint8_t*) malloc(server_cer_len);
  182. memcpy_P(nbuf, server_cer, server_cer_len);
  183. *buf = nbuf;
  184. DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
  185. return server_cer_len;
  186. }
  187. if (strcmp(filename, "server.key") == 0) {
  188. uint8_t * nbuf = (uint8_t*) malloc(server_key_len);
  189. memcpy_P(nbuf, server_key, server_key_len);
  190. *buf = nbuf;
  191. DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
  192. return server_key_len;
  193. }
  194. DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename);
  195. *buf = 0;
  196. return 0;
  197. #else
  198. File file = SPIFFS.open(filename, "r");
  199. if (file) {
  200. size_t size = file.size();
  201. uint8_t * nbuf = (uint8_t*) malloc(size);
  202. if (nbuf) {
  203. size = file.read(nbuf, size);
  204. file.close();
  205. *buf = nbuf;
  206. DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
  207. return size;
  208. }
  209. file.close();
  210. }
  211. DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename);
  212. *buf = 0;
  213. return 0;
  214. #endif
  215. }
  216. #endif
  217. void _onUpgradeResponse(AsyncWebServerRequest *request, int code, const String& payload = "") {
  218. auto *response = request->beginResponseStream("text/plain", 256);
  219. response->addHeader("Connection", "close");
  220. response->addHeader("X-XSS-Protection", "1; mode=block");
  221. response->addHeader("X-Content-Type-Options", "nosniff");
  222. response->addHeader("X-Frame-Options", "deny");
  223. response->setCode(code);
  224. if (payload.length()) {
  225. response->printf("%s", payload.c_str());
  226. } else {
  227. if (!Update.hasError()) {
  228. response->print("OK");
  229. } else {
  230. #if defined(ARDUINO_ESP8266_RELEASE_2_3_0)
  231. Update.printError(reinterpret_cast<Stream&>(response));
  232. #else
  233. Update.printError(*response);
  234. #endif
  235. }
  236. }
  237. request->send(response);
  238. }
  239. void _onUpgradeStatusSet(AsyncWebServerRequest *request, int code, const String& payload = "") {
  240. _onUpgradeResponse(request, code, payload);
  241. request->_tempObject = malloc(sizeof(bool));
  242. }
  243. void _onUpgrade(AsyncWebServerRequest *request) {
  244. webLog(request);
  245. if (!webAuthenticate(request)) {
  246. return request->requestAuthentication(getSetting("hostname").c_str());
  247. }
  248. if (request->_tempObject) {
  249. return;
  250. }
  251. _onUpgradeResponse(request, 200);
  252. }
  253. void _onUpgradeFile(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  254. if (!webAuthenticate(request)) {
  255. return request->requestAuthentication(getSetting("hostname").c_str());
  256. }
  257. // We set this after we are done with the request
  258. // It is still possible to re-enter this callback even after connection is already closed
  259. // 1.14.2: TODO: see https://github.com/me-no-dev/ESPAsyncWebServer/pull/660
  260. // remote close or request sending some data before finishing parsing of the body will leak 1460 bytes
  261. // waiting a bit for upstream. fork and point to the fixed version if not resolved before 1.14.2
  262. if (request->_tempObject) {
  263. return;
  264. }
  265. if (!index) {
  266. // TODO: stop network activity completely when handling Update through ArduinoOTA or `ota` command?
  267. if (Update.isRunning()) {
  268. _onUpgradeStatusSet(request, 400, F("ERROR: Upgrade in progress"));
  269. return;
  270. }
  271. // Check that header is correct and there is more data before anything is written to the flash
  272. if (final || !len) {
  273. _onUpgradeStatusSet(request, 400, F("ERROR: Invalid request"));
  274. return;
  275. }
  276. if (!otaVerifyHeader(data, len)) {
  277. _onUpgradeStatusSet(request, 400, F("ERROR: No magic byte / invalid flash config"));
  278. return;
  279. }
  280. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  281. eepromRotate(false);
  282. DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str());
  283. Update.runAsync(true);
  284. // Note: cannot use request->contentLength() for multipart/form-data
  285. if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
  286. _onUpgradeStatusSet(request, 500);
  287. eepromRotate(true);
  288. return;
  289. }
  290. }
  291. if (request->_tempObject) {
  292. return;
  293. }
  294. // Any error will cancel the update, but request may still be alive
  295. if (!Update.isRunning()) {
  296. return;
  297. }
  298. if (Update.write(data, len) != len) {
  299. _onUpgradeStatusSet(request, 500);
  300. Update.end();
  301. eepromRotate(true);
  302. return;
  303. }
  304. if (final) {
  305. otaFinalize(index + len, CUSTOM_RESET_UPGRADE, true);
  306. } else {
  307. otaProgress(index + len);
  308. }
  309. }
  310. bool _onAPModeRequest(AsyncWebServerRequest *request) {
  311. if ((WiFi.getMode() & WIFI_AP) > 0) {
  312. const String domain = getSetting("hostname") + ".";
  313. const String host = request->header("Host");
  314. const String ip = WiFi.softAPIP().toString();
  315. // Only allow requests that use our hostname or ip
  316. if (host.equals(ip)) return true;
  317. if (host.startsWith(domain)) return true;
  318. // Immediatly close the connection, ref: https://github.com/xoseperez/espurna/issues/1660
  319. // Not doing so will cause memory exhaustion, because the connection will linger
  320. request->send(404);
  321. request->client()->close();
  322. return false;
  323. }
  324. return true;
  325. }
  326. void _onRequest(AsyncWebServerRequest *request){
  327. if (!_onAPModeRequest(request)) return;
  328. // Send request to subscribers
  329. for (unsigned char i = 0; i < _web_request_callbacks.size(); i++) {
  330. bool response = (_web_request_callbacks[i])(request);
  331. if (response) return;
  332. }
  333. // No subscriber handled the request, return a 404 with implicit "Connection: close"
  334. request->send(404);
  335. // And immediatly close the connection, ref: https://github.com/xoseperez/espurna/issues/1660
  336. // Not doing so will cause memory exhaustion, because the connection will linger
  337. request->client()->close();
  338. }
  339. void _onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
  340. if (!_onAPModeRequest(request)) return;
  341. // Send request to subscribers
  342. for (unsigned char i = 0; i < _web_body_callbacks.size(); i++) {
  343. bool response = (_web_body_callbacks[i])(request, data, len, index, total);
  344. if (response) return;
  345. }
  346. // Same as _onAPModeRequest(...)
  347. request->send(404);
  348. request->client()->close();
  349. }
  350. // -----------------------------------------------------------------------------
  351. bool webAuthenticate(AsyncWebServerRequest *request) {
  352. #if USE_PASSWORD
  353. return request->authenticate(WEB_USERNAME, getAdminPass().c_str());
  354. #else
  355. return true;
  356. #endif
  357. }
  358. // -----------------------------------------------------------------------------
  359. AsyncWebServer * webServer() {
  360. return _server;
  361. }
  362. void webBodyRegister(web_body_callback_f callback) {
  363. _web_body_callbacks.push_back(callback);
  364. }
  365. void webRequestRegister(web_request_callback_f callback) {
  366. _web_request_callbacks.push_back(callback);
  367. }
  368. unsigned int webPort() {
  369. #if WEB_SSL_ENABLED
  370. return 443;
  371. #else
  372. return getSetting("webPort", WEB_PORT).toInt();
  373. #endif
  374. }
  375. void webLog(AsyncWebServerRequest *request) {
  376. DEBUG_MSG_P(PSTR("[WEBSERVER] Request: %s %s\n"), request->methodToString(), request->url().c_str());
  377. }
  378. void webSetup() {
  379. // Cache the Last-Modifier header value
  380. snprintf_P(_last_modified, sizeof(_last_modified), PSTR("%s %s GMT"), __DATE__, __TIME__);
  381. // Create server
  382. unsigned int port = webPort();
  383. _server = new AsyncWebServer(port);
  384. // Rewrites
  385. _server->rewrite("/", "/index.html");
  386. // Serve home (basic authentication protection)
  387. #if WEB_EMBEDDED
  388. _server->on("/index.html", HTTP_GET, _onHome);
  389. #endif
  390. // Other entry points
  391. _server->on("/reset", HTTP_GET, _onReset);
  392. _server->on("/config", HTTP_GET, _onGetConfig);
  393. _server->on("/config", HTTP_POST | HTTP_PUT, _onPostConfig, _onPostConfigFile);
  394. _server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeFile);
  395. _server->on("/discover", HTTP_GET, _onDiscover);
  396. // Serve static files
  397. #if SPIFFS_SUPPORT
  398. _server->serveStatic("/", SPIFFS, "/")
  399. .setLastModified(_last_modified)
  400. .setFilter([](AsyncWebServerRequest *request) -> bool {
  401. webLog(request);
  402. return true;
  403. });
  404. #endif
  405. // Handle other requests, including 404
  406. _server->onRequestBody(_onBody);
  407. _server->onNotFound(_onRequest);
  408. // Run server
  409. #if WEB_SSL_ENABLED
  410. _server->onSslFileRequest(_onCertificate, NULL);
  411. _server->beginSecure("server.cer", "server.key", NULL);
  412. #else
  413. _server->begin();
  414. #endif
  415. DEBUG_MSG_P(PSTR("[WEBSERVER] Webserver running on port %u\n"), port);
  416. }
  417. #endif // WEB_SUPPORT