From 6130e8cd7153d14fb7085dff8e7fa5f3a8a6d9be Mon Sep 17 00:00:00 2001 From: Th3maz1ng Date: Sat, 23 Apr 2022 22:48:26 +0200 Subject: [PATCH] Reworked the way the main send buffer buffer is allocated. This is not done on the stack anymore because it led to a nasty stackoverflow. Changed some debug statements, finished the sendDirectoryListing method which is now working fine. Txt files are now sent as text/plain data. --- src/app/WEBServer.h | 136 +++++++++++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 34 deletions(-) diff --git a/src/app/WEBServer.h b/src/app/WEBServer.h index 0602110..42e836c 100644 --- a/src/app/WEBServer.h +++ b/src/app/WEBServer.h @@ -7,7 +7,7 @@ #include "HttpConstants.h" #include "utilities.h" //#define DEBUG_WEBS -#define READ_WRITE_BUFFER_SIZE 2000 +#define READ_WRITE_BUFFER_SIZE 5000 template class WEBServer : public TCPServer, public HttpConstants @@ -562,8 +562,7 @@ class WEBServer : public TCPServer, public HttpConstants { File pageToSend; char *filePath(NULL), *header(NULL); - uint8_t sendBuffer[READ_WRITE_BUFFER_SIZE]; - int readBytes(0); + size_t readBytes(0); //We check what kind of http verb it is switch(client->_httpRequestData.HRM) { @@ -576,8 +575,7 @@ class WEBServer : public TCPServer, public HttpConstants } #ifdef DEBUG_WEBS - Serial.print("FILE PATH : "); - Serial.println(filePath); + Serial.printf("File path : #%s#\n",filePath); #endif pageToSend = _sdClass->open(filePath); @@ -588,7 +586,7 @@ class WEBServer : public TCPServer, public HttpConstants { char *response(NULL); - response = (char *) malloc(sizeof(char) * (strlen_P((PGM_P)F("Resource : not found on this server")) + strlen(client->_httpRequestData.httpResource) + 1)); + response = (char *) malloc(sizeof(char) * (36 + strlen(client->_httpRequestData.httpResource) + 1)); if(response == NULL) { sendInfoResponse(HTTP_CODE::HTTP_CODE_INTERNAL_SERVER_ERROR, client, "Failed to allocate memory for the response"); @@ -611,10 +609,28 @@ class WEBServer : public TCPServer, public HttpConstants if(pageToSend.isDirectory()) //TODO : List the files present in the directory { - //sendDirectoryListing(client, pageToSend); //Sends the content of the directory like what apache does by default. CRASHING FOR NOW, needs to be checked further. - pageToSend.close(); + //If the ressource wasn't terminated by a '/' then we issue a 301 Moved Permanently, else all's good + if(client->_httpRequestData.httpResource[strlen(client->_httpRequestData.httpResource)-1] != '/') + { + uint16_t locationLength(strlen(client->_httpRequestData.httpResource) + 7/*http://*/ + 16/*ip addr + :*/ + 5/*port*/ + 2); + char *location = (char *)malloc(locationLength * sizeof(char)); + char *message = (char *)malloc((27 + locationLength + 1) * sizeof(char)); + if(!location || !message) + { + sendInfoResponse(HTTP_CODE::HTTP_CODE_INTERNAL_SERVER_ERROR, client, "Failed to allocate memory for the location or message"); + pageToSend.close(); + return false; + } + sprintf(location, "http://%s:%u%s/",client->_client.localIP().toString().c_str(), TCPServer::getPort(), client->_httpRequestData.httpResource); + sprintf(message, "The document has moved to %s.", location); + sendInfoResponse(HTTP_CODE::HTTP_CODE_MOVED_PERMANENTLY, client, message, location); + + free(location);free(message); + } + else + sendDirectoryListing(client, pageToSend); //Sends the content of the directory like what apache does by default. - sendInfoResponse(HTTP_CODE::HTTP_CODE_FORBIDDEN, client, "The file you want to access is a folder"); + pageToSend.close(); return false; } @@ -667,14 +683,34 @@ class WEBServer : public TCPServer, public HttpConstants if(pageToSend.available() && client->_fileSentBytes < client->_rangeData._rangeEnd + 1) // File is done sending once the whole file was sent or the partial part is done sending { - readBytes = pageToSend.read(sendBuffer,READ_WRITE_BUFFER_SIZE); + size_t mallocAcceptedSize(0); + /* + The original issue was that a buffer of 2048 bytes was allocated on the stack causing a hard to track down stack overflow. + Possible solutions I cam up with : + 1) Create a statically allocated buffer which would live in the BSS (Block Started by a Symbol) RAM segment. + Advantage : buffer is allocated once and ready to be used immediately. + Drawback : takes space in RAM (lost space) even if the buffer isn't used. + 2) Create a dynamically allocated buffer using malloc and friends which would live in the heap RAM segment - SOLUTION I IMPLEMENTED + Advantage : buffer is taking RAM only when needed and can be freed afterwards. + Drawback : Allocating and deallocating heap memory a lot is costly in MCU time, leads to RAM fragmentation and could potentially fail... + */ + uint8_t *sendBuffer = (uint8_t*)mallocWithFallback(READ_WRITE_BUFFER_SIZE * sizeof(uint8_t), &mallocAcceptedSize); + + if(!sendBuffer) + { + pageToSend.close(); + return false; //Not clean but what else can I do. Should never happen anyway. + } + + readBytes = pageToSend.read(sendBuffer,mallocAcceptedSize); client->_client.write(sendBuffer, readBytes); - + + free(sendBuffer); + #ifdef DEBUG_WEBS - Serial.print("BYTES SENT : "); - Serial.println(readBytes); + Serial.printf("Bytes sent : %u, got allocated buffer size : %u - free stack : %u\n", readBytes, mallocAcceptedSize, ESP.getFreeContStack()); #endif - + client->_fileSentBytes += readBytes; //We save the number of bytes sent so that we can reopen the file to this position later on. } else @@ -702,7 +738,7 @@ class WEBServer : public TCPServer, public HttpConstants void sendDirectoryListing(T *client, File& pageToSend) { - /*sendHTTPHeader(&client->_client, HttpConstants::httpMIMETypeToString(HttpConstants::TEXT_HTML)); + sendHTTPHeader(&client->_client, HttpConstants::httpMIMETypeToString(HttpConstants::TEXT_HTML)); client->_client.printf_P(PSTR( "\r\n\ \r\n\ \r\n\ @@ -711,41 +747,66 @@ class WEBServer : public TCPServer, public HttpConstants \r\n\

Index of %s

\r\n\ \r\n\ - \r\n\ - \r\n") - , client->_httpRequestData.httpResource, client->_httpRequestData.httpResource);*/ - //File nextFile; - for (;;) - { - //if(!(nextFile = pageToSend.openNextFile()))break; - File nextFile = pageToSend.openNextFile(); - if (!nextFile) //No more files in the directory - break; - //time_t creationTime = nextFile.getCreationTime(), lastModified = nextFile.getLastWrite(); + \r\n\ + \r\n") + , client->_httpRequestData.httpResource, client->_httpRequestData.httpResource); + + if(strlen(client->_httpRequestData.httpResource) > 1) //Then we are not at the root of the WEBServer's directory. + { + char *rsrcCopy = strdup(client->_httpRequestData.httpResource); + if(rsrcCopy) + { + char *lastSlash(strrchr(rsrcCopy, '/')); + if(lastSlash)*lastSlash = '\0'; + lastSlash = strrchr(rsrcCopy, '/'); + if(lastSlash)*lastSlash = '\0'; - /*client->_client.printf_P(PSTR("\r\n"), + client->_client.printf_P(PSTR("\r\n"), rsrcCopy); + free(rsrcCopy); + } + } + + File nextFile; + for(;;) + { + if(!(nextFile = pageToSend.openNextFile()))break; + + char zero_prepended[4][3] = {"","","",""}; + time_t rawCreationTime(nextFile.getCreationTime()), rawLastModifiedTime(nextFile.getLastWrite()); + tm creationTime(*localtime(&rawCreationTime)), lastModifiedTime(*localtime(&rawLastModifiedTime)); + + client->_client.printf_P(PSTR("\r\n"), + nextFile.isDirectory() ? "[DIR]":"[FILE]", nextFile.name(), nextFile.name(), - nextFile.size() - );*/ - Serial.printf("%s %u\r\n", nextFile.name(), nextFile.size()); + creationTime.tm_year + 1900, creationTime.tm_mon + 1, creationTime.tm_mday, dateTimeFormater(zero_prepended[0], creationTime.tm_hour, '0'), dateTimeFormater(zero_prepended[1], creationTime.tm_min, '0'), + lastModifiedTime.tm_year + 1900, lastModifiedTime.tm_mon + 1, lastModifiedTime.tm_mday, dateTimeFormater(zero_prepended[2], lastModifiedTime.tm_hour, '0'), dateTimeFormater(zero_prepended[3], lastModifiedTime.tm_min, '0'), + nextFile.size() / 1024.0 + ); + + #ifdef DEBUG_WEBS + Serial.printf("File name : %s\nFile size : %u\nFree stack : %u\n", nextFile.name(), nextFile.size(), ESP.getFreeContStack()); + #endif nextFile.close(); } - /*client->_client.printf_P(PSTR( "\r\n\ + client->_client.printf_P(PSTR( "\r\n\
NameCreatedLast modifiedSize

TypeNameCreatedLast modifiedSize

%s2021-07-09 15:37 2021-07-09 15:37 %u
[DIR]Parent Directory - -
%s%s%d-%d-%d %s:%s%d-%d-%d %s:%s%.1fK


\r\n\
SAB WEBServer, Version %s at %s Port %u
\r\n\ \r\n\ - "), "1.0.0", "", TCPServer::getPort()); //TODO get IP*/ + "), "1.0.0", client->_client.localIP().toString().c_str(), TCPServer::getPort()); } /*Static helper methods*/ - static void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message) + static void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message, const char *location = nullptr) { char codeLiteral[100]; switch(http_code) { + case HTTP_CODE_MOVED_PERMANENTLY: + strcpy_P(codeLiteral,PSTR("Moved Permanently")); + break; case HTTP_CODE_BAD_REQUEST: strcpy_P(codeLiteral,PSTR("Bad Request")); break; @@ -768,7 +829,13 @@ class WEBServer : public TCPServer, public HttpConstants strcpy_P(codeLiteral,PSTR("Error Not Defined")); break; } - client->_client.printf_P(PSTR("HTTP/1.1 %d %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n\r\n\r\n

Error %d

%s

\r\n"), http_code, codeLiteral, strlen(message) + 56 + (http_code != 0 ? 3:1), http_code , message); + client->_client.printf_P(PSTR("HTTP/1.1 %d %s\r\n"), http_code, codeLiteral); + + + if(http_code == HTTP_CODE_MOVED_PERMANENTLY) + client->_client.printf_P(PSTR("Location: %s\r\n"), location); + + client->_client.printf_P(PSTR("Content-Type: text/html\r\nContent-Length: %d\r\n\r\n\r\n\r\n

Error %d

%s

\r\n"), strlen(message) + 56 + (http_code != 0 ? 3:1), http_code , message); } static HttpRequestMethod getHttpVerbEnumValue(const char *parseBuffer) @@ -802,6 +869,7 @@ class WEBServer : public TCPServer, public HttpConstants else if(strcmp(extension, "jpg") == 0) return IMAGE_JPEG; else if(strcmp(extension, "ico") == 0) return IMAGE_X_ICO; else if(strcmp(extension, "mp3") == 0) return AUDIO_MPEG; + else if(strcmp(extension, "txt") == 0) return TEXT_PLAIN; else return UNKNOWN_MIME; }