From 233dad0e4e1979fd1576914deabf84bdae7ded20 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Thu, 26 Dec 2019 15:51:22 +0100 Subject: [PATCH 1/2] Reduce code duplications in _parseRequest This function had two loops for parsing headers, one used for GET like requests and one for POST like request. we can combine them in one, which reduces PROGMEM footprint by over 250 bytes. --- libraries/ESP8266WebServer/src/Parsing-impl.h | 331 ++++++++---------- 1 file changed, 149 insertions(+), 182 deletions(-) diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h index 6c96d564a8..91f7f9be16 100644 --- a/libraries/ESP8266WebServer/src/Parsing-impl.h +++ b/libraries/ESP8266WebServer/src/Parsing-impl.h @@ -37,7 +37,6 @@ #endif static const char Content_Type[] PROGMEM = "Content-Type"; -static const char filename[] PROGMEM = "filename"; template static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms) @@ -62,216 +61,183 @@ static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t template bool ESP8266WebServerTemplate::_parseRequest(ClientType& client) { - // Read the first line of HTTP request - String req = client.readStringUntil('\r'); + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("request: "); DEBUG_OUTPUT.println(req); #endif - client.readStringUntil('\n'); - //reset header value - for (int i = 0; i < _headerKeysCount; ++i) { - _currentHeaders[i].value =String(); - } - - // First line of HTTP request looks like "GET /path HTTP/1.1" - // Retrieve the "/path" part by finding the spaces - int addr_start = req.indexOf(' '); - int addr_end = req.indexOf(' ', addr_start + 1); - if (addr_start == -1 || addr_end == -1) { + client.readStringUntil('\n'); + //reset header value + for (size_t i = 0; i < _headerKeysCount; ++i) { + _currentHeaders[i].value.clear(); + } + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Invalid request"); + DEBUG_OUTPUT.println("Invalid request"); #endif - return false; - } + return false; + } - String methodStr = req.substring(0, addr_start); - String url = req.substring(addr_start + 1, addr_end); - String versionEnd = req.substring(addr_end + 8); - _currentVersion = atoi(versionEnd.c_str()); - String searchStr; - int hasSearch = url.indexOf('?'); - if (hasSearch != -1){ - searchStr = url.substring(hasSearch + 1); - url = url.substring(0, hasSearch); - } - _currentUri = url; - _chunked = false; - - HTTPMethod method = HTTP_GET; - if (methodStr == F("HEAD")) { - method = HTTP_HEAD; - } else if (methodStr == F("POST")) { - method = HTTP_POST; - } else if (methodStr == F("DELETE")) { - method = HTTP_DELETE; - } else if (methodStr == F("OPTIONS")) { - method = HTTP_OPTIONS; - } else if (methodStr == F("PUT")) { - method = HTTP_PUT; - } else if (methodStr == F("PATCH")) { - method = HTTP_PATCH; - } - _currentMethod = method; + String methodStr = req.substring(0, addr_start); + String url = req.substring(addr_start + 1, addr_end); + _currentVersion = req.substring(addr_end + 8).toInt(); + String searchStr; + int hasSearch = url.indexOf('?'); + if (hasSearch != -1) { + searchStr = url.substring(hasSearch + 1); + url = url.substring(0, hasSearch); + } + _currentUri = url; + _chunked = false; + + HTTPMethod method = HTTP_GET; + if (methodStr == F("HEAD")) { + method = HTTP_HEAD; + } else if (methodStr == F("POST")) { + method = HTTP_POST; + } else if (methodStr == F("DELETE")) { + method = HTTP_DELETE; + } else if (methodStr == F("OPTIONS")) { + method = HTTP_OPTIONS; + } else if (methodStr == F("PUT")) { + method = HTTP_PUT; + } else if (methodStr == F("PATCH")) { + method = HTTP_PATCH; + } + _currentMethod = method; #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("method: "); - DEBUG_OUTPUT.print(methodStr); - DEBUG_OUTPUT.print(" url: "); - DEBUG_OUTPUT.print(url); - DEBUG_OUTPUT.print(" search: "); - DEBUG_OUTPUT.println(searchStr); + DEBUG_OUTPUT.print("method: "); + DEBUG_OUTPUT.print(methodStr); + DEBUG_OUTPUT.print(" url: "); + DEBUG_OUTPUT.print(url); + DEBUG_OUTPUT.print(" search: "); + DEBUG_OUTPUT.println(searchStr); #endif - //attach handler - RequestHandlerType* handler; - for (handler = _firstHandler; handler; handler = handler->next()) { - if (handler->canHandle(_currentMethod, _currentUri)) - break; - } - _currentHandler = handler; + //attach handler + RequestHandlerType* handler; + for (handler = _firstHandler; handler; handler = handler->next()) { + if (handler->canHandle(_currentMethod, _currentUri)) + break; + } + _currentHandler = handler; - String formData; - // below is needed only when POST type request - if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ - String boundaryStr; String headerName; String headerValue; + String boundaryStr; bool isForm = false; bool isEncoded = false; uint32_t contentLength = 0; - //parse headers - while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (req.isEmpty()) break;//no moar headers - int headerDiv = req.indexOf(':'); - if (headerDiv == -1){ - break; - } - headerName = req.substring(0, headerDiv); - headerValue = req.substring(headerDiv + 1); - headerValue.trim(); - _collectHeader(headerName.c_str(),headerValue.c_str()); - - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("headerName: "); - DEBUG_OUTPUT.println(headerName); - DEBUG_OUTPUT.print("headerValue: "); - DEBUG_OUTPUT.println(headerValue); - #endif - - if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ - using namespace mime; - if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ - isForm = false; - } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ - isForm = false; - isEncoded = true; - } else if (headerValue.startsWith(F("multipart/"))){ - boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); - boundaryStr.replace("\"",""); - isForm = true; - } - } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ - contentLength = headerValue.toInt(); - } else if (headerName.equalsIgnoreCase(F("Host"))){ - _hostHeader = headerValue; - } - } + // parse headers + while (true) { + req = client.readStringUntil('\r'); + client.readStringUntil('\n'); - String plainBuf; - if ( !isForm - && // read content into plainBuf - ( !readBytesWithTimeout(client, contentLength, plainBuf, HTTP_MAX_POST_WAIT) - || (plainBuf.length() < contentLength) - ) - ) - { - return false; - } + int headerDiv = req.indexOf(':'); + if (headerDiv < 0) { + break; + } + headerName = req.substring(0, headerDiv); + headerValue = req.substring(headerDiv + 1); + headerValue.trim(); + _collectHeader(headerName, headerValue); - if (isEncoded) { - // isEncoded => !isForm => plainBuf is not empty - // add plainBuf in search str - if (searchStr.length()) - searchStr += '&'; - searchStr += plainBuf; +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print(F("headerName: ")); + DEBUG_OUTPUT.println(headerName); + DEBUG_OUTPUT.print(F("headerValue: ")); + DEBUG_OUTPUT.println(headerValue); +#endif + if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) { + if (headerValue.startsWith(FPSTR(::mime::mimeTable[::mime::txt].mimeType))) { + isForm = false; + } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) { + isForm = false; + isEncoded = true; + } else if (headerValue.startsWith(F("multipart/"))) { + boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); + boundaryStr.replace("\"", ""); + isForm = true; + } + } else if (headerName.equalsIgnoreCase(F("Content-Length"))) { + contentLength = headerValue.toInt(); + } else if (headerName.equalsIgnoreCase(F("Host"))) { + _hostHeader = headerValue; + } } - - // parse searchStr for key/value pairs _parseArguments(searchStr); - if (!isForm) { - if (contentLength) { - // add key=value: plain={body} (post json or other data) - RequestArgument& arg = _currentArgs[_currentArgCount++]; - arg.key = F("plain"); - arg.value = plainBuf; - _currentArgsHavePlain = 1; - } - } else { // isForm is true - // here: content is not yet read (plainBuf is still empty) - if (!_parseForm(client, boundaryStr, contentLength)) { - return false; - } - } - } else { - String headerName; - String headerValue; - //parse headers - while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (req.isEmpty()) break;//no moar headers - int headerDiv = req.indexOf(':'); - if (headerDiv == -1){ - break; - } - headerName = req.substring(0, headerDiv); - headerValue = req.substring(headerDiv + 2); - _collectHeader(headerName.c_str(),headerValue.c_str()); + // below is needed only when POST type request + if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) { + String plainBuf; + if (!isForm && + // read content into plainBuf + (!readBytesWithTimeout(client, contentLength, plainBuf, HTTP_MAX_POST_WAIT) + || (plainBuf.length() < contentLength))) + { + return false; + } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print(F("headerName: ")); - DEBUG_OUTPUT.println(headerName); - DEBUG_OUTPUT.print(F("headerValue: ")); - DEBUG_OUTPUT.println(headerValue); -#endif + if (isEncoded) { + // isEncoded => !isForm => plainBuf is not empty + // add plainBuf in search str + if (searchStr.length()) + searchStr += '&'; + searchStr += plainBuf; + } - if (headerName.equalsIgnoreCase(F("Host"))){ - _hostHeader = headerValue; - } + // parse searchStr for key/value pairs + _parseArguments(searchStr); + + if (!isForm) { + if (contentLength) { + // add key=value: plain={body} (post json or other data) + RequestArgument& arg = _currentArgs[_currentArgCount++]; + arg.key = F("plain"); + arg.value = plainBuf; + _currentArgsHavePlain = 1; + } + } else { // isForm is true + // here: content is not yet read (plainBuf is still empty) + if (!_parseForm(client, boundaryStr, contentLength)) { + return false; + } + } } - _parseArguments(searchStr); - } - client.flush(); + client.flush(); #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print(F("Request: ")); - DEBUG_OUTPUT.println(url); - DEBUG_OUTPUT.print(F("Arguments: ")); - DEBUG_OUTPUT.println(searchStr); - - DEBUG_OUTPUT.println(F("final list of key/value pairs:")); - for (int i = 0; i < _currentArgCount; i++) - DEBUG_OUTPUT.printf(" key:'%s' value:'%s'\r\n", - _currentArgs[i].key.c_str(), - _currentArgs[i].value.c_str()); + DEBUG_OUTPUT.print(F("Request: ")); + DEBUG_OUTPUT.println(url); + DEBUG_OUTPUT.print(F("Arguments: ")); + DEBUG_OUTPUT.println(searchStr); + + DEBUG_OUTPUT.println(F("final list of key/value pairs:")); + for (int i = 0; i < _currentArgCount; i++) + DEBUG_OUTPUT.printf(" key:'%s' value:'%s'\r\n", + _currentArgs[i].key.c_str(), + _currentArgs[i].value.c_str()); #endif - return true; + return true; } template -bool ESP8266WebServerTemplate::_collectHeader(const char* headerName, const char* headerValue) { - for (int i = 0; i < _headerKeysCount; i++) { - if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { - _currentHeaders[i].value=headerValue; +bool ESP8266WebServerTemplate::_collectHeader(const String& headerName, const String& headerValue) { + for (int i = 0; i < _headerKeysCount; i++) { + if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { + _currentHeaders[i].value = headerValue; return true; } - } - return false; + } + return false; } template @@ -359,9 +325,9 @@ int ESP8266WebServerTemplate::_parseArgumentsPrivate(const String& d } template -void ESP8266WebServerTemplate::_uploadWriteByte(uint8_t b){ - if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){ - if(_currentHandler && _currentHandler->canUpload(_currentUri)) +void ESP8266WebServerTemplate::_uploadWriteByte(uint8_t b) { + if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) { + if (_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->totalSize += _currentUpload->currentSize; _currentUpload->currentSize = 0; @@ -427,8 +393,9 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const DEBUG_OUTPUT.println(argFilename); #endif //use GET to set the filename if uploading using blob - if (argFilename == F("blob") && hasArg(FPSTR(filename))) - argFilename = arg(FPSTR(filename)); + String filenameArg = arg(F("filename")); + if (argFilename == F("blob") && !filenameArg.isEmpty()) + argFilename = filenameArg; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Name: "); From 7a3426fcbcc28efdf98f1659604a880a94f6128c Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Thu, 2 Jan 2020 10:35:10 +0100 Subject: [PATCH 2/2] Add deprecated wrapper/forwarder for _collectHeader --- libraries/ESP8266WebServer/src/ESP8266WebServer.h | 3 ++- libraries/ESP8266WebServer/src/Parsing-impl.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 85eab1b7df..8986c8941d 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -204,7 +204,8 @@ class ESP8266WebServerTemplate void _uploadWriteByte(uint8_t b); uint8_t _uploadReadByte(ClientType& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); - bool _collectHeader(const char* headerName, const char* headerValue); + bool _collectHeader(const char* headerName, const char* headerValue) __attribute__((deprecated)); + bool _collectHeader(const String& headerName, const String& headerValue); void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h index 91f7f9be16..fa63d90c4d 100644 --- a/libraries/ESP8266WebServer/src/Parsing-impl.h +++ b/libraries/ESP8266WebServer/src/Parsing-impl.h @@ -229,6 +229,11 @@ bool ESP8266WebServerTemplate::_parseRequest(ClientType& client) { return true; } +template +bool ESP8266WebServerTemplate::_collectHeader(const char* headerName, const char* headerValue) { + return _collectHeader(String(headerName), String(headerValue)); +} + template bool ESP8266WebServerTemplate::_collectHeader(const String& headerName, const String& headerValue) { for (int i = 0; i < _headerKeysCount; i++) {