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 class WEBServer : public TCPServer<T>, public HttpConstants
{ {
public: 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}; enum WEBClientState {ACCEPTED, PARSING, QUERY_PARSED, RESPONSE_SENT, DONE};
struct HttpRequestData struct HttpRequestData
@ -23,7 +31,6 @@ class WEBServer : public TCPServer<T>, public HttpConstants
HttpMIMEType HMT; HttpMIMEType HMT;
Dictionary<DictionaryHelper::StringEntity> getParams; Dictionary<DictionaryHelper::StringEntity> getParams;
char *getParamsDataPointer; //Used in the getParams algorithm
Dictionary<DictionaryHelper::StringEntity> postParams; Dictionary<DictionaryHelper::StringEntity> postParams;
char *postParamsDataPointer; //Used in the postParams algorithm char *postParamsDataPointer; //Used in the postParams algorithm
@ -33,7 +40,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
uint16_t maxBodyBuffer; 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) 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) switch(client->_httpParserState)
{ {
case HttpParserStatus::HTTP_VERB: case HttpParserStatus::PARSE_HTTP_VERB:
{ {
#ifdef DEBUG_WEBS #ifdef DEBUG_WEBS
Serial.println((char *)client->_data); Serial.println((char *)client->_data);
#endif #endif
char *pVerb = strstr((char *)client->_data, " "); char *pVerb(strchr((char *)client->_data, ' '));
if(pVerb != NULL) if(pVerb != NULL)
{ {
@ -138,7 +145,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
Serial.println((char *)client->_data); Serial.println((char *)client->_data);
#endif #endif
client->_httpParserState = HttpParserStatus::HTTP_RESSOURCE; client->_httpParserState = HttpParserStatus::PARSE_HTTP_RESOURCE;
} }
else else
{ {
@ -147,24 +154,46 @@ class WEBServer : public TCPServer<T>, public HttpConstants
} }
} }
break; break;
case HttpParserStatus::HTTP_RESSOURCE: case HttpParserStatus::PARSE_HTTP_RESOURCE:
{ {
char *pRsrc = strstr((char *)client->_data, " "); 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 != NULL) if(pRsrc || pRsrcQuery)
{ {
*pRsrc = '\0'; uint16_t rawLengthOfResource(0);
uint16_t safeLength = pRsrc - (char *)client->_data <= client->_httpRequestData.maxResourceBuffer ? pRsrc - (char *)client->_data : client->_httpRequestData.maxResourceBuffer; if(pRsrcQuery)
#ifdef DEBUG_WEBS
Serial.print("Resrc length : ");Serial.println(safeLength);
#endif
client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (safeLength+1) ); //for \0
if(client->_httpRequestData.httpResource != NULL)
{ {
strncpy(client->_httpRequestData.httpResource, (char *)client->_data, safeLength); *pRsrcQuery = '\0'; // The ? is the end of the resource string
client->_httpRequestData.httpResource[safeLength] = '\0'; rawLengthOfResource = pRsrcQuery - (char *)client->_data;
#ifdef DEBUG_WEBS
Serial.printf("Resource w/ query\n");
#endif
}
else
{
*pRsrc = '\0';
rawLengthOfResource = pRsrc - (char *)client->_data;
#ifdef DEBUG_WEBS
Serial.printf("Resource w/o query\n");
#endif
}
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 ...
{
#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 else //Error 500
{ {
@ -172,39 +201,39 @@ class WEBServer : public TCPServer<T>, public HttpConstants
client->_clientState = TCPClient::ClientState::DISCARDED; client->_clientState = TCPClient::ClientState::DISCARDED;
break; 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 #ifdef DEBUG_WEBS
Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource); Serial.printf("Resource length : %u\nRsrc : %s\nclient->_data : #%s#\n",
Serial.println((char *)client->_data); rawLengthOfResource,
client->_httpRequestData.httpResource,
(char *)client->_data);
#endif #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 #ifdef DEBUG_WEBS
if(client->_httpRequestData.httpResource != NULL) Serial.printf("Could not find ' ' or '?' delimiter\nclient->_data : #%s#\n",
{ (char *)client->_data);
strncpy(client->_httpRequestData.httpResource, (char *)client->_data, client->_httpRequestData.maxResourceBuffer); #endif
client->_httpRequestData.httpResource[client->_httpRequestData.maxResourceBuffer] = '\0'; sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, "Resource too long");
} client->_clientState = TCPClient::ClientState::DISCARDED;
else //Error 500 break;
{
sendInfoResponse(HTTP_CODE::HTTP_CODE_INTERNAL_SERVER_ERROR, client, "Failed to allocate memory for resources");
client->_clientState = TCPClient::ClientState::DISCARDED;
break;
}
client->freeDataBuffer(client->_httpRequestData.maxResourceBuffer + 1);
} }
client->_httpParserState = HttpParserStatus::HTTP_PARAMS;
} }
break; break;
case HttpParserStatus::HTTP_VERSION: case HttpParserStatus::PARSE_HTTP_VERSION:
{ {
char *pEndline = strstr((char *)client->_data, "\r\n"); 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/"); char *pVers = strstr((char *)client->_data, "HTTP/");
@ -224,25 +253,29 @@ class WEBServer : public TCPServer<T>, public HttpConstants
Serial.println((char *)client->_data); Serial.println((char *)client->_data);
#endif #endif
client->_httpParserState = HttpParserStatus::HEADER_PARAMS; client->_httpParserState = HttpParserStatus::PARSE_HTTP_HEADER_PARAMS;
} }
} }
break; 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)) if(!httpRsrcParamParser(client))
{ {
#ifdef DEBUG_WEBS #ifdef DEBUG_WEBS
Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource);
Serial.println("Get params :"); 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 #endif
client->_httpParserState = HttpParserStatus::HTTP_VERSION; client->_httpParserState = HttpParserStatus::PARSE_HTTP_VERSION;
} }
break; 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"); 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 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... client->freeDataBuffer((pEndLine - (char *)client->_data) +3); //client->_data must not be empty...
break; 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 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; break;
case HttpParserStatus::POST_DATA: case HttpParserStatus::PARSE_HTTP_POST_DATA:
switch(client->_httpRequestData.HMT) 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) boolean httpRsrcParamParser(T *client)
{ {
char *pGetParam = strchr((char *)client->_httpRequestData.httpResource, '?'); char *end(strchr((char *)client->_data, ' '));
if(pGetParam != NULL) //There are some params to be parsed
{
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 //If we find the end we mark it, this is needed for subsequent strchr
{ if(end)*end = '\0';
client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL));
*pGetParam = '\0';
return false;
}
else if(key != NULL && value != NULL)
{
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);
#ifdef DEBUG_WEBS
Serial.print("Params pointer : ");Serial.println(client->_httpRequestData.getParamsDataPointer);
#endif
}
else if(key != NULL && value == NULL) //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)); char *key(strchr((char *)client->_data, '=')), *value(strchr((char *)client->_data, '&'));
memmove(client->_httpRequestData.getParamsDataPointer, value+1, strlen(value+1)+1);
} if(key == nullptr && value == nullptr) //Only the key is present
}
else //nothing to parse or done
{ {
client->_httpRequestData.getParams.add((char *)client->_data, new DictionaryHelper::StringEntity(NULL));
client->freeDataBuffer(strlen((char *)client->_data) + 1);
#ifdef DEBUG_WEBS #ifdef DEBUG_WEBS
Serial.println("Nothing to parse or done"); Serial.printf("client->_data : #%s#\n", client->_data);
#endif #endif
return false; return false;
} }
else if(key != nullptr && value != nullptr)
{
if(key < value)*key = '\0';
*value = '\0';
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.printf("client->_data : #%s#\n", client->_data);
#endif
}
else if(key != nullptr && value == nullptr) //Only one key/value pair present
{
*key = '\0';
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.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; return true;
} }
@ -674,15 +701,18 @@ class WEBServer : public TCPServer<T>, public HttpConstants
case HTTP_CODE_BAD_REQUEST: case HTTP_CODE_BAD_REQUEST:
strcpy_P(codeLiteral,PSTR("Bad Request")); strcpy_P(codeLiteral,PSTR("Bad Request"));
break; break;
case HTTP_CODE_NOT_FOUND:
strcpy_P(codeLiteral,PSTR("Not Found"));
break;
case HTTP_CODE_FORBIDDEN: case HTTP_CODE_FORBIDDEN:
strcpy_P(codeLiteral,PSTR("Forbidden")); strcpy_P(codeLiteral,PSTR("Forbidden"));
break; break;
case HTTP_CODE_NOT_FOUND:
strcpy_P(codeLiteral,PSTR("Not Found"));
break;
case HTTP_CODE_METHOD_NOT_ALLOWED: case HTTP_CODE_METHOD_NOT_ALLOWED:
strcpy_P(codeLiteral,PSTR("Method Not Allowed")); strcpy_P(codeLiteral,PSTR("Method Not Allowed"));
break; break;
case HTTP_CODE_URI_TOO_LONG:
strcpy_P(codeLiteral,PSTR("URI Too Long"));
break;
case HTTP_CODE_INTERNAL_SERVER_ERROR: case HTTP_CODE_INTERNAL_SERVER_ERROR:
strcpy_P(codeLiteral,PSTR("Internal Server Error")); strcpy_P(codeLiteral,PSTR("Internal Server Error"));
break; break;
@ -745,8 +775,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
static char *getFileExtension(char *name) static char *getFileExtension(char *name)
{ {
char *p(lastIndexOf(name, '.')); char *p(strrchr(name, '.'));
return p != NULL ? p+1 : NULL; return p != NULL ? p+1 : NULL;
} }