From 1a9b129a6d2f69e18fabd772f1c01add985d8472 Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 22 Sep 2025 16:06:25 +0200 Subject: [PATCH 01/24] Refactor tilefetcher --- src/ReusableTileFetcher.cpp | 243 +++++++++++++++++++++++++++--------- src/ReusableTileFetcher.hpp | 9 +- src/TileProvider.hpp | 2 +- 3 files changed, 190 insertions(+), 64 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 58f2767..3e8f737 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -28,13 +28,16 @@ ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) { - client.print(String("GET ") + path + " HTTP/1.1\r\n"); - client.print(String("Host: ") + host + "\r\n"); - client.print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\n"); - client.print("Connection: keep-alive\r\n"); - client.print("\r\n"); + Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + + s->print(String("GET ") + path + " HTTP/1.1\r\n"); + s->print(String("Host: ") + host + "\r\n"); + s->print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\n"); + s->print("Connection: keep-alive\r\n"); + s->print("\r\n"); } + void ReusableTileFetcher::disconnect() { client.stop(); @@ -46,105 +49,211 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul { String host, path; uint16_t port; - if (!parseUrl(url, host, path, port)) + bool useTLS; + + if (!parseUrl(url, host, path, port, useTLS)) { result = "Invalid URL"; return MemoryBuffer::empty(); } - if (!ensureConnection(host, port, timeoutMS, result)) - return MemoryBuffer::empty(); - - sendHttpRequest(host, path); - size_t contentLength = 0; - if (!readHttpHeaders(contentLength, timeoutMS, result)) - return MemoryBuffer::empty(); + // Follow redirects up to 3 + const int MAX_REDIRECTS = 3; + int redirects = 0; + String currentUrl = url; - if (contentLength == 0) + while (redirects <= MAX_REDIRECTS) { - result = "Empty response (Content-Length=0)"; - return MemoryBuffer::empty(); - } + // parse currentUrl each loop + if (!parseUrl(currentUrl, host, path, port, useTLS)) + { + result = "Invalid redirect URL: " + currentUrl; + return MemoryBuffer::empty(); + } - auto buffer = MemoryBuffer(contentLength); - if (!buffer.isAllocated()) - { - result = "Buffer allocation failed"; - return MemoryBuffer::empty(); - } + if (!ensureConnection(host, port, useTLS, timeoutMS, result)) + return MemoryBuffer::empty(); - if (!readBody(buffer, contentLength, timeoutMS, result)) - return MemoryBuffer::empty(); + sendHttpRequest(host, path); + + size_t contentLength = 0; + int statusCode = 0; + String location = ""; + bool connClose = false; + + if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, location, connClose)) + return MemoryBuffer::empty(); + + // Handle redirects (3xx) + if (statusCode >= 300 && statusCode < 400 && location.length() > 0) + { + // If server says close, close current socket + if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + + // Follow Location (may be absolute URL) + currentUrl = location; + redirects++; + continue; + } + + if (statusCode < 200 || statusCode >= 300) + { + result = "HTTP status " + String(statusCode); + if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + return MemoryBuffer::empty(); + } + + if (contentLength == 0) + { + result = "Empty response (Content-Length=0)"; + if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + return MemoryBuffer::empty(); + } + + auto buffer = MemoryBuffer(contentLength); + if (!buffer.isAllocated()) + { + result = "Buffer allocation failed"; + if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + return MemoryBuffer::empty(); + } + + if (!readBody(buffer, contentLength, timeoutMS, result)) + { + // readBody will disconnect on failure + return MemoryBuffer::empty(); + } + + // If server indicated Connection: close, drop socket now so next request will handshake again + if (connClose) { + if (currentIsTLS) secureClient.stop(); else client.stop(); + currentHost = ""; + currentPort = 0; + currentIsTLS = false; + } + + return buffer; + } - return buffer; + result = "Too many redirects"; + return MemoryBuffer::empty(); } -bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path, uint16_t &port) +bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS) { + useTLS = false; port = 80; - if (url.startsWith("https://")) - return false; - if (!url.startsWith("http://")) + if (url.startsWith("https://")) { + useTLS = true; + port = 443; + } else if (url.startsWith("http://")) { + useTLS = false; + port = 80; + } else { return false; + } - int idxHostStart = 7; // length of "http://" + int idxHostStart = useTLS ? 8 : 7; // length of "https://" : "http://" int idxPath = url.indexOf('/', idxHostStart); - if (idxPath == -1) - return false; + if (idxPath == -1) { + // allow bare host (no path) by setting path to "/" + host = url.substring(idxHostStart); + path = "/"; + return true; + } host = url.substring(idxHostStart, idxPath); path = url.substring(idxPath); return true; } -bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, unsigned long timeoutMS, String &result) +bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result) { - if (!client.connected() || host != currentHost || port != currentPort) - { - disconnect(); + // If we already have a connection to exact host/port/scheme and it's connected, keep it. + if ((useTLS == currentIsTLS) && (host == currentHost) && (port == currentPort) && + ((useTLS && secureClient.connected()) || (!useTLS && client.connected()))) { + return true; + } - // If caller didn’t set a timeout, fall back to 5000ms - uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - if (!client.connect(host.c_str(), port, connectTimeout)) - { - result = "Connection failed to " + host; + // Not connected or different target: close previous + if (currentIsTLS) { + if (secureClient) secureClient.stop(); + } else { + if (client) client.stop(); + } + currentHost = ""; + currentPort = 0; + currentIsTLS = false; + + // Choose client pointer + uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; + + if (useTLS) { + // Optionally: secureClient.setInsecure(); or setCACert(...) + // Do not recreate secureClient — reuse same object so mbedTLS can reuse the session. + secureClient.setInsecure(); + if (!secureClient.connect(host.c_str(), port, connectTimeout)) { + result = "TLS connect failed to " + host; + return false; + } + currentIsTLS = true; + } else { + if (!client.connect(host.c_str(), port, connectTimeout)) { + result = "TCP connect failed to " + host; return false; } - currentHost = host; - currentPort = port; - log_i("(Re)connected on core %i (timeout=%lu ms)", xPortGetCoreID(), connectTimeout); + currentIsTLS = false; } + currentHost = host; + currentPort = port; + log_i("(Re)connected on core %i to %s:%u (TLS=%d) (timeout=%lu ms)", xPortGetCoreID(), host.c_str(), port, useTLS ? 1 : 0, connectTimeout); return true; } -bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result) +bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, String &outLocation, bool &outConnectionClose) { String line; line.reserve(OSM_MAX_HEADERLENGTH); contentLength = 0; bool start = true; + outLocation = ""; + outConnectionClose = false; + statusCode = 0; uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - while (client.connected()) + Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + + while ((currentIsTLS ? secureClient.connected() : client.connected())) { if (!readLineWithTimeout(line, headerTimeout)) { result = "Header timeout"; - disconnect(); + // disconnect + if (currentIsTLS) secureClient.stop(); else client.stop(); return false; } line.trim(); if (start) { + // Example: HTTP/1.1 200 OK if (!line.startsWith("HTTP/1.")) { result = "Bad HTTP response: " + line; - disconnect(); + if (currentIsTLS) secureClient.stop(); else client.stop(); return false; } + // Extract status code + int sp1 = line.indexOf(' '); + if (sp1 >= 0) { + int sp2 = line.indexOf(' ', sp1 + 1); + String codeStr; + if (sp2 > sp1) codeStr = line.substring(sp1 + 1, sp2); + else codeStr = line.substring(sp1 + 1); + statusCode = codeStr.toInt(); + } start = false; } @@ -153,14 +262,25 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (line.startsWith("Content-Length:")) { - String val = line.substring(15); + String val = line.substring(String("Content-Length:").length()); val.trim(); contentLength = val.toInt(); } + else if (line.startsWith("Location:")) + { + outLocation = line.substring(String("Location:").length()); + outLocation.trim(); + } + else if (line.startsWith("Connection:")) + { + String val = line.substring(String("Connection:").length()); + val.trim(); + if (val.equalsIgnoreCase("close")) outConnectionClose = true; + } } if (contentLength == 0) - log_w("Content-Length = 0 (valid empty body)"); + log_w("Content-Length = 0 (valid empty body or chunked not supported)"); return true; } @@ -171,18 +291,23 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u size_t readSize = 0; unsigned long lastReadTime = millis(); - // Respect caller’s remaining budget, default to 5000ms if none const unsigned long maxStall = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; + Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + while (readSize < contentLength) { - size_t availableData = client.available(); + size_t availableData = (currentIsTLS ? secureClient.available() : client.available()); if (availableData == 0) { if (millis() - lastReadTime >= maxStall) { result = "Body read stalled for " + String(maxStall) + " ms"; - disconnect(); + // disconnect underlying client + if (currentIsTLS) secureClient.stop(); else client.stop(); + currentHost = ""; + currentPort = 0; + currentIsTLS = false; return false; } taskYIELD(); @@ -192,7 +317,7 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u size_t remaining = contentLength - readSize; size_t toRead = std::min(availableData, remaining); - int bytesRead = client.readBytes(dest + readSize, toRead); + int bytesRead = (currentIsTLS ? secureClient.readBytes(dest + readSize, toRead) : client.readBytes(dest + readSize, toRead)); if (bytesRead > 0) { readSize += bytesRead; @@ -211,18 +336,16 @@ bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) while ((millis() - start) < timeoutMs) { - if (client.available()) + int availableData = (currentIsTLS ? secureClient.available() : client.available()); + if (availableData) { - const char c = client.read(); + const char c = (currentIsTLS ? secureClient.read() : client.read()); if (c == '\r') continue; - if (c == '\n') return true; - if (line.length() >= OSM_MAX_HEADERLENGTH - 1) return false; - line += c; } else diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index ec0fbcf..679e1ee 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include "MemoryBuffer.hpp" @@ -44,13 +45,15 @@ class ReusableTileFetcher private: WiFiClient client; + WiFiClientSecure secureClient; + bool currentIsTLS = false; // true if secureClient is the active connection String currentHost; uint16_t currentPort = 80; - bool parseUrl(const String &url, String &host, String &path, uint16_t &port); - bool ensureConnection(const String &host, uint16_t port, unsigned long timeoutMS, String &result); + bool parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS); + bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const String &host, const String &path); - bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result); + bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, String &outLocation, bool &outConnectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); bool readLineWithTimeout(String &line, uint32_t timeoutMs); }; diff --git a/src/TileProvider.hpp b/src/TileProvider.hpp index 46752a6..b00af4b 100644 --- a/src/TileProvider.hpp +++ b/src/TileProvider.hpp @@ -38,7 +38,7 @@ struct TileProvider const TileProvider osmStandard = { "OSM Standard", - "http://tile.openstreetmap.org/{z}/{x}/{y}.png", + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "© OpenStreetMap contributors", false, "", From 2c84d7a6d9f312b4b980b7f160013d6c55e4e49c Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 22 Sep 2025 16:15:56 +0200 Subject: [PATCH 02/24] Formatting --- src/ReusableTileFetcher.cpp | 125 +++++++++++++++++++++++++++--------- src/TileProvider.hpp | 2 +- 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 3e8f737..8b54210 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -28,7 +28,7 @@ ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) { - Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); s->print(String("GET ") + path + " HTTP/1.1\r\n"); s->print(String("Host: ") + host + "\r\n"); @@ -37,7 +37,6 @@ void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path s->print("\r\n"); } - void ReusableTileFetcher::disconnect() { client.stop(); @@ -88,7 +87,14 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul if (statusCode >= 300 && statusCode < 400 && location.length() > 0) { // If server says close, close current socket - if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + if (connClose) + { + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); + currentHost = ""; + } // Follow Location (may be absolute URL) currentUrl = location; @@ -99,14 +105,28 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul if (statusCode < 200 || statusCode >= 300) { result = "HTTP status " + String(statusCode); - if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + if (connClose) + { + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); + currentHost = ""; + } return MemoryBuffer::empty(); } if (contentLength == 0) { result = "Empty response (Content-Length=0)"; - if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + if (connClose) + { + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); + currentHost = ""; + } return MemoryBuffer::empty(); } @@ -114,7 +134,14 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul if (!buffer.isAllocated()) { result = "Buffer allocation failed"; - if (connClose) { if (currentIsTLS) secureClient.stop(); else client.stop(); currentHost = ""; } + if (connClose) + { + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); + currentHost = ""; + } return MemoryBuffer::empty(); } @@ -125,8 +152,12 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul } // If server indicated Connection: close, drop socket now so next request will handshake again - if (connClose) { - if (currentIsTLS) secureClient.stop(); else client.stop(); + if (connClose) + { + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); currentHost = ""; currentPort = 0; currentIsTLS = false; @@ -144,19 +175,25 @@ bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path useTLS = false; port = 80; - if (url.startsWith("https://")) { + if (url.startsWith("https://")) + { useTLS = true; port = 443; - } else if (url.startsWith("http://")) { + } + else if (url.startsWith("http://")) + { useTLS = false; port = 80; - } else { + } + else + { return false; } int idxHostStart = useTLS ? 8 : 7; // length of "https://" : "http://" int idxPath = url.indexOf('/', idxHostStart); - if (idxPath == -1) { + if (idxPath == -1) + { // allow bare host (no path) by setting path to "/" host = url.substring(idxHostStart); path = "/"; @@ -171,16 +208,22 @@ bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result) { // If we already have a connection to exact host/port/scheme and it's connected, keep it. - if ((useTLS == currentIsTLS) && (host == currentHost) && (port == currentPort) && - ((useTLS && secureClient.connected()) || (!useTLS && client.connected()))) { + if ((useTLS == currentIsTLS) && (host == currentHost) && (port == currentPort) && + ((useTLS && secureClient.connected()) || (!useTLS && client.connected()))) + { return true; } // Not connected or different target: close previous - if (currentIsTLS) { - if (secureClient) secureClient.stop(); - } else { - if (client) client.stop(); + if (currentIsTLS) + { + if (secureClient) + secureClient.stop(); + } + else + { + if (client) + client.stop(); } currentHost = ""; currentPort = 0; @@ -189,17 +232,22 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo // Choose client pointer uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - if (useTLS) { + if (useTLS) + { // Optionally: secureClient.setInsecure(); or setCACert(...) // Do not recreate secureClient — reuse same object so mbedTLS can reuse the session. - secureClient.setInsecure(); - if (!secureClient.connect(host.c_str(), port, connectTimeout)) { + secureClient.setInsecure(); + if (!secureClient.connect(host.c_str(), port, connectTimeout)) + { result = "TLS connect failed to " + host; return false; } currentIsTLS = true; - } else { - if (!client.connect(host.c_str(), port, connectTimeout)) { + } + else + { + if (!client.connect(host.c_str(), port, connectTimeout)) + { result = "TCP connect failed to " + host; return false; } @@ -223,7 +271,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + // Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); while ((currentIsTLS ? secureClient.connected() : client.connected())) { @@ -231,7 +279,10 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t { result = "Header timeout"; // disconnect - if (currentIsTLS) secureClient.stop(); else client.stop(); + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); return false; } @@ -242,16 +293,22 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (!line.startsWith("HTTP/1.")) { result = "Bad HTTP response: " + line; - if (currentIsTLS) secureClient.stop(); else client.stop(); + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); return false; } // Extract status code int sp1 = line.indexOf(' '); - if (sp1 >= 0) { + if (sp1 >= 0) + { int sp2 = line.indexOf(' ', sp1 + 1); String codeStr; - if (sp2 > sp1) codeStr = line.substring(sp1 + 1, sp2); - else codeStr = line.substring(sp1 + 1); + if (sp2 > sp1) + codeStr = line.substring(sp1 + 1, sp2); + else + codeStr = line.substring(sp1 + 1); statusCode = codeStr.toInt(); } start = false; @@ -275,7 +332,8 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t { String val = line.substring(String("Connection:").length()); val.trim(); - if (val.equalsIgnoreCase("close")) outConnectionClose = true; + if (val.equalsIgnoreCase("close")) + outConnectionClose = true; } } @@ -293,7 +351,7 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u const unsigned long maxStall = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); + // Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); while (readSize < contentLength) { @@ -304,7 +362,10 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u { result = "Body read stalled for " + String(maxStall) + " ms"; // disconnect underlying client - if (currentIsTLS) secureClient.stop(); else client.stop(); + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); currentHost = ""; currentPort = 0; currentIsTLS = false; diff --git a/src/TileProvider.hpp b/src/TileProvider.hpp index b00af4b..a4c1584 100644 --- a/src/TileProvider.hpp +++ b/src/TileProvider.hpp @@ -68,7 +68,7 @@ const TileProvider ThunderForestCycle256 = { "YOUR_THUNDERFOREST_KEY", 22, 0, 256}; -// Replace 'YOUR_THUNDERFOREST_KEY' above with a -free- Thunderforest API key +// Replace 'YOUR_THUNDERFOREST_KEY' above with a -free- Thunderforest API key // and uncomment one of the following line to use Thunderforest tiles // const TileProvider tileProviders[] = {osmStandard, ThunderTransportDark256, ThunderForestCycle512, ThunderForestCycle256}; From aadef54b19f3185eb3ee687de9700c2d3dbc5bfc Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 22 Sep 2025 21:39:07 +0200 Subject: [PATCH 03/24] Refactor for simplicity --- src/ReusableTileFetcher.cpp | 193 +++++++++++------------------------- src/TileProvider.hpp | 10 +- 2 files changed, 62 insertions(+), 141 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 8b54210..f6b40f8 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -39,9 +39,11 @@ void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path void ReusableTileFetcher::disconnect() { - client.stop(); + if (currentIsTLS) + secureClient.stop(); + else + client.stop(); currentHost = ""; - currentPort = 80; } MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, unsigned long timeoutMS) @@ -50,124 +52,63 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul uint16_t port; bool useTLS; + log_i("url: %s", url.c_str()); + if (!parseUrl(url, host, path, port, useTLS)) { result = "Invalid URL"; return MemoryBuffer::empty(); } - // Follow redirects up to 3 - const int MAX_REDIRECTS = 3; - int redirects = 0; - String currentUrl = url; - - while (redirects <= MAX_REDIRECTS) - { - // parse currentUrl each loop - if (!parseUrl(currentUrl, host, path, port, useTLS)) - { - result = "Invalid redirect URL: " + currentUrl; - return MemoryBuffer::empty(); - } - - if (!ensureConnection(host, port, useTLS, timeoutMS, result)) - return MemoryBuffer::empty(); - - sendHttpRequest(host, path); - - size_t contentLength = 0; - int statusCode = 0; - String location = ""; - bool connClose = false; - - if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, location, connClose)) - return MemoryBuffer::empty(); - - // Handle redirects (3xx) - if (statusCode >= 300 && statusCode < 400 && location.length() > 0) - { - // If server says close, close current socket - if (connClose) - { - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - } + if (!ensureConnection(host, port, useTLS, timeoutMS, result)) + return MemoryBuffer::empty(); - // Follow Location (may be absolute URL) - currentUrl = location; - redirects++; - continue; - } + sendHttpRequest(host, path); - if (statusCode < 200 || statusCode >= 300) - { - result = "HTTP status " + String(statusCode); - if (connClose) - { - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - } - return MemoryBuffer::empty(); - } + size_t contentLength = 0; + int statusCode = 0; + String location; + bool connClose = false; - if (contentLength == 0) - { - result = "Empty response (Content-Length=0)"; - if (connClose) - { - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - } - return MemoryBuffer::empty(); - } + if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, location, connClose)) + { + disconnect(); + return MemoryBuffer::empty(); + } - auto buffer = MemoryBuffer(contentLength); - if (!buffer.isAllocated()) - { - result = "Buffer allocation failed"; - if (connClose) - { - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - } - return MemoryBuffer::empty(); - } + if (statusCode != 200) + { + result = "HTTP status " + String(statusCode); + disconnect(); + return MemoryBuffer::empty(); + } - if (!readBody(buffer, contentLength, timeoutMS, result)) - { - // readBody will disconnect on failure - return MemoryBuffer::empty(); - } + if (contentLength == 0) + { + result = "Empty response (Content-Length=0)"; + disconnect(); + return MemoryBuffer::empty(); + } - // If server indicated Connection: close, drop socket now so next request will handshake again - if (connClose) - { - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - currentPort = 0; - currentIsTLS = false; - } + auto buffer = MemoryBuffer(contentLength); + if (!buffer.isAllocated()) + { + result = "Buffer allocation failed"; + disconnect(); + return MemoryBuffer::empty(); + } - return buffer; + if (!readBody(buffer, contentLength, timeoutMS, result)) + { + disconnect(); + return MemoryBuffer::empty(); } - result = "Too many redirects"; - return MemoryBuffer::empty(); + // Server requested connection close → drop it + if (connClose) + disconnect(); + + return buffer; } bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS) @@ -229,13 +170,10 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo currentPort = 0; currentIsTLS = false; - // Choose client pointer uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; if (useTLS) { - // Optionally: secureClient.setInsecure(); or setCACert(...) - // Do not recreate secureClient — reuse same object so mbedTLS can reuse the session. secureClient.setInsecure(); if (!secureClient.connect(host.c_str(), port, connectTimeout)) { @@ -271,32 +209,23 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - // Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); - while ((currentIsTLS ? secureClient.connected() : client.connected())) { if (!readLineWithTimeout(line, headerTimeout)) { result = "Header timeout"; - // disconnect - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); return false; } line.trim(); + + log_d("read: %s", line.c_str()); + if (start) { - // Example: HTTP/1.1 200 OK if (!line.startsWith("HTTP/1.")) { result = "Bad HTTP response: " + line; - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); return false; } // Extract status code @@ -317,20 +246,22 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (line.length() == 0) break; // End of headers - if (line.startsWith("Content-Length:")) + line.toLowerCase(); + + if (line.startsWith("content-length:")) { - String val = line.substring(String("Content-Length:").length()); + String val = line.substring(String("content-length:").length()); val.trim(); contentLength = val.toInt(); } - else if (line.startsWith("Location:")) + else if (line.startsWith("location:")) { - outLocation = line.substring(String("Location:").length()); + outLocation = line.substring(String("location:").length()); outLocation.trim(); } - else if (line.startsWith("Connection:")) + else if (line.startsWith("connection:")) { - String val = line.substring(String("Connection:").length()); + String val = line.substring(String("connection:").length()); val.trim(); if (val.equalsIgnoreCase("close")) outConnectionClose = true; @@ -351,8 +282,6 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u const unsigned long maxStall = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - // Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); - while (readSize < contentLength) { size_t availableData = (currentIsTLS ? secureClient.available() : client.available()); @@ -361,14 +290,6 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u if (millis() - lastReadTime >= maxStall) { result = "Body read stalled for " + String(maxStall) + " ms"; - // disconnect underlying client - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - currentHost = ""; - currentPort = 0; - currentIsTLS = false; return false; } taskYIELD(); diff --git a/src/TileProvider.hpp b/src/TileProvider.hpp index a4c1584..2a9ca15 100644 --- a/src/TileProvider.hpp +++ b/src/TileProvider.hpp @@ -46,7 +46,7 @@ const TileProvider osmStandard = { const TileProvider ThunderTransportDark256 = { "Thunderforest Transport Dark 256px", - "http://tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png?apikey={apiKey}", + "https://tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png?apikey={apiKey}", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", @@ -54,7 +54,7 @@ const TileProvider ThunderTransportDark256 = { const TileProvider ThunderForestCycle512 = { "Thunderforest Cycle 512px", - "http://tile.thunderforest.com/cycle/{z}/{x}/{y}@2x.png?apikey={apiKey}", + "https://tile.thunderforest.com/cycle/{z}/{x}/{y}@2x.png?apikey={apiKey}", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", @@ -62,7 +62,7 @@ const TileProvider ThunderForestCycle512 = { const TileProvider ThunderForestCycle256 = { "Thunderforest Cycle 256px", - "http://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey={apiKey}", + "https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey={apiKey}", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", @@ -72,12 +72,12 @@ const TileProvider ThunderForestCycle256 = { // and uncomment one of the following line to use Thunderforest tiles // const TileProvider tileProviders[] = {osmStandard, ThunderTransportDark256, ThunderForestCycle512, ThunderForestCycle256}; -// const TileProvider tileProviders[] = {ThunderTransportDark256}; +const TileProvider tileProviders[] = {ThunderTransportDark256}; // const TileProvider tileProviders[] = {ThunderForestCycle512}; // const TileProvider tileProviders[] = {ThunderForestCycle256}; // If one of the above definitions is used, the following line should be commented out -const TileProvider tileProviders[] = {osmStandard}; +//const TileProvider tileProviders[] = {osmStandard}; constexpr int OSM_TILEPROVIDERS = sizeof(tileProviders) / sizeof(TileProvider); From 043c0c742304fb80d4f75f7b0a719201db1e35d3 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 23 Sep 2025 11:19:58 +0200 Subject: [PATCH 04/24] Cleanup --- src/ReusableTileFetcher.cpp | 59 +++++++++++++++++++------------------ src/ReusableTileFetcher.hpp | 4 +-- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index f6b40f8..3ef824c 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -44,6 +44,8 @@ void ReusableTileFetcher::disconnect() else client.stop(); currentHost = ""; + currentPort = 0; + currentIsTLS = false; } MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, unsigned long timeoutMS) @@ -52,7 +54,7 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul uint16_t port; bool useTLS; - log_i("url: %s", url.c_str()); + log_d("url: %s", url.c_str()); if (!parseUrl(url, host, path, port, useTLS)) { @@ -70,7 +72,7 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul String location; bool connClose = false; - if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, location, connClose)) + if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, connClose)) { disconnect(); return MemoryBuffer::empty(); @@ -157,15 +159,10 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo // Not connected or different target: close previous if (currentIsTLS) - { - if (secureClient) - secureClient.stop(); - } + secureClient.stop(); else - { - if (client) - client.stop(); - } + client.stop(); + currentHost = ""; currentPort = 0; currentIsTLS = false; @@ -197,14 +194,13 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo return true; } -bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, String &outLocation, bool &outConnectionClose) +bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, bool &connectionClose) { String line; line.reserve(OSM_MAX_HEADERLENGTH); contentLength = 0; bool start = true; - outLocation = ""; - outConnectionClose = false; + connectionClose = false; statusCode = 0; uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; @@ -219,7 +215,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t line.trim(); - log_d("read: %s", line.c_str()); + log_d("read header: %s", line.c_str()); if (start) { @@ -248,28 +244,26 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t line.toLowerCase(); - if (line.startsWith("content-length:")) + static const char *CONTENT_LENGTH = "content-length:"; + static const char *CONNECTION = "connection:"; + + if (line.startsWith(CONTENT_LENGTH)) { - String val = line.substring(String("content-length:").length()); + String val = line.substring(String(CONTENT_LENGTH).length()); val.trim(); contentLength = val.toInt(); } - else if (line.startsWith("location:")) - { - outLocation = line.substring(String("location:").length()); - outLocation.trim(); - } - else if (line.startsWith("connection:")) + else if (line.startsWith(CONNECTION)) { - String val = line.substring(String("connection:").length()); + String val = line.substring(String(CONNECTION).length()); val.trim(); if (val.equalsIgnoreCase("close")) - outConnectionClose = true; + connectionClose = true; } } if (contentLength == 0) - log_w("Content-Length = 0 (valid empty body or chunked not supported)"); + log_w("Content-Length = 0"); return true; } @@ -282,14 +276,20 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u const unsigned long maxStall = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; + if (currentIsTLS) + secureClient.setTimeout(maxStall); + else + client.setTimeout(maxStall); + while (readSize < contentLength) { - size_t availableData = (currentIsTLS ? secureClient.available() : client.available()); + size_t availableData = currentIsTLS ? secureClient.available() : client.available(); if (availableData == 0) { if (millis() - lastReadTime >= maxStall) { - result = "Body read stalled for " + String(maxStall) + " ms"; + result = "Timeout: body read stalled for " + String(maxStall) + " ms"; + disconnect(); return false; } taskYIELD(); @@ -299,7 +299,10 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u size_t remaining = contentLength - readSize; size_t toRead = std::min(availableData, remaining); - int bytesRead = (currentIsTLS ? secureClient.readBytes(dest + readSize, toRead) : client.readBytes(dest + readSize, toRead)); + int bytesRead = currentIsTLS + ? secureClient.readBytes(dest + readSize, toRead) + : client.readBytes(dest + readSize, toRead); + if (bytesRead > 0) { readSize += bytesRead; diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 679e1ee..413f9eb 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -46,14 +46,14 @@ class ReusableTileFetcher private: WiFiClient client; WiFiClientSecure secureClient; - bool currentIsTLS = false; // true if secureClient is the active connection + bool currentIsTLS = false; String currentHost; uint16_t currentPort = 80; bool parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS); bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const String &host, const String &path); - bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, String &outLocation, bool &outConnectionClose); + bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, bool &connectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); bool readLineWithTimeout(String &line, uint32_t timeoutMs); }; From fc93c2d62077b30fdb9c75ba7bdbbe3fac3a4768 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 23 Sep 2025 11:27:00 +0200 Subject: [PATCH 05/24] Set OpenStreetmap as default provider --- src/TileProvider.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TileProvider.hpp b/src/TileProvider.hpp index 2a9ca15..b30136e 100644 --- a/src/TileProvider.hpp +++ b/src/TileProvider.hpp @@ -72,12 +72,12 @@ const TileProvider ThunderForestCycle256 = { // and uncomment one of the following line to use Thunderforest tiles // const TileProvider tileProviders[] = {osmStandard, ThunderTransportDark256, ThunderForestCycle512, ThunderForestCycle256}; -const TileProvider tileProviders[] = {ThunderTransportDark256}; +// const TileProvider tileProviders[] = {ThunderTransportDark256}; // const TileProvider tileProviders[] = {ThunderForestCycle512}; // const TileProvider tileProviders[] = {ThunderForestCycle256}; // If one of the above definitions is used, the following line should be commented out -//const TileProvider tileProviders[] = {osmStandard}; +const TileProvider tileProviders[] = {osmStandard}; constexpr int OSM_TILEPROVIDERS = sizeof(tileProviders) / sizeof(TileProvider); From bf95848495912f950dc59df61a1f05c3ee225e51 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 23 Sep 2025 14:13:35 +0200 Subject: [PATCH 06/24] refactor some sanity checks --- src/ReusableTileFetcher.cpp | 49 ++++++++++++++++++++----------------- src/ReusableTileFetcher.hpp | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 3ef824c..20317cd 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -68,23 +68,15 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul sendHttpRequest(host, path); size_t contentLength = 0; - int statusCode = 0; String location; bool connClose = false; - if (!readHttpHeaders(contentLength, timeoutMS, result, statusCode, connClose)) + if (!readHttpHeaders(contentLength, timeoutMS, result, connClose)) { disconnect(); return MemoryBuffer::empty(); } - if (statusCode != 200) - { - result = "HTTP status " + String(statusCode); - disconnect(); - return MemoryBuffer::empty(); - } - if (contentLength == 0) { result = "Empty response (Content-Length=0)"; @@ -194,14 +186,14 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo return true; } -bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, bool &connectionClose) +bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose) { String line; line.reserve(OSM_MAX_HEADERLENGTH); contentLength = 0; bool start = true; connectionClose = false; - statusCode = 0; + bool pngFound = false; uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; @@ -214,7 +206,6 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t } line.trim(); - log_d("read header: %s", line.c_str()); if (start) @@ -225,17 +216,20 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t return false; } // Extract status code + int statusCode; int sp1 = line.indexOf(' '); if (sp1 >= 0) { int sp2 = line.indexOf(' ', sp1 + 1); - String codeStr; - if (sp2 > sp1) - codeStr = line.substring(sp1 + 1, sp2); - else - codeStr = line.substring(sp1 + 1); + String codeStr = (sp2 > sp1) ? line.substring(sp1 + 1, sp2) + : line.substring(sp1 + 1); statusCode = codeStr.toInt(); } + if (statusCode != 200) + { + result = "HTTP error " + String(statusCode); + return false; + } start = false; } @@ -245,25 +239,36 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t line.toLowerCase(); static const char *CONTENT_LENGTH = "content-length:"; + static const char *CONTENT_TYPE = "content-type:"; static const char *CONNECTION = "connection:"; if (line.startsWith(CONTENT_LENGTH)) { - String val = line.substring(String(CONTENT_LENGTH).length()); + String val = line.substring(strlen(CONTENT_LENGTH)); val.trim(); contentLength = val.toInt(); } else if (line.startsWith(CONNECTION)) { - String val = line.substring(String(CONNECTION).length()); + String val = line.substring(strlen(CONNECTION)); val.trim(); - if (val.equalsIgnoreCase("close")) + if (val.equals("close")) connectionClose = true; } + else if (line.startsWith(CONTENT_TYPE)) + { + String val = line.substring(strlen(CONTENT_TYPE)); + val.trim(); + if (val.equals("image/png")) + pngFound = true; + } } - if (contentLength == 0) - log_w("Content-Length = 0"); + if (!pngFound) + { + result = "Content-Type not PNG"; + return false; + } return true; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 413f9eb..e6a3960 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -53,7 +53,7 @@ class ReusableTileFetcher bool parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS); bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const String &host, const String &path); - bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, int &statusCode, bool &connectionClose); + bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); bool readLineWithTimeout(String &line, uint32_t timeoutMs); }; From b157771a457a502aaeaebfb95d3da0907417e46a Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 23 Sep 2025 14:35:42 +0200 Subject: [PATCH 07/24] Cleanup --- src/ReusableTileFetcher.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 20317cd..61a5304 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -107,9 +107,6 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS) { - useTLS = false; - port = 80; - if (url.startsWith("https://")) { useTLS = true; @@ -121,19 +118,12 @@ bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path port = 80; } else - { return false; - } - int idxHostStart = useTLS ? 8 : 7; // length of "https://" : "http://" + int idxHostStart = useTLS ? 8 : 7; // skip scheme int idxPath = url.indexOf('/', idxHostStart); if (idxPath == -1) - { - // allow bare host (no path) by setting path to "/" - host = url.substring(idxHostStart); - path = "/"; - return true; - } + return false; host = url.substring(idxHostStart, idxPath); path = url.substring(idxPath); From 6c516563cf14a709774f7dbe381b49d909ecaca4 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 24 Sep 2025 13:42:48 +0200 Subject: [PATCH 08/24] Use the stack while parsing --- src/ReusableTileFetcher.cpp | 45 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 61a5304..1446c3b 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -187,6 +187,15 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; + static constexpr const char CONTENT_LENGTH[] = "content-length:"; + static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; + + static constexpr const char CONTENT_TYPE[] = "content-type:"; + static constexpr size_t CONTENT_TYPE_LEN = sizeof(CONTENT_TYPE) - 1; + + static constexpr const char CONNECTION[] = "connection:"; + static constexpr size_t CONNECTION_LEN = sizeof(CONNECTION) - 1; + while ((currentIsTLS ? secureClient.connected() : client.connected())) { if (!readLineWithTimeout(line, headerTimeout)) @@ -206,7 +215,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t return false; } // Extract status code - int statusCode; + int statusCode = 0; int sp1 = line.indexOf(' '); if (sp1 >= 0) { @@ -226,30 +235,28 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (line.length() == 0) break; // End of headers - line.toLowerCase(); - - static const char *CONTENT_LENGTH = "content-length:"; - static const char *CONTENT_TYPE = "content-type:"; - static const char *CONNECTION = "connection:"; - - if (line.startsWith(CONTENT_LENGTH)) + const char *raw = line.c_str(); + if (strncasecmp(raw, CONTENT_LENGTH, CONTENT_LENGTH_LEN) == 0) { - String val = line.substring(strlen(CONTENT_LENGTH)); - val.trim(); - contentLength = val.toInt(); + const char *val = raw + CONTENT_LENGTH_LEN; + while (*val == ' ' || *val == '\t') + val++; // trim left + contentLength = atoi(val); } - else if (line.startsWith(CONNECTION)) + else if (strncasecmp(raw, CONNECTION, CONNECTION_LEN) == 0) { - String val = line.substring(strlen(CONNECTION)); - val.trim(); - if (val.equals("close")) + const char *val = raw + CONNECTION_LEN; + while (*val == ' ' || *val == '\t') + val++; + if (strcasecmp(val, "close") == 0) connectionClose = true; } - else if (line.startsWith(CONTENT_TYPE)) + else if (strncasecmp(raw, CONTENT_TYPE, CONTENT_TYPE_LEN) == 0) { - String val = line.substring(strlen(CONTENT_TYPE)); - val.trim(); - if (val.equals("image/png")) + const char *val = raw + CONTENT_TYPE_LEN; + while (*val == ' ' || *val == '\t') + val++; + if (strcasecmp(val, "image/png") == 0) pngFound = true; } } From 865c2219373abe439a878722fe092f497a0152a0 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 24 Sep 2025 14:21:01 +0200 Subject: [PATCH 09/24] Use in-place parsing Make `headerLine` a heap object --- src/ReusableTileFetcher.cpp | 55 +++++++++++++++++++++++++------------ src/ReusableTileFetcher.hpp | 1 + 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 1446c3b..323f012 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -23,7 +23,7 @@ #include "ReusableTileFetcher.hpp" -ReusableTileFetcher::ReusableTileFetcher() {} +ReusableTileFetcher::ReusableTileFetcher() { headerLine.reserve(OSM_MAX_HEADERLENGTH); } ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) @@ -178,15 +178,6 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose) { - String line; - line.reserve(OSM_MAX_HEADERLENGTH); - contentLength = 0; - bool start = true; - connectionClose = false; - bool pngFound = false; - - uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; - static constexpr const char CONTENT_LENGTH[] = "content-length:"; static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; @@ -196,6 +187,15 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t static constexpr const char CONNECTION[] = "connection:"; static constexpr size_t CONNECTION_LEN = sizeof(CONNECTION) - 1; + String &line = headerLine; + line = ""; + contentLength = 0; + bool start = true; + connectionClose = false; + bool pngFound = false; + + uint32_t headerTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; + while ((currentIsTLS ? secureClient.connected() : client.connected())) { if (!readLineWithTimeout(line, headerTimeout)) @@ -214,21 +214,42 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t result = "Bad HTTP response: " + line; return false; } - // Extract status code + int statusCode = 0; - int sp1 = line.indexOf(' '); - if (sp1 >= 0) + const char *reasonPhrase = ""; + + const char *buf = line.c_str(); + const char *sp1 = strchr(buf, ' '); + if (sp1) { - int sp2 = line.indexOf(' ', sp1 + 1); - String codeStr = (sp2 > sp1) ? line.substring(sp1 + 1, sp2) - : line.substring(sp1 + 1); - statusCode = codeStr.toInt(); + // parse up to 3 digits after first space + const char *p = sp1 + 1; + while (*p && isspace((unsigned char)*p)) + p++; // skip extra spaces + + while (*p && isdigit((unsigned char)*p)) + { + statusCode = statusCode * 10 + (*p - '0'); + p++; + } + + // skip a single space to get to reason phrase + if (*p == ' ') + reasonPhrase = p + 1; } + if (statusCode != 200) { result = "HTTP error " + String(statusCode); + if (*reasonPhrase) + { + result += " ("; + result += reasonPhrase; + result += ")"; + } return false; } + start = false; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index e6a3960..327065b 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -49,6 +49,7 @@ class ReusableTileFetcher bool currentIsTLS = false; String currentHost; uint16_t currentPort = 80; + String headerLine; bool parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS); bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); From c21d8d1850f8e28eca0ee5052c8bae31bb75f383 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 24 Sep 2025 14:44:22 +0200 Subject: [PATCH 10/24] Formatting and cleanup --- src/ReusableTileFetcher.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 323f012..1b79419 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -178,15 +178,6 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose) { - static constexpr const char CONTENT_LENGTH[] = "content-length:"; - static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; - - static constexpr const char CONTENT_TYPE[] = "content-type:"; - static constexpr size_t CONTENT_TYPE_LEN = sizeof(CONTENT_TYPE) - 1; - - static constexpr const char CONNECTION[] = "connection:"; - static constexpr size_t CONNECTION_LEN = sizeof(CONNECTION) - 1; - String &line = headerLine; line = ""; contentLength = 0; @@ -256,6 +247,15 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (line.length() == 0) break; // End of headers + static constexpr const char CONTENT_LENGTH[] = "content-length:"; + static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; + + static constexpr const char CONTENT_TYPE[] = "content-type:"; + static constexpr size_t CONTENT_TYPE_LEN = sizeof(CONTENT_TYPE) - 1; + + static constexpr const char CONNECTION[] = "connection:"; + static constexpr size_t CONNECTION_LEN = sizeof(CONNECTION) - 1; + const char *raw = line.c_str(); if (strncasecmp(raw, CONTENT_LENGTH, CONTENT_LENGTH_LEN) == 0) { From 8d5321dcf17ee6a74f290443aba3a178a9e69bf1 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 24 Sep 2025 14:49:09 +0200 Subject: [PATCH 11/24] Cleanup --- src/ReusableTileFetcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 1b79419..3712eb3 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -245,8 +245,9 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t } if (line.length() == 0) - break; // End of headers + break; + // start parsing static constexpr const char CONTENT_LENGTH[] = "content-length:"; static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; From 5280cf643920711f877df7db7e85a0c36120b9e8 Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 25 Sep 2025 11:06:41 +0200 Subject: [PATCH 12/24] Cleanup --- src/ReusableTileFetcher.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 3712eb3..9ca103f 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -140,14 +140,7 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo } // Not connected or different target: close previous - if (currentIsTLS) - secureClient.stop(); - else - client.stop(); - - currentHost = ""; - currentPort = 0; - currentIsTLS = false; + disconnect(); uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; @@ -195,7 +188,6 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t return false; } - line.trim(); log_d("read header: %s", line.c_str()); if (start) From 5918d25700b6d4d5e60ec896e50b7040e768e333 Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 25 Sep 2025 14:04:59 +0200 Subject: [PATCH 13/24] Cleanup --- src/ReusableTileFetcher.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 9ca103f..05af8ed 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -68,7 +68,6 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul sendHttpRequest(host, path); size_t contentLength = 0; - String location; bool connClose = false; if (!readHttpHeaders(contentLength, timeoutMS, result, connClose)) From e8931d18b24eb6bb886b1cb8d84543e2ec496a0b Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 25 Sep 2025 15:52:24 +0200 Subject: [PATCH 14/24] Refactor host and path from String to char * --- src/ReusableTileFetcher.cpp | 42 ++++++++++++++++++++++++------------- src/ReusableTileFetcher.hpp | 6 ++++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 05af8ed..62f68cd 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -26,15 +26,14 @@ ReusableTileFetcher::ReusableTileFetcher() { headerLine.reserve(OSM_MAX_HEADERLENGTH); } ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } -void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) +void ReusableTileFetcher::sendHttpRequest(const char *host, const char *path) { Stream *s = currentIsTLS ? static_cast(&secureClient) : static_cast(&client); - s->print(String("GET ") + path + " HTTP/1.1\r\n"); - s->print(String("Host: ") + host + "\r\n"); - s->print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\n"); - s->print("Connection: keep-alive\r\n"); - s->print("\r\n"); + char buf[256]; + snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\nHost: %s\r\n", path, host); + s->print(buf); + s->print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\nConnection: keep-alive\r\n\r\n"); } void ReusableTileFetcher::disconnect() @@ -50,22 +49,23 @@ void ReusableTileFetcher::disconnect() MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, unsigned long timeoutMS) { - String host, path; - uint16_t port; + char currentHost[128]; + char currentPath[128]; + uint16_t currentPort; bool useTLS; log_d("url: %s", url.c_str()); - if (!parseUrl(url, host, path, port, useTLS)) + if (!parseUrl(url, currentHost, currentPath, currentPort, useTLS)) { result = "Invalid URL"; return MemoryBuffer::empty(); } - if (!ensureConnection(host, port, useTLS, timeoutMS, result)) + if (!ensureConnection(currentHost, currentPort, useTLS, timeoutMS, result)) return MemoryBuffer::empty(); - sendHttpRequest(host, path); + sendHttpRequest(currentHost, currentPath); size_t contentLength = 0; bool connClose = false; @@ -104,7 +104,7 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul return buffer; } -bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS) +bool ReusableTileFetcher::parseUrl(const String &url, char *host, char *path, uint16_t &port, bool &useTLS) { if (url.startsWith("https://")) { @@ -124,8 +124,22 @@ bool ReusableTileFetcher::parseUrl(const String &url, String &host, String &path if (idxPath == -1) return false; - host = url.substring(idxHostStart, idxPath); - path = url.substring(idxPath); + // Copy host substring into buffer + int hostLen = idxPath - idxHostStart; + if (hostLen <= 0 || hostLen >= OSM_MAX_HOST_LEN) + return false; // too long for buffer + + strncpy(host, url.c_str() + idxHostStart, hostLen); + host[hostLen] = '\0'; + + // Copy path substring into buffer + int pathLen = url.length() - idxPath; + if (pathLen <= 0 || pathLen >= OSM_MAX_PATH_LEN) + return false; // too long for buffer + + strncpy(path, url.c_str() + idxPath, pathLen); + path[pathLen] = '\0'; + return true; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 327065b..f04afe0 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -29,6 +29,8 @@ #include "MemoryBuffer.hpp" constexpr int OSM_MAX_HEADERLENGTH = 256; +constexpr int OSM_MAX_HOST_LEN = 128; +constexpr int OSM_MAX_PATH_LEN = 128; constexpr int OSM_DEFAULT_TIMEOUT_MS = 5000; class ReusableTileFetcher @@ -51,9 +53,9 @@ class ReusableTileFetcher uint16_t currentPort = 80; String headerLine; - bool parseUrl(const String &url, String &host, String &path, uint16_t &port, bool &useTLS); + bool parseUrl(const String &url, char *host, char *path, uint16_t &port, bool &useTLS); bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); - void sendHttpRequest(const String &host, const String &path); + void sendHttpRequest(const char *host, const char *path); bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); bool readLineWithTimeout(String &line, uint32_t timeoutMs); From 0b696d8dc51dd645292e85cfae82bb50467ce819 Mon Sep 17 00:00:00 2001 From: Cellie Date: Fri, 26 Sep 2025 13:54:08 +0200 Subject: [PATCH 15/24] Fix some codacy issues --- src/ReusableTileFetcher.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 62f68cd..e12a91d 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -49,8 +49,8 @@ void ReusableTileFetcher::disconnect() MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, unsigned long timeoutMS) { - char currentHost[128]; - char currentPath[128]; + char currentHost[OSM_MAX_HOST_LEN]; + char currentPath[OSM_MAX_PATH_LEN]; uint16_t currentPort; bool useTLS; @@ -124,21 +124,17 @@ bool ReusableTileFetcher::parseUrl(const String &url, char *host, char *path, ui if (idxPath == -1) return false; - // Copy host substring into buffer int hostLen = idxPath - idxHostStart; if (hostLen <= 0 || hostLen >= OSM_MAX_HOST_LEN) return false; // too long for buffer - strncpy(host, url.c_str() + idxHostStart, hostLen); - host[hostLen] = '\0'; + snprintf(host, OSM_MAX_HOST_LEN, "%.*s", hostLen, url.c_str() + idxHostStart); - // Copy path substring into buffer int pathLen = url.length() - idxPath; if (pathLen <= 0 || pathLen >= OSM_MAX_PATH_LEN) return false; // too long for buffer - strncpy(path, url.c_str() + idxPath, pathLen); - path[pathLen] = '\0'; + snprintf(path, OSM_MAX_PATH_LEN, "%.*s", pathLen, url.c_str() + idxPath); return true; } @@ -152,7 +148,6 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo return true; } - // Not connected or different target: close previous disconnect(); uint32_t connectTimeout = timeoutMS > 0 ? timeoutMS : OSM_DEFAULT_TIMEOUT_MS; From 947075de1c260c4b3f359060687f0f6382573cc9 Mon Sep 17 00:00:00 2001 From: Cellie Date: Sat, 27 Sep 2025 17:55:37 +0200 Subject: [PATCH 16/24] Refactor url from String to char array --- src/OpenStreetMap-esp32.cpp | 23 +++++++++------ src/OpenStreetMap-esp32.hpp | 2 +- src/ReusableTileFetcher.cpp | 57 +++++++++++++++++++------------------ src/ReusableTileFetcher.hpp | 10 +++---- src/TileProvider.hpp | 8 +++--- 5 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 17cbbab..718e177 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -205,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom, Til if (!jobs.empty()) { runJobs(jobs); - log_d("Finished %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); + log_i("Finished %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); } } @@ -360,12 +360,19 @@ void OpenStreetMap::PNGDraw(PNGDRAW *pDraw) bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result, unsigned long timeout) { - String url = currentProvider->urlTemplate; - url.replace("{x}", String(x)); - url.replace("{y}", String(y)); - url.replace("{z}", String(zoom)); - if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}")) - url.replace("{apiKey}", currentProvider->apiKey); + char url[256]; + if (currentProvider->requiresApiKey) + { + snprintf(url, sizeof(url), + currentProvider->urlTemplate, + zoom, x, y, currentProvider->apiKey); + } + else + { + snprintf(url, sizeof(url), + currentProvider->urlTemplate, + zoom, x, y); + } MemoryBuffer buffer = fetcher.fetchToBuffer(url, result, timeout); if (!buffer.isAllocated()) @@ -390,7 +397,7 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui const int decodeResult = png->decode(0, PNG_FAST_PALETTE); if (decodeResult != PNG_SUCCESS) { - result = "Decoding " + url + " failed with code: " + String(decodeResult); + result = "Decoding " + String(url) + " failed with code: " + String(decodeResult); return false; } diff --git a/src/OpenStreetMap-esp32.hpp b/src/OpenStreetMap-esp32.hpp index c5cd3fb..dd20637 100644 --- a/src/OpenStreetMap-esp32.hpp +++ b/src/OpenStreetMap-esp32.hpp @@ -41,7 +41,7 @@ constexpr uint16_t OSM_BGCOLOR = lgfx::color565(32, 32, 128); constexpr UBaseType_t OSM_TASK_PRIORITY = 1; -constexpr uint32_t OSM_TASK_STACKSIZE = 5120; +constexpr uint32_t OSM_TASK_STACKSIZE = 6144; constexpr uint32_t OSM_JOB_QUEUE_SIZE = 50; constexpr bool OSM_FORCE_SINGLECORE = false; constexpr int OSM_SINGLECORE_NUMBER = 1; diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index e12a91d..34adad8 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -42,30 +42,30 @@ void ReusableTileFetcher::disconnect() secureClient.stop(); else client.stop(); - currentHost = ""; + currentHost[0] = 0; currentPort = 0; currentIsTLS = false; } -MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, unsigned long timeoutMS) +MemoryBuffer ReusableTileFetcher::fetchToBuffer(const char *url, String &result, unsigned long timeoutMS) { - char currentHost[OSM_MAX_HOST_LEN]; - char currentPath[OSM_MAX_PATH_LEN]; - uint16_t currentPort; + char host[OSM_MAX_HOST_LEN]; + char path[OSM_MAX_PATH_LEN]; + uint16_t port; bool useTLS; - log_d("url: %s", url.c_str()); + log_d("url: %s", url); - if (!parseUrl(url, currentHost, currentPath, currentPort, useTLS)) + if (!parseUrl(url, host, path, port, useTLS)) { result = "Invalid URL"; return MemoryBuffer::empty(); } - if (!ensureConnection(currentHost, currentPort, useTLS, timeoutMS, result)) + if (!ensureConnection(host, port, useTLS, timeoutMS, result)) return MemoryBuffer::empty(); - sendHttpRequest(currentHost, currentPath); + sendHttpRequest(host, path); size_t contentLength = 0; bool connClose = false; @@ -104,14 +104,17 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &resul return buffer; } -bool ReusableTileFetcher::parseUrl(const String &url, char *host, char *path, uint16_t &port, bool &useTLS) +bool ReusableTileFetcher::parseUrl(const char *url, char *host, char *path, uint16_t &port, bool &useTLS) { - if (url.startsWith("https://")) + if (!url) + return false; + + if (strncmp(url, "https://", 8) == 0) { useTLS = true; port = 443; } - else if (url.startsWith("http://")) + else if (strncmp(url, "http://", 7) == 0) { useTLS = false; port = 80; @@ -120,29 +123,29 @@ bool ReusableTileFetcher::parseUrl(const String &url, char *host, char *path, ui return false; int idxHostStart = useTLS ? 8 : 7; // skip scheme - int idxPath = url.indexOf('/', idxHostStart); - if (idxPath == -1) - return false; + const char *pathPtr = strchr(url + idxHostStart, '/'); + if (!pathPtr) + return false; // no '/' → invalid - int hostLen = idxPath - idxHostStart; + int hostLen = pathPtr - (url + idxHostStart); if (hostLen <= 0 || hostLen >= OSM_MAX_HOST_LEN) return false; // too long for buffer - snprintf(host, OSM_MAX_HOST_LEN, "%.*s", hostLen, url.c_str() + idxHostStart); + snprintf(host, OSM_MAX_HOST_LEN, "%.*s", hostLen, url + idxHostStart); - int pathLen = url.length() - idxPath; + int pathLen = strlen(pathPtr); if (pathLen <= 0 || pathLen >= OSM_MAX_PATH_LEN) return false; // too long for buffer - snprintf(path, OSM_MAX_PATH_LEN, "%.*s", pathLen, url.c_str() + idxPath); + snprintf(path, OSM_MAX_PATH_LEN, "%s", pathPtr); return true; } -bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result) +bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result) { // If we already have a connection to exact host/port/scheme and it's connected, keep it. - if ((useTLS == currentIsTLS) && (host == currentHost) && (port == currentPort) && + if ((useTLS == currentIsTLS) && !strcmp(host, currentHost) && (port == currentPort) && ((useTLS && secureClient.connected()) || (!useTLS && client.connected()))) { return true; @@ -155,25 +158,25 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, bo if (useTLS) { secureClient.setInsecure(); - if (!secureClient.connect(host.c_str(), port, connectTimeout)) + if (!secureClient.connect(host, port, connectTimeout)) { - result = "TLS connect failed to " + host; + result = "TLS connect failed to " + String(host); return false; } currentIsTLS = true; } else { - if (!client.connect(host.c_str(), port, connectTimeout)) + if (!client.connect(host, port, connectTimeout)) { - result = "TCP connect failed to " + host; + result = "TCP connect failed to " + String(host); return false; } currentIsTLS = false; } - currentHost = host; + snprintf(currentHost, sizeof(currentHost), "%s", host); currentPort = port; - log_i("(Re)connected on core %i to %s:%u (TLS=%d) (timeout=%lu ms)", xPortGetCoreID(), host.c_str(), port, useTLS ? 1 : 0, connectTimeout); + log_i("(Re)connected on core %i to %s:%u (TLS=%d) (timeout=%lu ms)", xPortGetCoreID(), host, port, useTLS ? 1 : 0, connectTimeout); return true; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index f04afe0..583384a 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -42,19 +42,19 @@ class ReusableTileFetcher ReusableTileFetcher(const ReusableTileFetcher &) = delete; ReusableTileFetcher &operator=(const ReusableTileFetcher &) = delete; - MemoryBuffer fetchToBuffer(const String &url, String &result, unsigned long timeoutMS); + MemoryBuffer fetchToBuffer(const char *url, String &result, unsigned long timeoutMS); void disconnect(); private: WiFiClient client; WiFiClientSecure secureClient; bool currentIsTLS = false; - String currentHost; - uint16_t currentPort = 80; + char currentHost[OSM_MAX_HOST_LEN] = {0}; + uint16_t currentPort = 0; String headerLine; - bool parseUrl(const String &url, char *host, char *path, uint16_t &port, bool &useTLS); - bool ensureConnection(const String &host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); + bool parseUrl(const char *url, char *host, char *path, uint16_t &port, bool &useTLS); + bool ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const char *host, const char *path); bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); diff --git a/src/TileProvider.hpp b/src/TileProvider.hpp index b30136e..8799177 100644 --- a/src/TileProvider.hpp +++ b/src/TileProvider.hpp @@ -38,7 +38,7 @@ struct TileProvider const TileProvider osmStandard = { "OSM Standard", - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "https://tile.openstreetmap.org/%d/%d/%d.png", "© OpenStreetMap contributors", false, "", @@ -46,7 +46,7 @@ const TileProvider osmStandard = { const TileProvider ThunderTransportDark256 = { "Thunderforest Transport Dark 256px", - "https://tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png?apikey={apiKey}", + "https://tile.thunderforest.com/transport-dark/%d/%d/%d.png?apikey=%s", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", @@ -54,7 +54,7 @@ const TileProvider ThunderTransportDark256 = { const TileProvider ThunderForestCycle512 = { "Thunderforest Cycle 512px", - "https://tile.thunderforest.com/cycle/{z}/{x}/{y}@2x.png?apikey={apiKey}", + "https://tile.thunderforest.com/transport-dark/%d/%d/%d@2x.png?apikey=%s", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", @@ -62,7 +62,7 @@ const TileProvider ThunderForestCycle512 = { const TileProvider ThunderForestCycle256 = { "Thunderforest Cycle 256px", - "https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey={apiKey}", + "https://tile.thunderforest.com/cycle/%d/%d/%d.png?apikey=%s", "© Thunderforest, OpenStreetMap contributors", true, "YOUR_THUNDERFOREST_KEY", From cb5776c4be4a386af394d1a2ddd67799b911a5e2 Mon Sep 17 00:00:00 2001 From: Cellie Date: Sat, 27 Sep 2025 18:04:14 +0200 Subject: [PATCH 17/24] Fix a codacy issue - replaced strlen() with strnlen() --- src/ReusableTileFetcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 34adad8..f8c909b 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -133,7 +133,7 @@ bool ReusableTileFetcher::parseUrl(const char *url, char *host, char *path, uint snprintf(host, OSM_MAX_HOST_LEN, "%.*s", hostLen, url + idxHostStart); - int pathLen = strlen(pathPtr); + int pathLen = strnlen(pathPtr, OSM_MAX_PATH_LEN); if (pathLen <= 0 || pathLen >= OSM_MAX_PATH_LEN) return false; // too long for buffer From b0d4e91c314d90ecc1ace922e53a1c35b385359c Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 29 Sep 2025 10:42:09 +0200 Subject: [PATCH 18/24] Small fix --- src/ReusableTileFetcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index f8c909b..19919e3 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -134,7 +134,7 @@ bool ReusableTileFetcher::parseUrl(const char *url, char *host, char *path, uint snprintf(host, OSM_MAX_HOST_LEN, "%.*s", hostLen, url + idxHostStart); int pathLen = strnlen(pathPtr, OSM_MAX_PATH_LEN); - if (pathLen <= 0 || pathLen >= OSM_MAX_PATH_LEN) + if (pathLen == 0 || pathLen >= OSM_MAX_PATH_LEN) return false; // too long for buffer snprintf(path, OSM_MAX_PATH_LEN, "%s", pathPtr); From 007c66b64f87bd24dc53f1c6e1e16a5b8bcb7430 Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 29 Sep 2025 11:09:49 +0200 Subject: [PATCH 19/24] Refactor `headerline` from string to char array --- src/ReusableTileFetcher.cpp | 68 +++++++++++++++---------------------- src/ReusableTileFetcher.hpp | 4 +-- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 19919e3..98bc7e9 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -23,7 +23,7 @@ #include "ReusableTileFetcher.hpp" -ReusableTileFetcher::ReusableTileFetcher() { headerLine.reserve(OSM_MAX_HEADERLENGTH); } +ReusableTileFetcher::ReusableTileFetcher() {} ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } void ReusableTileFetcher::sendHttpRequest(const char *host, const char *path) @@ -182,8 +182,6 @@ bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose) { - String &line = headerLine; - line = ""; contentLength = 0; bool start = true; connectionClose = false; @@ -193,41 +191,36 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t while ((currentIsTLS ? secureClient.connected() : client.connected())) { - if (!readLineWithTimeout(line, headerTimeout)) + if (!readLineWithTimeout(headerLine, sizeof(headerLine), headerTimeout)) { result = "Header timeout"; return false; } - log_d("read header: %s", line.c_str()); + log_d("read header: %s", headerLine); if (start) { - if (!line.startsWith("HTTP/1.")) + if (strncmp(headerLine, "HTTP/1.", 7) != 0) { - result = "Bad HTTP response: " + line; + result = String("Bad HTTP response: ") + headerLine; return false; } + // parse status code int statusCode = 0; const char *reasonPhrase = ""; - - const char *buf = line.c_str(); - const char *sp1 = strchr(buf, ' '); + const char *sp1 = strchr(headerLine, ' '); if (sp1) { - // parse up to 3 digits after first space const char *p = sp1 + 1; while (*p && isspace((unsigned char)*p)) - p++; // skip extra spaces - + p++; while (*p && isdigit((unsigned char)*p)) { statusCode = statusCode * 10 + (*p - '0'); p++; } - - // skip a single space to get to reason phrase if (*p == ' ') reasonPhrase = p + 1; } @@ -247,38 +240,28 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t start = false; } - if (line.length() == 0) + if (headerLine[0] == '\0') // empty line = end of headers break; - // start parsing - static constexpr const char CONTENT_LENGTH[] = "content-length:"; - static constexpr size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1; - - static constexpr const char CONTENT_TYPE[] = "content-type:"; - static constexpr size_t CONTENT_TYPE_LEN = sizeof(CONTENT_TYPE) - 1; - - static constexpr const char CONNECTION[] = "connection:"; - static constexpr size_t CONNECTION_LEN = sizeof(CONNECTION) - 1; - - const char *raw = line.c_str(); - if (strncasecmp(raw, CONTENT_LENGTH, CONTENT_LENGTH_LEN) == 0) + // parse headers + if (strncasecmp(headerLine, "content-length:", 15) == 0) { - const char *val = raw + CONTENT_LENGTH_LEN; + const char *val = headerLine + 15; while (*val == ' ' || *val == '\t') - val++; // trim left + val++; contentLength = atoi(val); } - else if (strncasecmp(raw, CONNECTION, CONNECTION_LEN) == 0) + else if (strncasecmp(headerLine, "connection:", 11) == 0) { - const char *val = raw + CONNECTION_LEN; + const char *val = headerLine + 11; while (*val == ' ' || *val == '\t') val++; if (strcasecmp(val, "close") == 0) connectionClose = true; } - else if (strncasecmp(raw, CONTENT_TYPE, CONTENT_TYPE_LEN) == 0) + else if (strncasecmp(headerLine, "content-type:", 13) == 0) { - const char *val = raw + CONTENT_TYPE_LEN; + const char *val = headerLine + 13; while (*val == ' ' || *val == '\t') val++; if (strcasecmp(val, "image/png") == 0) @@ -341,27 +324,32 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u return true; } -bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) +bool ReusableTileFetcher::readLineWithTimeout(char *lineBuf, size_t bufSize, uint32_t timeoutMs) { - line = ""; + size_t len = 0; const uint32_t start = millis(); while ((millis() - start) < timeoutMs) { - int availableData = (currentIsTLS ? secureClient.available() : client.available()); + int availableData = currentIsTLS ? secureClient.available() : client.available(); if (availableData) { - const char c = (currentIsTLS ? secureClient.read() : client.read()); + char c = currentIsTLS ? secureClient.read() : client.read(); if (c == '\r') continue; if (c == '\n') + { + lineBuf[len] = '\0'; // terminate return true; - if (line.length() >= OSM_MAX_HEADERLENGTH - 1) + } + if (len >= bufSize - 1) // prevent overflow return false; - line += c; + lineBuf[len++] = c; } else + { taskYIELD(); + } } return false; // Timed out } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 583384a..3e82682 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -50,13 +50,13 @@ class ReusableTileFetcher WiFiClientSecure secureClient; bool currentIsTLS = false; char currentHost[OSM_MAX_HOST_LEN] = {0}; + char headerLine[OSM_MAX_HEADERLENGTH] = {0}; uint16_t currentPort = 0; - String headerLine; bool parseUrl(const char *url, char *host, char *path, uint16_t &port, bool &useTLS); bool ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const char *host, const char *path); bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); - bool readLineWithTimeout(String &line, uint32_t timeoutMs); + bool readLineWithTimeout(char *lineBuf, size_t bufSize, uint32_t timeoutMs); }; From 6709b133e7f00299641759723290aa5ed50aa372 Mon Sep 17 00:00:00 2001 From: Cellie Date: Mon, 29 Sep 2025 11:53:41 +0200 Subject: [PATCH 20/24] Cleanup --- src/ReusableTileFetcher.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 98bc7e9..2d15653 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -160,7 +160,8 @@ bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool secureClient.setInsecure(); if (!secureClient.connect(host, port, connectTimeout)) { - result = "TLS connect failed to " + String(host); + result = "TLS connect failed to "; + result += host; return false; } currentIsTLS = true; @@ -169,7 +170,8 @@ bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool { if (!client.connect(host, port, connectTimeout)) { - result = "TCP connect failed to " + String(host); + result = "TCP connect failed to "; + result += host; return false; } currentIsTLS = false; @@ -203,7 +205,8 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t { if (strncmp(headerLine, "HTTP/1.", 7) != 0) { - result = String("Bad HTTP response: ") + headerLine; + result = "Bad HTTP response: "; + result += headerLine; return false; } @@ -227,7 +230,8 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t if (statusCode != 200) { - result = "HTTP error " + String(statusCode); + result = "HTTP error "; + result += statusCode; if (*reasonPhrase) { result += " ("; @@ -298,7 +302,9 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u { if (millis() - lastReadTime >= maxStall) { - result = "Timeout: body read stalled for " + String(maxStall) + " ms"; + result = "Timeout: body read stalled for "; + result += maxStall; + result += " ms"; disconnect(); return false; } From c6fb08909a5e6f34e17af062afa6978163efb9a5 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 30 Sep 2025 15:48:36 +0200 Subject: [PATCH 21/24] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 931a39e..3264ad6 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,15 @@ OSM tiles are quite large at 128kB or insane large at 512kB per tile, so psram i You can switch provider and tile format at runtime, or set up a different default tile provider if you want. This library can do it all and is very easy to configure and use. +### TLS validation note + +This project currently uses `setInsecure()` for `WiFiClientSecure` connections. +This means server certificates are **not** validated. The trade-offs are: + +- ❌ No TLS validation → potential API key leakage or manipulated tiles (low risk in practice). +- ✅ Much simpler setup → supports multiple tileservers without managing CA bundles. + + ## How to use This library is **PlatformIO only** due to use of modern C++ features. The Arduino IDE is **not** supported. From 78c4782efddaa94b5155623e3361d160995e2e73 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 30 Sep 2025 15:49:51 +0200 Subject: [PATCH 22/24] Skip too long headers --- src/OpenStreetMap-esp32.cpp | 4 ++++ src/ReusableTileFetcher.cpp | 46 ++++++++++++++++++++++++++++--------- src/ReusableTileFetcher.hpp | 4 ++-- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 718e177..63fe3ca 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -378,6 +378,8 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui if (!buffer.isAllocated()) return false; + [[maybe_unused]] const unsigned long startMS = millis(); + PNG *png = getPNGCurrentCore(); const int16_t rc = png->openRAM(buffer.get(), buffer.size(), PNGDraw); if (rc != PNG_SUCCESS) @@ -401,6 +403,8 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui return false; } + log_d("decoding %s took %lu ms on core %i", url, millis() - startMS, xPortGetCoreID()); + tile.x = x; tile.y = y; tile.z = zoom; diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 2d15653..32ee246 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -56,6 +56,8 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const char *url, String &result, log_d("url: %s", url); + [[maybe_unused]] const unsigned long startMS = millis(); + if (!parseUrl(url, host, path, port, useTLS)) { result = "Invalid URL"; @@ -86,7 +88,7 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const char *url, String &result, auto buffer = MemoryBuffer(contentLength); if (!buffer.isAllocated()) { - result = "Buffer allocation failed"; + result = "Download buffer allocation failed"; disconnect(); return MemoryBuffer::empty(); } @@ -97,6 +99,8 @@ MemoryBuffer ReusableTileFetcher::fetchToBuffer(const char *url, String &result, return MemoryBuffer::empty(); } + log_d("fetching %s took %lu ms", url, millis() - startMS); + // Server requested connection close → drop it if (connClose) disconnect(); @@ -193,7 +197,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t while ((currentIsTLS ? secureClient.connected() : client.connected())) { - if (!readLineWithTimeout(headerLine, sizeof(headerLine), headerTimeout)) + if (!readLineWithTimeout(headerTimeout)) { result = "Header timeout"; return false; @@ -330,10 +334,11 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, u return true; } -bool ReusableTileFetcher::readLineWithTimeout(char *lineBuf, size_t bufSize, uint32_t timeoutMs) +bool ReusableTileFetcher::readLineWithTimeout(uint32_t timeoutMs) { size_t len = 0; const uint32_t start = millis(); + bool skipping = false; while ((millis() - start) < timeoutMs) { @@ -343,19 +348,38 @@ bool ReusableTileFetcher::readLineWithTimeout(char *lineBuf, size_t bufSize, uin char c = currentIsTLS ? secureClient.read() : client.read(); if (c == '\r') continue; + if (c == '\n') { - lineBuf[len] = '\0'; // terminate - return true; + if (skipping) + { + // We were discarding an oversized line → reset and keep going + len = 0; + skipping = false; + continue; // stay in loop, keep reading next line + } + + headerLine[len] = '\0'; + return true; // got a usable line + } + + if (!skipping) + { + if (len < sizeof(headerLine) - 1) + { + headerLine[len++] = c; + } + else + { + // buffer too small → switch to skipping mode + skipping = true; + len = 0; // clear partial junk + } } - if (len >= bufSize - 1) // prevent overflow - return false; - lineBuf[len++] = c; } else - { taskYIELD(); - } } - return false; // Timed out + + return false; // timeout } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 3e82682..9f1c7c8 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -28,7 +28,7 @@ #include #include "MemoryBuffer.hpp" -constexpr int OSM_MAX_HEADERLENGTH = 256; +constexpr int OSM_MAX_HEADERLENGTH = 64; constexpr int OSM_MAX_HOST_LEN = 128; constexpr int OSM_MAX_PATH_LEN = 128; constexpr int OSM_DEFAULT_TIMEOUT_MS = 5000; @@ -57,6 +57,6 @@ class ReusableTileFetcher bool ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); void sendHttpRequest(const char *host, const char *path); bool readHttpHeaders(size_t &contentLength, unsigned long timeoutMS, String &result, bool &connectionClose); + bool readLineWithTimeout(uint32_t timeoutMs); bool readBody(MemoryBuffer &buffer, size_t contentLength, unsigned long timeoutMS, String &result); - bool readLineWithTimeout(char *lineBuf, size_t bufSize, uint32_t timeoutMs); }; From e918e5d4a22f7eaa292bf0aa5271510edd81ecae Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 1 Oct 2025 13:53:37 +0200 Subject: [PATCH 23/24] Set up sockets --- src/ReusableTileFetcher.cpp | 10 +++++++++- src/ReusableTileFetcher.hpp | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 32ee246..cfab51a 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -146,6 +146,12 @@ bool ReusableTileFetcher::parseUrl(const char *url, char *host, char *path, uint return true; } +void ReusableTileFetcher::setSocket(WiFiClient &c) +{ + c.setNoDelay(true); + c.setTimeout(OSM_DEFAULT_TIMEOUT_MS); +} + bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result) { // If we already have a connection to exact host/port/scheme and it's connected, keep it. @@ -168,6 +174,7 @@ bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool result += host; return false; } + setSocket(secureClient); currentIsTLS = true; } else @@ -178,6 +185,7 @@ bool ReusableTileFetcher::ensureConnection(const char *host, uint16_t port, bool result += host; return false; } + setSocket(client); currentIsTLS = false; } snprintf(currentHost, sizeof(currentHost), "%s", host); @@ -199,7 +207,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, unsigned long t { if (!readLineWithTimeout(headerTimeout)) { - result = "Header timeout"; + result = "Header error or timeout"; return false; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 9f1c7c8..6cffb07 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -52,6 +52,7 @@ class ReusableTileFetcher char currentHost[OSM_MAX_HOST_LEN] = {0}; char headerLine[OSM_MAX_HEADERLENGTH] = {0}; uint16_t currentPort = 0; + void setSocket(WiFiClient &c); bool parseUrl(const char *url, char *host, char *path, uint16_t &port, bool &useTLS); bool ensureConnection(const char *host, uint16_t port, bool useTLS, unsigned long timeoutMS, String &result); From be6e2507a6169942d653219dd3e6246843d0a3f2 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 1 Oct 2025 14:03:22 +0200 Subject: [PATCH 24/24] Update README.md --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3264ad6..2b1d0a6 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,15 @@ This library can do it all and is very easy to configure and use. ### TLS validation note -This project currently uses `setInsecure()` for `WiFiClientSecure` connections. -This means server certificates are **not** validated. The trade-offs are: +This project currently uses `setInsecure()` for `WiFiClientSecure` connections, which disables certificate validation. -- ❌ No TLS validation → potential API key leakage or manipulated tiles (low risk in practice). -- ✅ Much simpler setup → supports multiple tileservers without managing CA bundles. +- Risk: Without TLS validation, responses could in theory be intercepted or altered. +This matters most if requests carry secrets (e.g. API keys). +- Practical impact: Standard OpenStreetMap tile servers do not require API keys or credentials. +In this case, the main risk is limited to someone tampering with map images. + +- Benefit: Simplifies setup and supports multiple tile providers without needing to manage CA certificates. ## How to use