Merge PR #13 with gzip-only WebConfig assets
Keep the graceful final-ACK TCP close and optional FEM control guards from pixitha's browser compatibility fixes. Replace Brotli negotiation and duplicate page assets with one gzip representation compressed by Google Zopfli 0.4.3 using 1,000 iterations. Compress the bootstrap, captive success, compact OTA, and static WiFi setup pages with the same validated build cache. Honor gzip quality values and wildcard precedence. Validate compressed OTA page evidence in the firmware capability gate, and add native and Python regressions plus CI coverage. Remove the unused Brotli build dependency. Validation: 54 native tests, 56 Python/browser checks, full V4.3 build and verified application flash. Actual V4 WiFi responses matched the generated gzip assets; 30 repeat downloads completed without reboot. Chromium loaded the setup and settings pages without script or transfer errors. Physical Android testing is pending because the Pi detects no phone over USB or mDNS.
This commit is contained in:
commit
02b8935794
23 files changed
+2674
-2084
No files matched your search
@@ -58,6 +58,7 @@ bool ESP32Board::isUserGpioAvailable(uint8_t pin) const {
|
||||
#if defined(LIGHTWEIGHT_WIFI_OTA) && \
|
||||
(defined(ADMIN_PASSWORD) || defined(COMPANION_RADIO_FULL))
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/StaticHtml.h>
|
||||
#include <Update.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <SPIFFS.h>
|
||||
@@ -75,18 +76,6 @@ static constexpr uint16_t LIGHTWEIGHT_OTA_PORT = 8080;
|
||||
static constexpr uint16_t LIGHTWEIGHT_OTA_PORT = 80;
|
||||
#endif
|
||||
|
||||
static const char LIGHTWEIGHT_OTA_PAGE[] = R"HTML(<!doctype html>
|
||||
<html><head><meta name="viewport" content="width=device-width"><title>MeshCore OTA</title>
|
||||
<style>body{font-family:sans-serif;max-width:32rem;margin:3rem auto;padding:0 1rem}button,input{font-size:1rem;margin:.5rem 0}pre{white-space:pre-wrap}</style></head>
|
||||
<body><h2>MeshCore firmware update</h2><input id="file" type="file" accept=".bin,application/octet-stream"><br>
|
||||
<button id="upload">Upload and reboot</button><pre id="status"></pre><script>
|
||||
const f=document.getElementById('file'),s=document.getElementById('status'),b=document.getElementById('upload');
|
||||
b.onclick=()=>{if(!f.files.length){s.textContent='Select a firmware .bin file.';return}b.disabled=true;
|
||||
const x=new XMLHttpRequest();x.open('POST','/update');x.setRequestHeader('Content-Type','application/octet-stream');
|
||||
x.upload.onprogress=e=>{if(e.lengthComputable)s.textContent='Uploading '+Math.round(e.loaded*100/e.total)+'%'};
|
||||
x.onload=()=>{s.textContent=x.responseText;b.disabled=false};x.onerror=()=>{s.textContent='Upload failed';b.disabled=false};x.send(f.files[0])};
|
||||
</script></body></html>)HTML";
|
||||
|
||||
class LightweightOTAServer {
|
||||
WiFiServer server{LIGHTWEIGHT_OTA_PORT};
|
||||
TaskHandle_t task = nullptr;
|
||||
@@ -122,8 +111,11 @@ class LightweightOTAServer {
|
||||
}
|
||||
|
||||
void sendPage(WiFiClient& client) {
|
||||
sendResponse(client, 200, "OK", "text/html", LIGHTWEIGHT_OTA_PAGE,
|
||||
strlen(LIGHTWEIGHT_OTA_PAGE));
|
||||
client.printf("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n"
|
||||
"Content-Encoding: gzip\r\nContent-Length: %u\r\n"
|
||||
"Connection: close\r\nCache-Control: no-store\r\n\r\n",
|
||||
static_cast<unsigned>(MESH_HTML_LIGHTWEIGHT_OTA_LEN));
|
||||
client.write(MESH_HTML_LIGHTWEIGHT_OTA, MESH_HTML_LIGHTWEIGHT_OTA_LEN);
|
||||
}
|
||||
|
||||
void sendLog(WiFiClient& client) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace http {
|
||||
|
||||
inline bool encodingTokenEquals(const char* first, const char* last, const char* token) {
|
||||
while (first != last && *token) {
|
||||
char c = *first++;
|
||||
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
|
||||
if (c != *token++) return false;
|
||||
}
|
||||
return first == last && *token == 0;
|
||||
}
|
||||
|
||||
inline void trimEncodingToken(const char*& first, const char*& last) {
|
||||
while (first != last && (*first == ' ' || *first == '\t')) ++first;
|
||||
while (first != last && (last[-1] == ' ' || last[-1] == '\t')) --last;
|
||||
}
|
||||
|
||||
// A missing header permits any coding (RFC 9110). An empty header requests
|
||||
// identity only. An explicit gzip preference takes precedence over '*'.
|
||||
inline bool acceptsGzip(const char* header) {
|
||||
if (!header) return true;
|
||||
bool wildcard = false;
|
||||
bool explicit_gzip = false;
|
||||
bool gzip_allowed = false;
|
||||
const char* item = header;
|
||||
while (*item) {
|
||||
const char* end = strchr(item, ',');
|
||||
if (!end) end = item + strlen(item);
|
||||
const char* semi = static_cast<const char*>(memchr(item, ';', end - item));
|
||||
const char* first = item;
|
||||
const char* last = semi ? semi : end;
|
||||
trimEncodingToken(first, last);
|
||||
const bool gzip = encodingTokenEquals(first, last, "gzip");
|
||||
const bool star = encodingTokenEquals(first, last, "*");
|
||||
bool allowed = true;
|
||||
for (const char* param = semi; param && param != end;) {
|
||||
first = param + 1;
|
||||
param = static_cast<const char*>(memchr(first, ';', end - first));
|
||||
last = param ? param : end;
|
||||
trimEncodingToken(first, last);
|
||||
const char* equal = static_cast<const char*>(memchr(first, '=', last - first));
|
||||
if (!equal) continue;
|
||||
const char* key_end = equal;
|
||||
trimEncodingToken(first, key_end);
|
||||
if (!encodingTokenEquals(first, key_end, "q")) continue;
|
||||
first = equal + 1;
|
||||
trimEncodingToken(first, last);
|
||||
// qvalue is 0 or 1 with at most three fractional digits; do not let
|
||||
// malformed parameters accidentally enable a compressed response.
|
||||
allowed = false;
|
||||
if (first == last || (*first != '0' && *first != '1')) continue;
|
||||
const bool one = *first++ == '1';
|
||||
bool nonzero = one;
|
||||
bool valid = true;
|
||||
if (first != last) {
|
||||
if (*first++ != '.' || last - first > 3) valid = false;
|
||||
while (first != last) {
|
||||
if (*first < '0' || *first > '9' || (one && *first != '0')) valid = false;
|
||||
if (*first != '0') nonzero = true;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
allowed = valid && nonzero;
|
||||
}
|
||||
if (gzip) { explicit_gzip = true; gzip_allowed = allowed; }
|
||||
if (star) wildcard = allowed;
|
||||
item = *end ? end + 1 : end;
|
||||
}
|
||||
return explicit_gzip ? gzip_allowed : wildcard;
|
||||
}
|
||||
|
||||
} // namespace http
|
||||
} // namespace mesh
|
||||
@@ -3,6 +3,7 @@
|
||||
#if defined(ESP32_PLATFORM) && defined(WIFI_SSID)
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/StaticHtml.h>
|
||||
#include <DNSServer.h>
|
||||
#include <Preferences.h>
|
||||
#include <esp_wifi.h>
|
||||
@@ -24,19 +25,6 @@ static const uint32_t CONNECT_TIMEOUT_MS = 20000;
|
||||
// disappears; allow one second for the HTTP response to flush first.
|
||||
static const uint32_t AP_CLOSE_DELAY_MS = 1000;
|
||||
|
||||
static const char SETUP_PAGE[] = R"HTML(<!doctype html>
|
||||
<html><head><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>MeshCore WiFi setup</title><style>
|
||||
body{font-family:sans-serif;max-width:28rem;margin:2rem auto;padding:0 1rem;color:#222}
|
||||
label{display:block;margin-top:1rem}input,button{box-sizing:border-box;width:100%;font-size:1rem;padding:.7rem;margin-top:.35rem}
|
||||
button{margin-top:1.2rem}small{color:#555}</style></head><body>
|
||||
<h2>MeshCore WiFi setup</h2><p>Enter the WiFi network this node should join.</p>
|
||||
<form method="post" action="/save">
|
||||
<label>SSID<input name="ssid" maxlength="31" required autocomplete="off"></label>
|
||||
<label>Password<input name="password" type="password" maxlength="64" autocomplete="off"></label>
|
||||
<small>Leave blank for an open network. A 64-character key must be hexadecimal.</small>
|
||||
<button type="submit">Save and connect</button></form></body></html>)HTML";
|
||||
|
||||
struct PortalImpl {
|
||||
WiFiServer server{80};
|
||||
DNSServer dns;
|
||||
@@ -257,7 +245,11 @@ static void handleClient(PortalImpl* impl, WiFiClient& client) {
|
||||
} else if (get_request) {
|
||||
// Serve the form for every GET path. Together with wildcard DNS this makes
|
||||
// common captive-portal probes land on the setup page.
|
||||
sendResponse(client, 200, "OK", "text/html", SETUP_PAGE);
|
||||
client.printf("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n"
|
||||
"Content-Encoding: gzip\r\nContent-Length: %u\r\n"
|
||||
"Connection: close\r\nCache-Control: no-store\r\n\r\n",
|
||||
static_cast<unsigned>(MESH_HTML_WIFI_SETUP_LEN));
|
||||
client.write(MESH_HTML_WIFI_SETUP, MESH_HTML_WIFI_SETUP_LEN);
|
||||
} else {
|
||||
sendResponse(client, 404, "Not Found", "text/plain", "Not found.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Auto-generated by scripts/generate_webconfig_html.py.
|
||||
// Google Zopfli 0.4.3 (gzip, 1000 iterations).
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <pgmspace.h>
|
||||
const uint32_t MESH_HTML_LIGHTWEIGHT_OTA_LEN = 619;
|
||||
const uint8_t MESH_HTML_LIGHTWEIGHT_OTA[] PROGMEM = {
|
||||
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x74,0x52,0xc5,0x9a,0xdb,0x30,
|
||||
0x10,0xbe,0xe7,0x29,0xb4,0xe8,0x64,0xc1,0x5e,0x26,0x43,0x99,0x99,0x7b,0x95,0xad,
|
||||
0x71,0xac,0xaf,0xb6,0xa4,0x4a,0xe3,0xc0,0xc2,0xbb,0x77,0x14,0xa5,0xdc,0x5e,0x44,
|
||||
0x43,0x3f,0x28,0x5b,0x11,0xba,0xc2,0xb9,0x01,0xd6,0x60,0xd7,0x16,0x83,0x6c,0xb1,
|
||||
0x65,0x0d,0x70,0x51,0x64,0x1d,0x20,0x67,0x8a,0x77,0x90,0xaf,0x4e,0x24,0x4c,0x8d,
|
||||
0xb6,0xb8,0xca,0x2a,0xad,0x10,0x14,0xe6,0xab,0x53,0x29,0xb0,0xc9,0x05,0x4c,0x64,
|
||||
0x05,0xbb,0x8b,0xcb,0x6a,0x91,0xa1,0xc4,0x16,0x8a,0x17,0xe0,0x9a,0x7b,0xda,0x02,
|
||||
0x7b,0xf5,0xfe,0x4e,0x96,0x84,0xb7,0x41,0xe6,0x70,0x4e,0x7b,0xa9,0xc5,0xfc,0xaa,
|
||||
0xa6,0x26,0xbb,0x35,0xef,0x64,0x3b,0xbf,0x70,0x5c,0xb9,0x5d,0x07,0x56,0xd6,0x69,
|
||||
0xc7,0x67,0xa1,0xd3,0xc5,0xe1,0x81,0x85,0x8e,0xee,0x76,0x2c,0xd5,0xc5,0x21,0x9d,
|
||||
0x19,0xef,0x51,0xa7,0x86,0x0b,0x21,0xd5,0xf8,0x62,0x8f,0xed,0xd3,0xdb,0x4d,0xd9,
|
||||
0x23,0x6a,0xb5,0x23,0x95,0xe9,0x31,0xf4,0x74,0xf2,0x12,0x2e,0xf6,0x7f,0xa9,0x8d,
|
||||
0x8f,0x7d,0xf1,0xde,0x8d,0xb1,0x70,0x35,0x6d,0x24,0xc2,0xae,0x33,0xbc,0x82,0x0b,
|
||||
0x63,0x09,0xb4,0xe5,0xe6,0x26,0x4b,0x02,0xae,0x2c,0xf1,0xa4,0x09,0xa6,0x07,0x48,
|
||||
0x0a,0x1c,0xfc,0x64,0x51,0x4b,0xdb,0x4d,0x39,0x1d,0x7a,0x23,0x38,0x42,0x96,0x50,
|
||||
0x30,0x5b,0x0c,0x65,0x52,0xe4,0xab,0xb5,0x6c,0x61,0x95,0x79,0x11,0xbf,0x9f,0x79,
|
||||
0x55,0x81,0x21,0x89,0xe2,0x52,0xaa,0x1d,0x6e,0x4c,0x2b,0x2b,0x8e,0x52,0xab,0x84,
|
||||
0xb4,0x06,0x02,0x89,0x16,0x78,0x47,0x6a,0x95,0x96,0xe6,0x05,0x12,0x8b,0x4e,0xbd,
|
||||
0x69,0x35,0x17,0xab,0xc5,0x87,0xc5,0xce,0xb8,0x12,0xcc,0x42,0xa9,0x35,0x66,0x49,
|
||||
0xc8,0x2a,0x32,0xc2,0xbd,0x48,0x75,0xc8,0xb1,0x77,0xd4,0x23,0x31,0x96,0xc0,0xbb,
|
||||
0xca,0x4a,0x83,0xc5,0x80,0xdc,0x71,0xc8,0xea,0x9c,0x5c,0xed,0x3b,0x50,0x18,0x8f,
|
||||
0x01,0x1f,0xb4,0xe0,0x8f,0x77,0xe7,0x4f,0xc4,0x30,0xf2,0x00,0xa3,0xd1,0x8e,0xfb,
|
||||
0x7f,0x46,0xe8,0x4c,0x39,0xe5,0xff,0x73,0x02,0xd0,0x68,0x94,0x0e,0xca,0x58,0xab,
|
||||
0x8a,0xf8,0x7d,0xc9,0x87,0xa3,0xbc,0xb8,0x92,0xf5,0x70,0xa5,0x8e,0xfd,0x10,0x17,
|
||||
0xb7,0xa0,0xc6,0xd8,0x8c,0xae,0x5c,0x8c,0x30,0xc3,0x7b,0xcb,0x7f,0x13,0xbd,0x83,
|
||||
0x16,0x2a,0x64,0xfc,0xa7,0xac,0x5e,0x26,0xe6,0x6b,0xe2,0x28,0xb5,0x80,0xbd,0x55,
|
||||
0x37,0x65,0x2c,0xa4,0xe3,0x65,0x0b,0x22,0x47,0xdb,0x43,0xba,0x24,0x36,0xcb,0x15,
|
||||
0x4c,0xd9,0xe7,0x17,0xcf,0x1f,0x23,0x9a,0xb7,0xf0,0xb5,0x07,0x87,0xc3,0x51,0x3a,
|
||||
0x8b,0xb5,0x01,0x35,0x8c,0x5e,0xbf,0x7a,0xf7,0x3e,0xda,0x89,0x92,0xe0,0x53,0xe4,
|
||||
0x03,0x0e,0x70,0x99,0xf7,0x18,0xb8,0x00,0x3b,0x8c,0x96,0x40,0x76,0xdf,0x93,0x61,
|
||||
0x94,0xfc,0x3f,0x7b,0x3c,0xb9,0x59,0x1c,0x88,0x12,0x47,0x63,0xf5,0xd8,0x82,0x73,
|
||||
0x39,0x04,0x96,0xb0,0xa4,0x77,0x4f,0x77,0xa6,0x47,0x8f,0x74,0xf4,0x07,0xcf,0x60,
|
||||
0x22,0xfd,0x56,0x16,0x6d,0xbf,0xe0,0xd8,0xc4,0x56,0xf7,0x4a,0xf8,0x42,0x7a,0x06,
|
||||
0xb1,0xb5,0xbf,0xb7,0x97,0x40,0x8c,0x1a,0x79,0x3b,0xda,0x8e,0x36,0xa2,0x1b,0x3f,
|
||||
0x4f,0x2b,0x1f,0x0d,0x5a,0xfe,0xde,0x6f,0x16,0xd3,0x78,0x43,0x2a,0xc0,0x7b,0x7a,
|
||||
0x4d,0x7f,0x11,0xa8,0xe6,0xad,0x83,0x9b,0xd4,0x17,0x83,0xb5,0xda,0x86,0xea,0x7f,
|
||||
0xa2,0x61,0x35,0x97,0x54,0x12,0xfd,0xb3,0xdc,0x01,0xc1,0x5b,0x9a,0xf7,0x6d,0xd1,
|
||||
0x06,0xb1,0x9a,0x40,0x07,0xd9,0xe8,0x43,0x13,0x96,0x8d,0x3e,0x24,0x5b,0xe8,0x83,
|
||||
0xcb,0x07,0x00,0x75,0x0d,0xa9,0x2f,0x36,0x04,0x00,0x00,
|
||||
};
|
||||
const uint32_t MESH_HTML_WIFI_SETUP_LEN = 517;
|
||||
const uint8_t MESH_HTML_WIFI_SETUP[] PROGMEM = {
|
||||
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x7d,0x53,0xc5,0x96,0xe3,0x3a,
|
||||
0x10,0xdd,0xe7,0x2b,0xea,0xe9,0x9d,0xd9,0x75,0xb0,0xd9,0x71,0x3c,0xcc,0xcc,0x6b,
|
||||
0xd9,0xaa,0xb4,0x35,0x91,0x25,0x8d,0x54,0x0e,0x9e,0xfe,0xf7,0x29,0xc3,0x30,0x6c,
|
||||
0x2c,0xbe,0x98,0xa4,0xff,0x29,0x57,0xd0,0xce,0x23,0x94,0x54,0x99,0x6c,0x90,0xb6,
|
||||
0x43,0x5a,0xa2,0x54,0x59,0x5a,0x21,0x49,0xb0,0xb2,0xc2,0x85,0x58,0x6b,0xdc,0x78,
|
||||
0x17,0x48,0x40,0xe1,0x2c,0xa1,0xa5,0x85,0xd8,0x68,0x45,0xe5,0x42,0xe1,0x5a,0x17,
|
||||
0x38,0x6c,0x17,0x47,0xda,0x6a,0xd2,0xd2,0x0c,0x63,0x21,0x0d,0x2e,0xa6,0x82,0xf1,
|
||||
0x48,0x93,0xc1,0xec,0x39,0xc6,0xf2,0xae,0x0b,0x08,0x1f,0xf5,0x03,0x0d,0x11,0xa9,
|
||||
0xf6,0xe9,0xb8,0x3b,0x4a,0x23,0xed,0x78,0x18,0xe4,0x4e,0xed,0x0e,0x4b,0x06,0x1f,
|
||||
0x2e,0x65,0xa5,0xcd,0x2e,0x89,0xd2,0xc6,0x61,0xc4,0xa0,0x97,0xf3,0x4a,0x6e,0x3b,
|
||||
0x86,0x64,0x76,0x11,0xb0,0xe2,0x75,0xb8,0xd2,0x36,0x99,0xf1,0x1c,0x64,0x4d,0x6e,
|
||||
0xee,0xa5,0x52,0xda,0x5e,0x25,0x13,0x98,0x36,0xe7,0x85,0x33,0x2e,0x24,0xff,0xcf,
|
||||
0x66,0xb3,0xeb,0x81,0x91,0x39,0x9a,0x83,0xd2,0xd1,0x1b,0xb9,0x4b,0x72,0xe3,0x8a,
|
||||
0x55,0xff,0x7e,0x48,0xce,0x27,0xcd,0xfd,0x6b,0x6d,0x7d,0x4d,0x47,0x79,0x4d,0xe4,
|
||||
0xec,0x21,0x77,0xdb,0x61,0xd4,0xfb,0x06,0x2e,0x77,0x41,0x61,0x18,0xf2,0xce,0xbc,
|
||||
0xa3,0x9f,0x4e,0x26,0x37,0xe6,0xad,0x48,0xbe,0x81,0xed,0xe3,0x6f,0xdc,0xa3,0x73,
|
||||
0x5e,0xfd,0x08,0x3d,0x3a,0x3e,0x6d,0xc0,0x07,0x3d,0xee,0x8f,0xa4,0xa3,0x59,0x73,
|
||||
0x12,0x2b,0x69,0xcc,0xa1,0x17,0x7b,0x7a,0x7a,0x7a,0x9d,0x8e,0xbb,0x30,0xd2,0x71,
|
||||
0x57,0x40,0x93,0x49,0xd3,0xc9,0xec,0xcf,0x01,0xf2,0x7e,0xea,0xb3,0xfb,0x96,0x30,
|
||||
0x00,0x95,0xfd,0x99,0x45,0xda,0xb8,0xb0,0xe2,0x0d,0x1d,0xc1,0x3a,0x85,0x10,0x4b,
|
||||
0x57,0x1b,0x05,0x9f,0x9c,0xb6,0xa3,0x74,0xec,0x19,0x70,0xe9,0x42,0x05,0xdc,0x6e,
|
||||
0xe9,0xd4,0x42,0x78,0x17,0xb9,0x56,0x59,0x90,0x76,0x76,0x21,0xc6,0x51,0xae,0xb1,
|
||||
0x29,0xae,0xcd,0x2d,0x7b,0xfb,0xf6,0xf1,0xbd,0xb4,0x8d,0xa7,0xff,0x21,0xc4,0xa8,
|
||||
0x95,0x00,0x2e,0xc4,0xa0,0xbd,0xe2,0xfe,0xc5,0xf1,0x54,0x40,0xc0,0xcf,0xb5,0x0e,
|
||||
0xa8,0xda,0x32,0x0a,0x57,0x79,0x83,0xc4,0x77,0xdd,0x72,0x29,0xd8,0x4b,0x87,0xf4,
|
||||
0x15,0xf1,0x95,0x8c,0x71,0xc3,0xb9,0xfe,0x84,0xea,0xfb,0x4d,0x01,0xb4,0xf3,0x3f,
|
||||
0xae,0x7f,0x64,0x3a,0x3b,0x11,0xff,0x26,0x68,0xf3,0xcc,0x9e,0x21,0x3b,0x80,0xdc,
|
||||
0x48,0xbb,0x02,0x36,0x0a,0xd2,0x82,0xf3,0x68,0xbf,0x06,0x33,0x82,0xdb,0x70,0x76,
|
||||
0x32,0x2c,0x4a,0x19,0xd8,0x33,0x06,0x58,0xe1,0x0e,0xaa,0x3a,0x12,0xe4,0x08,0x25,
|
||||
0x6e,0xa5,0xc2,0x42,0x33,0x10,0x47,0xd5,0xe2,0x31,0x6e,0x57,0x60,0x2f,0x2d,0xd6,
|
||||
0x79,0xa5,0x49,0x64,0x6f,0x99,0x85,0xb1,0x15,0x14,0xce,0x5a,0x2c,0x28,0x1d,0x77,
|
||||
0xd7,0x58,0x4f,0x13,0x2f,0x0f,0x4d,0x7b,0x3c,0xb4,0xff,0xa8,0x2f,0x86,0x62,0xfc,
|
||||
0x1a,0x68,0x03,0x00,0x00,
|
||||
};
|
||||
+2171
-1965
File diff suppressed because it is too large.
Load diff
@@ -31,6 +31,7 @@ static_assert(sizeof(WEBCONFIG_AP_PREFIX) <= 28,
|
||||
#endif
|
||||
|
||||
#include "WebConfigHtml.h"
|
||||
#include <helpers/HttpContentEncoding.h>
|
||||
#include "WebTerminalStream.h"
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <new>
|
||||
@@ -154,15 +155,11 @@ public:
|
||||
if (_in_flight != 0) return 0;
|
||||
if (_state == RESPONSE_WAIT_ACK) {
|
||||
// The full Content-Length has now been acknowledged. ESPAsyncWebServer
|
||||
// otherwise performs a graceful active close for every response, and
|
||||
// this IDF 4.4 stack retains malloc-backed TCP PCBs in TIME_WAIT for about
|
||||
// two minutes. Repeated WebConfig loads consume scarce internal heap and
|
||||
// can also prevent AsyncTCP from retiring close events, wedging both
|
||||
// infrastructure WiFi and ESP-NOW. Abort only after the final ACK so the
|
||||
// peer has the complete page, while freeing this PCB immediately. Keep
|
||||
// RESPONSE_WAIT_ACK: abort synchronously dispatches disconnect and the
|
||||
// request's scoped owner then destroys this response.
|
||||
if (client) client->abort();
|
||||
// otherwise performs a graceful active close for every response. Keep
|
||||
// the graceful close here too: browsers can report a complete,
|
||||
// Content-Length-delimited response as a failed transfer when the peer
|
||||
// force-aborts the TCP PCB immediately after the final ACK.
|
||||
if (client) client->close();
|
||||
return 0;
|
||||
}
|
||||
return queueNext(request);
|
||||
@@ -1636,21 +1633,38 @@ bool WebConfigServer::checkAuth(AsyncWebServerRequest* req) {
|
||||
// Handlers (async_tcp task - no CLI/prefs writes, no radio access)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool webConfigClientAcceptsGzip(AsyncWebServerRequest* req) {
|
||||
return req && mesh::http::acceptsGzip(req->hasHeader("Accept-Encoding")
|
||||
? req->getHeader("Accept-Encoding")->value().c_str() : nullptr);
|
||||
}
|
||||
|
||||
static void webConfigRejectEncoding(AsyncWebServerRequest* req) {
|
||||
AsyncWebServerResponse* res = req->beginResponse(
|
||||
406, "text/plain", "WebConfig requires gzip support");
|
||||
res->addHeader("Vary", "Accept-Encoding");
|
||||
res->addHeader("Cache-Control", "no-store");
|
||||
req->send(res);
|
||||
}
|
||||
|
||||
void WebConfigServer::handleRoot(AsyncWebServerRequest* req) {
|
||||
if (_mode == MODE_OFF) { req->send(503); return; }
|
||||
_last_activity = millis();
|
||||
if (!webConfigClientAcceptsGzip(req)) { webConfigRejectEncoding(req); return; }
|
||||
if (req->hasHeader("If-None-Match") &&
|
||||
req->getHeader("If-None-Match")->value() == WEBCONFIG_HTML_ETAG) {
|
||||
req->send(304);
|
||||
return;
|
||||
}
|
||||
AsyncWebServerResponse* res =
|
||||
req->beginResponse(200, "text/html", WEBCONFIG_HTML_LOADER,
|
||||
req->beginResponse(200, "text/html; charset=utf-8", WEBCONFIG_HTML_LOADER,
|
||||
WEBCONFIG_HTML_LOADER_LEN);
|
||||
res->addHeader("ETag", WEBCONFIG_HTML_ETAG);
|
||||
res->addHeader("Content-Encoding", "gzip");
|
||||
res->addHeader("Vary", "Accept-Encoding");
|
||||
req->send(res);
|
||||
}
|
||||
|
||||
|
||||
void WebConfigServer::handleUi(AsyncWebServerRequest* req) {
|
||||
if (_mode == MODE_OFF) { req->send(503); return; }
|
||||
_last_activity = millis();
|
||||
@@ -1664,13 +1678,11 @@ void WebConfigServer::handleUi(AsyncWebServerRequest* req) {
|
||||
req->send(res);
|
||||
return;
|
||||
}
|
||||
AsyncWebServerResponse* res =
|
||||
new WebConfigPacedProgmemResponse("text/html; charset=utf-8",
|
||||
WEBCONFIG_HTML_BR,
|
||||
WEBCONFIG_HTML_BR_LEN);
|
||||
// Keep exactly one whole-page asset in flash. Brotli is decoded by the
|
||||
// browser; the ESP32 only streams these precompressed bytes.
|
||||
res->addHeader("Content-Encoding", "br");
|
||||
if (!webConfigClientAcceptsGzip(req)) { webConfigRejectEncoding(req); return; }
|
||||
AsyncWebServerResponse* res = new WebConfigPacedProgmemResponse(
|
||||
"text/html; charset=utf-8", WEBCONFIG_HTML_GZ, WEBCONFIG_HTML_GZ_LEN);
|
||||
res->addHeader("Content-Encoding", "gzip");
|
||||
res->addHeader("Vary", "Accept-Encoding");
|
||||
res->addHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||
req->send(res);
|
||||
}
|
||||
@@ -2767,8 +2779,14 @@ void WebConfigServer::handleNotFound(AsyncWebServerRequest* req) {
|
||||
if (url.indexOf("generate_204") >= 0 || url.indexOf("gen_204") >= 0) {
|
||||
req->send(204); // Android
|
||||
} else if (url.indexOf("hotspot-detect") >= 0 || url.indexOf("success") >= 0) {
|
||||
req->send(200, "text/html", // Apple CNA
|
||||
"<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>");
|
||||
if (!webConfigClientAcceptsGzip(req)) { webConfigRejectEncoding(req); return; }
|
||||
AsyncWebServerResponse* res = req->beginResponse(
|
||||
200, "text/html; charset=utf-8", WEBCONFIG_HTML_SUCCESS,
|
||||
WEBCONFIG_HTML_SUCCESS_LEN);
|
||||
res->addHeader("Content-Encoding", "gzip");
|
||||
res->addHeader("Vary", "Accept-Encoding");
|
||||
res->addHeader("Cache-Control", "no-store");
|
||||
req->send(res); // Apple CNA
|
||||
} else if (url.indexOf("ncsi.txt") >= 0) {
|
||||
req->send(200, "text/plain", "Microsoft NCSI"); // Windows
|
||||
} else if (url.indexOf("connecttest.txt") >= 0) {
|
||||
|
||||
Reference in new issue
Block a user