updated firmware version in platformio; added ble pin display to ble home page in ui; updates to try fixing form login functionality in web reader

This commit is contained in:
pelgraine
2026-02-22 16:47:13 +11:00
parent 2024dc2a1b
commit 4a1fe3b190
3 changed files with 163 additions and 21 deletions

View File

@@ -402,8 +402,19 @@ public:
display.drawXbm((display.width() - 32) / 2, 18,
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
32, 32);
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 53, "< Connected >");
} else if (_task->isSerialEnabled() && the_mesh.getBLEPin() != 0) {
display.setColor(DisplayDriver::RED);
display.setTextSize(2);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
display.drawTextCentered(display.width() / 2, 53, tmp);
}
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL);
display.drawTextCentered(display.width() / 2, 72, "toggle: " PRESS_LABEL);
#endif
} else if (_page == HomePage::ADVERT) {
display.setColor(DisplayDriver::GREEN);

View File

@@ -932,11 +932,11 @@ private:
unsigned long _formLastCharAt; // millis() of last char typed (for brief password reveal)
// Cookies (simple key=value store per domain)
#define WEB_MAX_COOKIES 32
#define WEB_MAX_COOKIES 16
struct Cookie {
char domain[64];
char name[64];
char value[256];
char value[512]; // AO3 session cookies are 300+ chars of base64
};
Cookie _cookies[WEB_MAX_COOKIES];
int _cookieCount;
@@ -980,7 +980,7 @@ private:
const char* host = strstr(url, "://");
if (host) host += 3; else host = url;
int i = 0;
while (host[i] && host[i] != '/' && host[i] != ':' && i < domainMax - 1) {
while (host[i] && host[i] != '/' && host[i] != ':' && host[i] != '?' && host[i] != '#' && i < domainMax - 1) {
domain[i] = host[i]; i++;
}
domain[i] = '\0';
@@ -1055,38 +1055,42 @@ private:
// when multiple Set-Cookie headers exist. Iterating by index catches all.
void captureResponseCookies(HTTPClient& http, const char* domain) {
int hCount = http.headers();
Serial.printf("Cookie capture: %d header slots, domain=%s\n", hCount, domain);
int found = 0;
for (int h = 0; h < hCount; h++) {
if (http.headerName(h).equalsIgnoreCase("Set-Cookie")) {
String sc = http.header(h);
String name = http.headerName(h);
String val = http.header(h);
Serial.printf(" Slot[%d] '%s' (%d chars) = '%.200s%s'\n", h, name.c_str(),
val.length(), val.c_str(), val.length() > 200 ? "..." : "");
if (name.equalsIgnoreCase("Set-Cookie")) {
// A single header entry might still contain multiple cookies
// concatenated by ESP32 with comma. Try to split them.
int start = 0;
while (start < (int)sc.length()) {
int comma = sc.indexOf(", ", start);
while (start < (int)val.length()) {
int comma = val.indexOf(", ", start);
String single;
if (comma >= 0) {
// Check if what follows looks like a new cookie (name=value before ;)
String rest = sc.substring(comma + 2);
String rest = val.substring(comma + 2);
int eq = rest.indexOf('=');
int semi = rest.indexOf(';');
if (eq > 0 && (semi < 0 || eq < semi)) {
single = sc.substring(start, comma);
single = val.substring(start, comma);
start = comma + 2;
} else {
// Comma is part of a cookie value (e.g. date), skip
comma = sc.indexOf(", ", comma + 2);
comma = val.indexOf(", ", comma + 2);
if (comma >= 0) {
single = sc.substring(start, comma);
single = val.substring(start, comma);
start = comma + 2;
} else {
single = sc.substring(start);
start = sc.length();
single = val.substring(start);
start = val.length();
}
}
} else {
single = sc.substring(start);
start = sc.length();
single = val.substring(start);
start = val.length();
}
parseSetCookie(single, domain);
found++;
@@ -1097,10 +1101,13 @@ private:
if (found == 0 && http.hasHeader("Set-Cookie")) {
String sc = http.header("Set-Cookie");
if (sc.length() > 0) {
Serial.printf("Cookie: fallback name-based lookup found: %s\n", sc.c_str());
Serial.printf("Cookie: fallback name-based: %.80s\n", sc.c_str());
parseSetCookie(sc, domain);
}
}
if (found == 0 && !http.hasHeader("Set-Cookie")) {
Serial.println("Cookie capture: NO Set-Cookie headers in response at all");
}
Serial.printf("Cookie jar now has %d cookies for domain %s\n", _cookieCount, domain);
}
@@ -1455,6 +1462,8 @@ private:
bool fetchPage(const char* url, const char* postBody = nullptr,
const char* contentType = nullptr,
const char* referer = nullptr) {
Serial.printf("WebReader: fetchPage('%s', post=%s, ref=%s)\n",
url, postBody ? "yes" : "no", referer ? referer : "(null)");
if (!allocateBuffers()) {
_fetchError = "Out of memory";
_mode = HOME;
@@ -1561,6 +1570,10 @@ private:
http.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
http.addHeader("Accept-Language", "en-US,en;q=0.9");
http.addHeader("Upgrade-Insecure-Requests", "1");
// Cache-busting: tell Cloudflare to revalidate with origin.
// Using max-age=0 (browser standard) instead of no-cache to avoid
// Cloudflare 525 SSL errors on some origins.
http.addHeader("Cache-Control", "max-age=0");
// Re-render splash before blocking call
if (_display) {
@@ -1594,7 +1607,6 @@ private:
http.addHeader("Sec-Fetch-Mode", "navigate");
http.addHeader("Sec-Fetch-Site", "same-origin");
http.addHeader("Sec-Fetch-User", "?1");
http.addHeader("Cache-Control", "max-age=0");
httpCode = http.POST((uint8_t*)postBody, strlen(postBody));
Serial.printf("WebReader: POST -> %d (Referer: %s)\n", httpCode, ref);
} else {
@@ -1748,12 +1760,118 @@ private:
}
if (form.isPost) {
// Save user-entered field values before first POST attempt.
// After fetchPage(), the form structures get overwritten by the new page.
// We need these to retry if CSRF fails (stale token from cached page).
struct SavedField { char name[32]; char value[WEB_MAX_FIELD_VALUE]; };
SavedField savedFields[WEB_MAX_FORM_FIELDS];
int savedCount = form.fieldCount;
char savedAction[WEB_MAX_URL_LEN];
char savedReferer[WEB_MAX_URL_LEN];
strncpy(savedAction, form.action, WEB_MAX_URL_LEN - 1);
savedAction[WEB_MAX_URL_LEN - 1] = '\0';
strncpy(savedReferer, _currentUrl, WEB_MAX_URL_LEN - 1);
savedReferer[WEB_MAX_URL_LEN - 1] = '\0';
for (int f = 0; f < form.fieldCount && f < WEB_MAX_FORM_FIELDS; f++) {
strncpy(savedFields[f].name, form.fields[f].name, 31);
savedFields[f].name[31] = '\0';
strncpy(savedFields[f].value, form.fields[f].value, WEB_MAX_FIELD_VALUE - 1);
savedFields[f].value[WEB_MAX_FIELD_VALUE - 1] = '\0';
}
String body = buildFormBody(form);
Serial.printf("WebReader: POST body (%d bytes): %s\n",
body.length(), body.c_str());
strncpy(_urlBuffer, form.action, WEB_MAX_URL_LEN - 1);
_urlLen = strlen(_urlBuffer);
return fetchPage(form.action, body.c_str(), nullptr, _currentUrl);
// --- First POST attempt ---
// On Cloudflare-cached sites, this may fail because the CSRF token
// came from a cached page with no session. But the 302 response
// WILL set _otwarchive_session, creating the session we need.
bool result = fetchPage(form.action, body.c_str(), nullptr, _currentUrl);
// Check if we got redirected to an auth error page
if (strstr(_currentUrl, "auth_error") || strstr(_currentUrl, "session_expired")) {
Serial.println("WebReader: Auth error detected — CSRF token was stale (cached page)");
Serial.println("WebReader: Retrying with fresh session cookie...");
// Show retry status on display
if (_display) {
_display->startFrame();
_display->setColor(DisplayDriver::GREEN);
_display->setTextSize(2);
_display->setCursor(10, 20);
_display->print("Logging in...");
_display->setTextSize(0);
_display->setColor(DisplayDriver::LIGHT);
_display->setCursor(10, 45);
_display->print("Refreshing session...");
_display->endFrame();
}
// Re-fetch the original form page.
// Now that we have _otwarchive_session cookie, Cloudflare should
// bypass its cache and serve a fresh page from AO3's origin with
// a CSRF token that matches our session.
Serial.printf("WebReader: Re-fetching form page: %s\n", savedReferer);
result = fetchPage(savedReferer);
if (result && _formCount > 0) {
// Find the login form and update its fields with saved user data
int retryForm = -1;
for (int fi = 0; fi < _formCount; fi++) {
// Match by action URL
if (strstr(_forms[fi].action, "login") || strstr(_forms[fi].action, "session")) {
retryForm = fi;
break;
}
}
if (retryForm < 0) retryForm = 0; // fallback to first form
WebForm& newForm = _forms[retryForm];
Serial.printf("WebReader: Found retry form %d with %d fields, action: %s\n",
retryForm, newForm.fieldCount, newForm.action);
// Copy saved user values into matching fields of new form.
// Skip CSRF tokens (they have fresh values from the re-fetch).
for (int sf = 0; sf < savedCount; sf++) {
// Skip CSRF-type fields — new form already has fresh token
if (strcmp(savedFields[sf].name, "authenticity_token") == 0 ||
strcmp(savedFields[sf].name, "csrf_token") == 0 ||
strcmp(savedFields[sf].name, "_token") == 0 ||
strcmp(savedFields[sf].name, "commit") == 0) {
continue;
}
// Find matching field in new form
for (int nf = 0; nf < newForm.fieldCount; nf++) {
if (strcmp(newForm.fields[nf].name, savedFields[sf].name) == 0) {
strncpy(newForm.fields[nf].value, savedFields[sf].value,
WEB_MAX_FIELD_VALUE - 1);
Serial.printf("WebReader: Restored field '%s'\n", savedFields[sf].name);
break;
}
}
}
// Build new POST body with fresh CSRF token + saved user data
String retryBody = buildFormBody(newForm);
Serial.printf("WebReader: Retry POST body (%d bytes): %s\n",
retryBody.length(), retryBody.c_str());
Serial.printf("WebReader: Cookie jar has %d cookies:\n", _cookieCount);
for (int c = 0; c < _cookieCount; c++) {
Serial.printf(" [%d] %s = %.30s... (domain=%s)\n",
c, _cookies[c].name, _cookies[c].value, _cookies[c].domain);
}
strncpy(_urlBuffer, newForm.action, WEB_MAX_URL_LEN - 1);
_urlLen = strlen(_urlBuffer);
result = fetchPage(newForm.action, retryBody.c_str(), nullptr, savedReferer);
} else {
Serial.println("WebReader: Re-fetch failed or no forms found for retry");
}
}
return result;
} else {
// GET - append form data as query string
String getUrl = form.action;
@@ -2111,7 +2229,12 @@ private:
if (_urlEditing) {
display.print("Type URL Ent:Go");
} else {
display.print("Q:Back W/S:Nav Ent:Go");
char footerBuf[48];
if (_cookieCount > 0)
snprintf(footerBuf, sizeof(footerBuf), "Q:Bk W/S:Nav Ent:Go X:Clr%dck", _cookieCount);
else
snprintf(footerBuf, sizeof(footerBuf), "Q:Back W/S:Nav Ent:Go");
display.print(footerBuf);
}
}
@@ -2462,6 +2585,14 @@ private:
return true;
}
// X - clear all cookies
if (c == 'x' || c == 'X') {
_cookieCount = 0;
memset(_cookies, 0, sizeof(_cookies));
Serial.println("WebReader: Cookies cleared");
return true;
}
return false;
}

View File

@@ -155,7 +155,7 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D HAS_4G_MODEM=1
-D MECK_WEB_READER=1
-D FIRMWARE_VERSION='"Meck v0.9.2-4G"'
-D FIRMWARE_VERSION='"Meck v0.9.3-4G"'
build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>