diff --git a/cores/esp8266/Stream.cpp b/cores/esp8266/Stream.cpp index b9b5b95f65..6f34680043 100644 --- a/cores/esp8266/Stream.cpp +++ b/cores/esp8266/Stream.cpp @@ -20,8 +20,9 @@ parsing functions based on TextFinder library by Michael Margolis */ -#include -#include +#include "Arduino.h" +#include "Stream.h" +#include "StreamString.h" #define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait #define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field @@ -254,34 +255,45 @@ String Stream::readString() { String Stream::readStringUntil(char terminator) { String ret; - int c = timedRead(); - while(c >= 0 && c != terminator) { - ret += (char) c; - c = timedRead(); - } + + S2Stream s2s(ret); + sendUntil(s2s, terminator, _timeout); + return ret; } String Stream::readStringUntil(const char* terminator, uint32_t untilTotalNumberOfOccurrences) { String ret; - int c; + if (!untilTotalNumberOfOccurrences) { + return ret; + } + + const size_t termLen = strlen_P(terminator); + if (!termLen) { + return ret; + } + + S2Stream s2s(ret); uint32_t occurrences = 0; - size_t termLen = strlen(terminator); - size_t termIndex = 0; - size_t index = 0; + const size_t tailLen = termLen - 1; - while ((c = timedRead()) > 0) { - ret += (char) c; - index++; + for (;;) { + sendUntil(s2s, terminator[tailLen], _timeout); + if (s2s.getLastSendReport() != Stream::Report::Success) { + break; + } + + if ((ret.length() >= tailLen) + && ((0 == tailLen) || (0 == memcmp_P(terminator, ret.end() - tailLen, tailLen)))) + { + ++occurrences; + } - if (terminator[termIndex] == c) { - if (++termIndex == termLen && ++occurrences == untilTotalNumberOfOccurrences) { - // don't include terminator in returned string - ret.remove(index - termIndex, termLen); - break; + if (untilTotalNumberOfOccurrences == occurrences) { + if (tailLen) { + ret.remove(ret.length() - tailLen); } - } else { - termIndex = 0; + break; } } diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h index 672682706e..1d62d7d5e2 100644 --- a/libraries/ESP8266WebServer/src/Parsing-impl.h +++ b/libraries/ESP8266WebServer/src/Parsing-impl.h @@ -44,9 +44,8 @@ static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t template typename ESP8266WebServerTemplate::ClientFuture ESP8266WebServerTemplate::_parseRequest(ClientType& client) { // Read the first line of HTTP request - String req = client.readStringUntil('\r'); + String req = client.readStringUntil("\r\n"); DBGWS("request: %s\n", req.c_str()); - client.readStringUntil('\n'); //reset header value for (int i = 0; i < _headerKeysCount; ++i) { _currentHeaders[i].value.clear(); @@ -122,8 +121,7 @@ typename ESP8266WebServerTemplate::ClientFuture ESP8266WebServerTemp uint32_t contentLength = 0; //parse headers while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); + req = client.readStringUntil("\r\n"); if (req.isEmpty()) break; //no more headers int headerDiv = req.indexOf(':'); if (headerDiv == -1){ @@ -198,8 +196,7 @@ typename ESP8266WebServerTemplate::ClientFuture ESP8266WebServerTemp String headerValue; //parse headers while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); + req = client.readStringUntil("\r\n"); if (req.isEmpty()) break;//no moar headers int headerDiv = req.indexOf(':'); if (headerDiv == -1){ @@ -351,7 +348,7 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const String line; int retry = 0; do { - line = client.readStringUntil('\r'); + line = client.readStringUntil("\r\n"); ++retry; } while (line.length() == 0 && retry < 3); @@ -367,8 +364,7 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const String argFilename; bool argIsFile = false; - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); + line = client.readStringUntil("\r\n"); if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ int nameStart = line.indexOf('='); if (nameStart != -1){ @@ -388,19 +384,16 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const DBGWS("PostArg Name: %s\n", argName.c_str()); using namespace mime; argType = FPSTR(mimeTable[txt].mimeType); - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); + line = client.readStringUntil("\r\n"); if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ argType = line.substring(line.indexOf(':')+2); //skip next line - client.readStringUntil('\r'); - client.readStringUntil('\n'); + client.readStringUntil("\r\n"); } DBGWS("PostArg Type: %s\n", argType.c_str()); if (!argIsFile){ while(1){ - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); + line = client.readStringUntil("\r\n"); if (line.startsWith("--"+boundary)) break; if (argValue.length() > 0) argValue += '\n'; argValue += line; @@ -474,8 +467,7 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const _currentUpload->type.c_str(), (int)_currentUpload->totalSize); if (!client.connected()) return _parseFormUploadAborted(); - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); + line = client.readStringUntil("\r\n"); if (line == "--") { // extra two dashes mean we reached the end of all form fields DBGWS("Done Parsing POST\n"); break; diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index 92b37e4279..dd20374b16 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -512,6 +512,31 @@ TEST_CASE("Issue #2736 - StreamString SSO fix", "[core][StreamString]") REQUIRE(s == "{\"message\""); } +TEST_CASE("Issue #9005 - StreamString for Stream->String conversion", "[core][StreamString]") +{ + const char buffer[] = + "this is a test string" + "\r\n" + "delimited as if it was a http request" + "\r\n" + "\r\n"; + + StreamString input; + input.print(buffer); + REQUIRE(input == buffer); + + String out = input.readStringUntil("\r\n"); + REQUIRE(21 == out.length()); + REQUIRE(out == "this is a test string"); + + out = input.readStringUntil("\r\n"); + REQUIRE(37 == out.length()); + REQUIRE(out == "delimited as if it was a http request"); + + out = input.readStringUntil("\r\n"); + REQUIRE(0 == out.length()); +} + TEST_CASE("Strings with NULs", "[core][String]") { // The following should never be done in a real app! This is only to inject 0s in the middle of