Renamed the HttpParserStatus enum members for the sake of consistency, added the 414 http error : URI too long, reworked the parsing of http resource queries, removedd the use of lastIndexOf, replaced useless strstr with strchr where needed

This commit is contained in:
Th3maz1ng 2022-04-17 21:41:50 +02:00
parent 5d05bdb144
commit ec7c608dfe

View File

@ -13,7 +13,15 @@ template <typename T>
class WEBServer : public TCPServer<T>, public HttpConstants
{
public:
enum HttpParserStatus {HTTP_VERB, HTTP_RESSOURCE, HTTP_VERSION, HTTP_PARAMS, POST_DATA, HEADER_PARAMS};
enum HttpParserStatus
{
PARSE_HTTP_VERB,
PARSE_HTTP_RESOURCE,
PARSE_HTTP_VERSION,
PARSE_HTTP_RESOURCE_QUERY,
PARSE_HTTP_POST_DATA,
PARSE_HTTP_HEADER_PARAMS
};
enum WEBClientState {ACCEPTED, PARSING, QUERY_PARSED, RESPONSE_SENT, DONE};
struct HttpRequestData
@ -23,7 +31,6 @@ class WEBServer : public TCPServer<T>, public HttpConstants
HttpMIMEType HMT;
Dictionary<DictionaryHelper::StringEntity> getParams;
char *getParamsDataPointer; //Used in the getParams algorithm
Dictionary<DictionaryHelper::StringEntity> postParams;
char *postParamsDataPointer; //Used in the postParams algorithm
@ -33,7 +40,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
uint16_t maxBodyBuffer;
};
WEBServer(uint16_t port = 80, SDClass *sdClass = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 255) : TCPServer<T>(port, maxClient, clientDataBufferSize), _sdClass(sdClass) {}
WEBServer(uint16_t port = 80, SDClass *sdClass = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 256) : TCPServer<T>(port, maxClient, clientDataBufferSize), _sdClass(sdClass) {}
boolean addApiRoutine(const char *uri, boolean (*apiRoutine)(HttpRequestData&, WiFiClient*, void*), void *pData, HttpRequestMethod HRM = UNDEFINED)
{
@ -112,13 +119,13 @@ class WEBServer : public TCPServer<T>, public HttpConstants
{
switch(client->_httpParserState)
{
case HttpParserStatus::HTTP_VERB:
case HttpParserStatus::PARSE_HTTP_VERB:
{
#ifdef DEBUG_WEBS
Serial.println((char *)client->_data);
#endif
char *pVerb = strstr((char *)client->_data, " ");
char *pVerb(strchr((char *)client->_data, ' '));
if(pVerb != NULL)
{
@ -138,7 +145,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
Serial.println((char *)client->_data);
#endif
client->_httpParserState = HttpParserStatus::HTTP_RESSOURCE;
client->_httpParserState = HttpParserStatus::PARSE_HTTP_RESOURCE;
}
else
{
@ -147,24 +154,46 @@ class WEBServer : public TCPServer<T>, public HttpConstants
}
}
break;
case HttpParserStatus::HTTP_RESSOURCE:
case HttpParserStatus::PARSE_HTTP_RESOURCE:
{
char *pRsrc = strstr((char *)client->_data, " ");
if(pRsrc != NULL)
char *pRsrc(strchr((char *)client->_data, ' ')), *pRsrcQuery(strchr((char *)client->_data, '?'));
//The case where we have the resource complete or not complete with query parameters like : GET /some/path/resource.rsrc?param1=one&param2 HTTP/1.1
if(pRsrc || pRsrcQuery)
{
uint16_t rawLengthOfResource(0);
if(pRsrcQuery)
{
*pRsrcQuery = '\0'; // The ? is the end of the resource string
rawLengthOfResource = pRsrcQuery - (char *)client->_data;
#ifdef DEBUG_WEBS
Serial.printf("Resource w/ query\n");
#endif
}
else
{
*pRsrc = '\0';
uint16_t safeLength = pRsrc - (char *)client->_data <= client->_httpRequestData.maxResourceBuffer ? pRsrc - (char *)client->_data : client->_httpRequestData.maxResourceBuffer;
rawLengthOfResource = pRsrc - (char *)client->_data;
#ifdef DEBUG_WEBS
Serial.print("Resrc length : ");Serial.println(safeLength);
Serial.printf("Resource w/o query\n");
#endif
}
client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (safeLength+1) ); //for \0
if(client->_httpRequestData.httpResource != NULL)
if(rawLengthOfResource >= client->_httpRequestData.maxResourceBuffer) //Then we cannot handle the full resource and there is no point of truncating it
//better tell the client about it ...
{
strncpy(client->_httpRequestData.httpResource, (char *)client->_data, safeLength);
client->_httpRequestData.httpResource[safeLength] = '\0';
#ifdef DEBUG_WEBS
Serial.printf("Resource too long\nResource raw length is : %u (\\0 included)\nMax length is : %u (\\0 included)\nclient->_data : #%s#\n", rawLengthOfResource + 1, client->_httpRequestData.maxResourceBuffer, (char *)client->_data);
#endif
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, "Resource too long");
client->_clientState = TCPClient::ClientState::DISCARDED;
break;
}
client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (rawLengthOfResource + 1)); // +1 for the \0
if(client->_httpRequestData.httpResource != nullptr)
{
strncpy(client->_httpRequestData.httpResource, (char *)client->_data, rawLengthOfResource);
client->_httpRequestData.httpResource[rawLengthOfResource] = '\0';
}
else //Error 500
{
@ -173,38 +202,38 @@ class WEBServer : public TCPServer<T>, public HttpConstants
break;
}
client->freeDataBuffer(safeLength + 1);
client->freeDataBuffer(rawLengthOfResource + 1); //+1 to go past the \0, only the query parameters left in the buffer '?' excluded
#ifdef DEBUG_WEBS
Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource);
Serial.println((char *)client->_data);
Serial.printf("Resource length : %u\nRsrc : %s\nclient->_data : #%s#\n",
rawLengthOfResource,
client->_httpRequestData.httpResource,
(char *)client->_data);
#endif
if(pRsrcQuery)
client->_httpParserState = HttpParserStatus::PARSE_HTTP_RESOURCE_QUERY;
else
client->_httpParserState = HttpParserStatus::PARSE_HTTP_VERSION;
}
else //Resource is probably too long, so we truncate it
else //The URL is too long to fit in the buffer, we dont know it's length nor we now if it has query parameters.
//TODO : Maybe the query is incomplete and the client will send more data, case to handle.
{
client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (client->_httpRequestData.maxResourceBuffer+1) ); //for \0
if(client->_httpRequestData.httpResource != NULL)
{
strncpy(client->_httpRequestData.httpResource, (char *)client->_data, client->_httpRequestData.maxResourceBuffer);
client->_httpRequestData.httpResource[client->_httpRequestData.maxResourceBuffer] = '\0';
}
else //Error 500
{
sendInfoResponse(HTTP_CODE::HTTP_CODE_INTERNAL_SERVER_ERROR, client, "Failed to allocate memory for resources");
#ifdef DEBUG_WEBS
Serial.printf("Could not find ' ' or '?' delimiter\nclient->_data : #%s#\n",
(char *)client->_data);
#endif
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, "Resource too long");
client->_clientState = TCPClient::ClientState::DISCARDED;
break;
}
client->freeDataBuffer(client->_httpRequestData.maxResourceBuffer + 1);
}
client->_httpParserState = HttpParserStatus::HTTP_PARAMS;
}
break;
case HttpParserStatus::HTTP_VERSION:
case HttpParserStatus::PARSE_HTTP_VERSION:
{
char *pEndline = strstr((char *)client->_data, "\r\n");
if(pEndline == NULL) pEndline = strstr((char *)client->_data, "\n");
if(pEndline == NULL) pEndline = strchr((char *)client->_data, '\n');
char *pVers = strstr((char *)client->_data, "HTTP/");
@ -224,25 +253,29 @@ class WEBServer : public TCPServer<T>, public HttpConstants
Serial.println((char *)client->_data);
#endif
client->_httpParserState = HttpParserStatus::HEADER_PARAMS;
client->_httpParserState = HttpParserStatus::PARSE_HTTP_HEADER_PARAMS;
}
}
break;
case HttpParserStatus::HTTP_PARAMS: //index.htm?var1=1&var2=2...
//index.htm?var1=1&var2=2...
//----------^^^^^^^^^^^^^^
case HttpParserStatus::PARSE_HTTP_RESOURCE_QUERY:
//If we are here, it means we are sure that there is at least one parameter
if(!httpRsrcParamParser(client))
{
#ifdef DEBUG_WEBS
Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource);
Serial.println("Get params :");
for(int i = 0; i < client->_httpRequestData.getParams.count(); i++)
for(unsigned int i = 0; i < client->_httpRequestData.getParams.count(); i++)
{
Serial.print(client->_httpRequestData.getParams.getParameter(i));Serial.print(" : ");Serial.println(client->_httpRequestData.getParams.getAt(i)->getString());
Serial.printf("%s : %s\n", client->_httpRequestData.getParams.getParameter(i), client->_httpRequestData.getParams.getAt(i)->getString());
}
Serial.printf("client->_data : #%s#\n", client->_data);
#endif
client->_httpParserState = HttpParserStatus::HTTP_VERSION;
client->_httpParserState = HttpParserStatus::PARSE_HTTP_VERSION;
}
break;
case HttpParserStatus::HEADER_PARAMS: //Here we parse the different header params until we arrive to \r\n\r\n
case HttpParserStatus::PARSE_HTTP_HEADER_PARAMS: //Here we parse the different header params until we arrive to \r\n\r\n
{
char *pEndLine = strstr((char *)client->_data, "\r\n");
@ -254,7 +287,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
if(*(pEndLine+2) == '\r') //We got \r\n\r\n -> so we go to the post data section
{
client->_httpParserState = HttpParserStatus::POST_DATA;
client->_httpParserState = HttpParserStatus::PARSE_HTTP_POST_DATA;
client->freeDataBuffer((pEndLine - (char *)client->_data) +3); //client->_data must not be empty...
break;
}
@ -265,11 +298,11 @@ class WEBServer : public TCPServer<T>, public HttpConstants
}
else //Error : indeed, we should at least have : \r\n. We go to the next step anyway
{
client->_httpParserState = HttpParserStatus::POST_DATA;
client->_httpParserState = HttpParserStatus::PARSE_HTTP_POST_DATA;
}
}
break;
case HttpParserStatus::POST_DATA:
case HttpParserStatus::PARSE_HTTP_POST_DATA:
switch(client->_httpRequestData.HMT)
{
@ -391,65 +424,59 @@ class WEBServer : public TCPServer<T>, public HttpConstants
}
/*
* This function is here to parse resources parameters
* This function is here to parse resources query parameters
*/
boolean httpRsrcParamParser(T *client)
{
char *pGetParam = strchr((char *)client->_httpRequestData.httpResource, '?');
char *end(strchr((char *)client->_data, ' '));
//If we find the end we mark it, this is needed for subsequent strchr
if(end)*end = '\0';
if(pGetParam != NULL) //There are some params to be parsed
char *key(strchr((char *)client->_data, '=')), *value(strchr((char *)client->_data, '&'));
if(key == nullptr && value == nullptr) //Only the key is present
{
if(client->_httpRequestData.getParamsDataPointer == NULL)
{
client->_httpRequestData.getParamsDataPointer = pGetParam +1;//We save the starting position of the string to parse
}
char *key = strchr(client->_httpRequestData.getParamsDataPointer, '=');
char *value = strchr(client->_httpRequestData.getParamsDataPointer, '&');
if(key == NULL && value == NULL) //Only the key is present
{
client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL));
*pGetParam = '\0';
client->_httpRequestData.getParams.add((char *)client->_data, new DictionaryHelper::StringEntity(NULL));
client->freeDataBuffer(strlen((char *)client->_data) + 1);
#ifdef DEBUG_WEBS
Serial.printf("client->_data : #%s#\n", client->_data);
#endif
return false;
}
else if(key != NULL && value != NULL)
else if(key != nullptr && value != nullptr)
{
if(key < value)*key = '\0';
*value = '\0';
client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key > value ? NULL : key + 1));
memmove(client->_httpRequestData.getParamsDataPointer, value+1, strlen(value+1)+1);
client->_httpRequestData.getParams.add((char *)client->_data, new DictionaryHelper::StringEntity(key > value ? NULL : key + 1));
client->freeDataBuffer((value - (char *)client->_data) + 1);
#ifdef DEBUG_WEBS
Serial.print("Params pointer : ");Serial.println(client->_httpRequestData.getParamsDataPointer);
Serial.printf("client->_data : #%s#\n", client->_data);
#endif
}
else if(key != NULL && value == NULL) //Only one key/value pair present
else if(key != nullptr && value == nullptr) //Only one key/value pair present
{
*key = '\0';
client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key+1));
*pGetParam = '\0';
return false;
}
else if(key == NULL && value != NULL)
{
*value = '\0';
client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL));
memmove(client->_httpRequestData.getParamsDataPointer, value+1, strlen(value+1)+1);
}
}
else //nothing to parse or done
{
client->_httpRequestData.getParams.add((char *)client->_data, new DictionaryHelper::StringEntity(key+1));
client->freeDataBuffer((key - (char *)client->_data) + strlen(key+1) + 2);
#ifdef DEBUG_WEBS
Serial.println("Nothing to parse or done");
Serial.printf("client->_data : #%s#\n", client->_data);
#endif
return false;
}
else if(key == nullptr && value != nullptr)
{
*value = '\0';
client->_httpRequestData.getParams.add((char *)client->_data, new DictionaryHelper::StringEntity(NULL));
client->freeDataBuffer((value - (char *)client->_data) + 1);
#ifdef DEBUG_WEBS
Serial.printf("client->_data : #%s#\n", client->_data);
#endif
}
return true;
}
@ -674,15 +701,18 @@ class WEBServer : public TCPServer<T>, public HttpConstants
case HTTP_CODE_BAD_REQUEST:
strcpy_P(codeLiteral,PSTR("Bad Request"));
break;
case HTTP_CODE_NOT_FOUND:
strcpy_P(codeLiteral,PSTR("Not Found"));
break;
case HTTP_CODE_FORBIDDEN:
strcpy_P(codeLiteral,PSTR("Forbidden"));
break;
case HTTP_CODE_NOT_FOUND:
strcpy_P(codeLiteral,PSTR("Not Found"));
break;
case HTTP_CODE_METHOD_NOT_ALLOWED:
strcpy_P(codeLiteral,PSTR("Method Not Allowed"));
break;
case HTTP_CODE_URI_TOO_LONG:
strcpy_P(codeLiteral,PSTR("URI Too Long"));
break;
case HTTP_CODE_INTERNAL_SERVER_ERROR:
strcpy_P(codeLiteral,PSTR("Internal Server Error"));
break;
@ -745,8 +775,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
static char *getFileExtension(char *name)
{
char *p(lastIndexOf(name, '.'));
char *p(strrchr(name, '.'));
return p != NULL ? p+1 : NULL;
}