Added full HTTP cookie support to the WEBServer, did some cleanup and refactoring around the HTTP Header Parsing algorithm to ease the cookie parser implementation
This commit is contained in:
parent
bbebac6212
commit
0f68644064
@ -32,6 +32,7 @@ WEBClient::~WEBClient()
|
|||||||
void WEBClient::clearHttpRequestData()
|
void WEBClient::clearHttpRequestData()
|
||||||
{
|
{
|
||||||
free(_httpRequestData.httpResource);free(_httpRequestData.httpBody);
|
free(_httpRequestData.httpResource);free(_httpRequestData.httpBody);
|
||||||
|
_httpRequestData.cookies.dispose();
|
||||||
_httpRequestData.getParams.dispose();
|
_httpRequestData.getParams.dispose();
|
||||||
_httpRequestData.postParams.dispose();
|
_httpRequestData.postParams.dispose();
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,14 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
PARSE_HTTP_VERSION,
|
PARSE_HTTP_VERSION,
|
||||||
PARSE_HTTP_RESOURCE_QUERY,
|
PARSE_HTTP_RESOURCE_QUERY,
|
||||||
PARSE_HTTP_POST_DATA,
|
PARSE_HTTP_POST_DATA,
|
||||||
PARSE_HTTP_HEADER_PARAMS
|
PARSE_HTTP_HEADER_PARAMS,
|
||||||
|
PARSE_HTTP_COOKIES
|
||||||
};
|
};
|
||||||
enum WEBClientState {ACCEPTED, PARSING, QUERY_PARSED, RESPONSE_SENT, DONE};
|
enum WEBClientState {ACCEPTED, PARSING, QUERY_PARSED, RESPONSE_SENT, DONE};
|
||||||
|
|
||||||
struct HttpCookie
|
struct HttpCookie
|
||||||
{
|
{
|
||||||
DictionaryHelper::StringEntity value;
|
DictionaryHelper::StringEntity value;
|
||||||
DictionaryHelper::StringEntity domain;
|
|
||||||
DictionaryHelper::StringEntity path;
|
|
||||||
int32_t sameSite : 1, httpOnly : 1, maxAge : 30;
|
|
||||||
//Need to add the expires field as well. Thinking about the best way of doing it.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HttpRequestData
|
struct HttpRequestData
|
||||||
@ -40,6 +37,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
HttpMIMEType HMT;
|
HttpMIMEType HMT;
|
||||||
size_t contentLength;
|
size_t contentLength;
|
||||||
|
|
||||||
|
Dictionary<HttpCookie> cookies;
|
||||||
Dictionary<DictionaryHelper::StringEntity> getParams;
|
Dictionary<DictionaryHelper::StringEntity> getParams;
|
||||||
Dictionary<DictionaryHelper::StringEntity> postParams;
|
Dictionary<DictionaryHelper::StringEntity> postParams;
|
||||||
char *postParamsDataPointer; //Used in the postParams algorithm
|
char *postParamsDataPointer; //Used in the postParams algorithm
|
||||||
@ -100,9 +98,9 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
* The addCookie method adds cookies to the cookie dictionary which will be used when calling the sendHTTPResponse method.
|
* The addCookie method adds cookies to the cookie dictionary which will be used when calling the sendHTTPResponse method.
|
||||||
* Once the cookies are sent, the dictionary will be emptied.
|
* Once the cookies are sent, the dictionary will be emptied.
|
||||||
**/
|
**/
|
||||||
boolean addCookies(const char *cookieName, const char *cookieValue, int32_t maxAge = -1, const char *cookiePath = nullptr, const char *cookieDomain = nullptr, boolean httpOnly = false, boolean sameSite = false)
|
boolean addCookies(const char *cookieName, const char *cookieValue = nullptr, int32_t maxAge = -1, const char *cookiePath = nullptr, const char *cookieDomain = nullptr, boolean httpOnly = false, boolean sameSite = false)
|
||||||
{
|
{
|
||||||
return _setCookieDictionary.add(cookieName, new HttpCookie({cookieValue, cookieDomain, cookiePath, sameSite, httpOnly, maxAge}));
|
return _setCookieDictionary.add(cookieName, new ServerHttpCookie({cookieValue, cookieDomain, cookiePath, sameSite, httpOnly, maxAge}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearCookies(void)
|
void clearCookies(void)
|
||||||
@ -136,10 +134,26 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
//We here send the user defined cookies :)
|
//We here send the user defined cookies :)
|
||||||
for(unsigned int i(0); i < _setCookieDictionary.count(); i++)
|
for(unsigned int i(0); i < _setCookieDictionary.count(); i++)
|
||||||
{
|
{
|
||||||
//client
|
ServerHttpCookie *pCookie = _setCookieDictionary.getAt(i);
|
||||||
|
|
||||||
|
if(pCookie)
|
||||||
|
{
|
||||||
|
client->printf_P(PSTR("\r\nSet-Cookie: %s=%s"), _setCookieDictionary.getParameter(i), pCookie->value.getString());
|
||||||
|
if(pCookie->maxAge != -1)
|
||||||
|
client->printf_P(PSTR("; Max-Age=%d"), pCookie->maxAge);
|
||||||
|
if(pCookie->sameSite)
|
||||||
|
client->printf_P(PSTR("; SameSite=Strict"));
|
||||||
|
if(pCookie->domain.getString()[0] != '\0')
|
||||||
|
client->printf_P(PSTR("; Domain=%s"), pCookie->domain.getString());
|
||||||
|
if(pCookie->path.getString()[0] != '\0')
|
||||||
|
client->printf_P(PSTR("; Path=%s"), pCookie->path.getString());
|
||||||
|
if(pCookie->httpOnly)
|
||||||
|
client->printf_P(PSTR("; HttpOnly"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//We do not forget to clear them after
|
//We do not forget to clear the cookie dictionary after
|
||||||
clearCookies();
|
clearCookies();
|
||||||
|
|
||||||
client->print("\r\n\r\n");
|
client->print("\r\n\r\n");
|
||||||
@ -270,7 +284,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
|
|
||||||
if(client->_httpRequestData.HRM == HttpRequestMethod::UNDEFINED) //Error 400
|
if(client->_httpRequestData.HRM == HttpRequestMethod::UNDEFINED) //Error 400
|
||||||
{
|
{
|
||||||
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, "The server could not understand the request due to invalid syntax");
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, PSTR("The server could not understand the request due to invalid syntax"));
|
||||||
client->_clientState = TCPClient::ClientState::DISCARDED;
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -284,7 +298,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, "The server could not understand the request due to invalid syntax");
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, PSTR("The server could not understand the request due to invalid syntax"));
|
||||||
client->_clientState = TCPClient::ClientState::DISCARDED;
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,7 +336,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
#ifdef DEBUG_WEBS
|
#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);
|
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
|
#endif
|
||||||
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, "Resource too long");
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, PSTR("Resource too long"));
|
||||||
client->_clientState = TCPClient::ClientState::DISCARDED;
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -361,7 +375,7 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
Serial.printf("Could not find ' ' or '?' delimiter\nclient->_data : #%s#\n",
|
Serial.printf("Could not find ' ' or '?' delimiter\nclient->_data : #%s#\n",
|
||||||
(char *)client->_data);
|
(char *)client->_data);
|
||||||
#endif
|
#endif
|
||||||
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, "Resource too long");
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_URI_TOO_LONG, client, PSTR("Resource too long"));
|
||||||
client->_clientState = TCPClient::ClientState::DISCARDED;
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -413,43 +427,63 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
client->_httpParserState = HttpParserStatus::PARSE_HTTP_VERSION;
|
client->_httpParserState = HttpParserStatus::PARSE_HTTP_VERSION;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HttpParserStatus::PARSE_HTTP_HEADER_PARAMS: //Here we parse the different header params until we arrive to \r\n\r\n
|
//Here we parse the different header params until we arrive to \r\n\r\n
|
||||||
|
//We also know that header params are of the form : Param1: value1\r\nParam2: value2\r\n etc
|
||||||
|
//So we look for the delimitor which is ":"
|
||||||
|
case HttpParserStatus::PARSE_HTTP_HEADER_PARAMS:
|
||||||
|
{
|
||||||
|
char *pDelimiter(strchr((char *)client->_data, ':'));
|
||||||
|
char *endHeaderDelimiter(strstr((char *)client->_data, "\r\n"));
|
||||||
|
|
||||||
|
//If we found the delimiter, this means there is at least one parameter
|
||||||
|
if(pDelimiter)
|
||||||
{
|
{
|
||||||
char *pEndLine = strstr((char *)client->_data, "\r\n");
|
*pDelimiter = '\0';
|
||||||
|
httpHeaderParamParser(client);
|
||||||
if( pEndLine != NULL )
|
}
|
||||||
{
|
//There is maybe not headers so we go to the http body part
|
||||||
*pEndLine = '\0';
|
else if(endHeaderDelimiter)
|
||||||
|
{
|
||||||
httpHeaderParamParser(client);
|
if(client->_httpRequestData.contentLength) //If a body is expected
|
||||||
|
|
||||||
if(*(pEndLine+2) == '\r') //We got \r\n\r\n -> so we go to the post data section
|
|
||||||
{
|
|
||||||
if(client->_httpRequestData.contentLength) //If a body is expected
|
|
||||||
client->_httpParserState = HttpParserStatus::PARSE_HTTP_POST_DATA;
|
|
||||||
else //Else we are done
|
|
||||||
client->_WEBClientState = WEBClientState::QUERY_PARSED;
|
|
||||||
|
|
||||||
client->freeDataBuffer((pEndLine - (char *)client->_data) +3); //client->_data must not be empty...
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Before in the buffer : key1: value1\r\nkey2: value2
|
|
||||||
//After in the buffer : key2: value2\r\n
|
|
||||||
client->freeDataBuffer((pEndLine - (char *)client->_data) + 2);
|
|
||||||
}
|
|
||||||
else //Error : indeed, we should at least have : \r\n. We go to the next step anyway
|
|
||||||
{
|
|
||||||
client->_httpParserState = HttpParserStatus::PARSE_HTTP_POST_DATA;
|
client->_httpParserState = HttpParserStatus::PARSE_HTTP_POST_DATA;
|
||||||
|
else //Else we are done
|
||||||
|
client->_WEBClientState = WEBClientState::QUERY_PARSED;
|
||||||
|
|
||||||
|
client->freeDataBuffer((endHeaderDelimiter - (char *)client->_data) +1); //client->_data must not be empty so we keep the last \n...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.println("Error, should have found \\r\\n\\r\\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, PSTR("The server could not understand the request due to invalid syntax"));
|
||||||
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HttpParserStatus::PARSE_HTTP_COOKIES:
|
||||||
|
if(!httpCookiesParser(client))
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.println("Cookies :");
|
||||||
|
for(unsigned int i = 0; i < client->_httpRequestData.cookies.count(); i++)
|
||||||
|
{
|
||||||
|
Serial.printf("#%s# : #%s#\n", client->_httpRequestData.cookies.getParameter(i), client->_httpRequestData.cookies.getAt(i)->value.getString());
|
||||||
}
|
}
|
||||||
|
Serial.printf("phc client->_data : #%s#\n", client->_data);
|
||||||
|
#endif
|
||||||
|
//Once we are done parsing the cookies, we go back to the HTTP HEADER PARAMS PARSER as there may be still some more after the cookies ?
|
||||||
|
client->_httpParserState = HttpParserStatus::PARSE_HTTP_HEADER_PARAMS;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HttpParserStatus::PARSE_HTTP_POST_DATA:
|
case HttpParserStatus::PARSE_HTTP_POST_DATA:
|
||||||
|
|
||||||
switch(client->_httpRequestData.HMT)
|
switch(client->_httpRequestData.HMT)
|
||||||
{
|
{
|
||||||
case APPLICATION_X_WWW_FORM_URLENCODED:
|
case APPLICATION_X_WWW_FORM_URLENCODED:
|
||||||
#if 1//def DEBUG_WEBS
|
#ifdef DEBUG_WEBS
|
||||||
Serial.printf("Post data : APPLICATION_X_WWW_FORM_URLENCODED\nPost data : #%s#\n", client->_data);
|
Serial.printf("Post data : APPLICATION_X_WWW_FORM_URLENCODED\nPost data : #%s#\n", client->_data);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -457,11 +491,11 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
if(!httpPostParamParser(client))
|
if(!httpPostParamParser(client))
|
||||||
{
|
{
|
||||||
//Parsing done!
|
//Parsing done!
|
||||||
#if 1//def DEBUG_WEBS
|
#ifdef DEBUG_WEBS
|
||||||
Serial.println("Post params :");
|
Serial.println("Post params :");
|
||||||
for(unsigned int i = 0; i < client->_httpRequestData.postParams.count(); i++)
|
for(unsigned int i = 0; i < client->_httpRequestData.postParams.count(); i++)
|
||||||
{
|
{
|
||||||
Serial.print(client->_httpRequestData.postParams.getParameter(i));Serial.print(" : ");Serial.println(client->_httpRequestData.postParams.getAt(i)->getString());
|
Serial.printf("#%s# : #%s#\n", client->_httpRequestData.postParams.getParameter(i), client->_httpRequestData.postParams.getAt(i)->getString());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
client->_WEBClientState = WEBClientState::QUERY_PARSED;
|
client->_WEBClientState = WEBClientState::QUERY_PARSED;
|
||||||
@ -486,79 +520,116 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
*/
|
*/
|
||||||
void httpHeaderParamParser(T *client)
|
void httpHeaderParamParser(T *client)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_WEBS
|
char *pHeaderParam((char *)client->_data);
|
||||||
Serial.printf("Header param : %s\n",(char *)client->_data);
|
char *pHeaderValue((char *)client->_data + strlen((char *)client->_data) + 2); //+2 is to discard the ": "
|
||||||
#endif
|
char *pHeaderEndOfValue(strstr((char *)pHeaderValue, "\r\n"));
|
||||||
|
|
||||||
//Here we check if we have interesting params
|
if(pHeaderEndOfValue)
|
||||||
char *search = strstr((char *)client->_data, "t-Type: application/x-www-form-urlen");
|
{
|
||||||
if(search != nullptr)
|
*pHeaderEndOfValue = '\0';
|
||||||
|
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.printf("Header param->value : #%s# -> #%s#\n", pHeaderParam, pHeaderValue);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *searchParam(strstr(pHeaderParam, "t-Type"));
|
||||||
|
char *searchValue(strstr(pHeaderValue, "ion/x-www-for"));
|
||||||
|
|
||||||
|
if(searchParam && searchValue)
|
||||||
|
{
|
||||||
|
client->_httpRequestData.HMT = APPLICATION_X_WWW_FORM_URLENCODED;
|
||||||
|
client->freeDataBuffer((pHeaderEndOfValue - (char *)client->_data) + 2);
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.println("Content-Type is APPLICATION_X_WWW_FORM_URLENCODED");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((searchParam = strstr(pHeaderParam, "ion")) && (searchValue = strstr(pHeaderValue, "keep-al")))
|
||||||
|
{
|
||||||
|
client->_keepAlive = true;
|
||||||
|
client->freeDataBuffer((pHeaderEndOfValue - (char *)client->_data) + 2);
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.println("Connection: keep-alive");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((searchParam = strstr(pHeaderParam, "ent-Len")))
|
||||||
|
{
|
||||||
|
char *check(nullptr);
|
||||||
|
client->_httpRequestData.contentLength = strtoul(pHeaderValue, &check, 10);
|
||||||
|
|
||||||
|
if(*check != '\0') //Failed to parse the content length !
|
||||||
|
{
|
||||||
|
client->_httpRequestData.contentLength = 0;
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.println("Failed to parse Content-Length");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.printf("Content-Length: %u\n", client->_httpRequestData.contentLength);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
client->freeDataBuffer((pHeaderEndOfValue - (char *)client->_data) + 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Range part for file downloads and media playback
|
||||||
|
if((searchParam = strstr(pHeaderParam, "nge")) && (searchValue = strstr(pHeaderValue, "ytes=")))
|
||||||
|
{
|
||||||
|
//We need to parse the range values
|
||||||
|
if(fillRangeByteStruct(client, pHeaderValue))
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.printf("Range (bytes) data : start -> %u ; end -> %u\n", client->_rangeData._rangeStart, client->_rangeData._rangeEnd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.printf("Range (bytes) data parse error : start -> %u ; end -> %u\n", client->_rangeData._rangeStart, client->_rangeData._rangeEnd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
client->freeDataBuffer((pHeaderEndOfValue - (char *)client->_data) + 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here we check if there are some cookies to be parsed
|
||||||
|
if((searchParam = strstr(pHeaderParam, "okie")))
|
||||||
|
{
|
||||||
|
client->_httpParserState = HttpParserStatus::PARSE_HTTP_COOKIES;
|
||||||
|
client->freeDataBuffer(pHeaderValue - (char *)client->_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//We still need to remove
|
||||||
|
client->freeDataBuffer((pHeaderEndOfValue - (char *)client->_data) + 2);
|
||||||
|
}
|
||||||
|
//Error, we did not find the \r\n, maybe the parameter value is too long, error needs to be handled !
|
||||||
|
else
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_WEBS
|
#ifdef DEBUG_WEBS
|
||||||
Serial.printf("Content-Type : APPLICATION_X_WWW_FORM_URLENCODED\n");
|
Serial.println("Error : header value \\r\\n not found!");
|
||||||
#endif
|
#endif
|
||||||
client->_httpRequestData.HMT = APPLICATION_X_WWW_FORM_URLENCODED;
|
|
||||||
return; //No need to look further
|
|
||||||
}
|
|
||||||
|
|
||||||
search = strstr((char *)client->_data, "ion: keep-al");
|
sendInfoResponse(HTTP_CODE::HTTP_CODE_BAD_REQUEST, client, PSTR("The server could not understand the request due to invalid syntax"));
|
||||||
if(search != nullptr)
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
||||||
{
|
|
||||||
#ifdef DEBUG_WEBS
|
|
||||||
Serial.printf("Connection : keep-alive\n");
|
|
||||||
#endif
|
|
||||||
client->_keepAlive = true;
|
|
||||||
return; //No need to look further
|
|
||||||
}
|
|
||||||
|
|
||||||
//Range part for file downloads and media playback
|
|
||||||
search = strstr((char *)client->_data, "nge: bytes=");
|
|
||||||
|
|
||||||
if(search != nullptr)
|
|
||||||
{
|
|
||||||
//We parse the range byte data
|
|
||||||
if(fillRangeByteStruct(client))
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_WEBS
|
|
||||||
Serial.printf("Range (bytes) data : start -> %u ; end -> %u\n", client->_rangeData._rangeStart, client->_rangeData._rangeEnd);
|
|
||||||
#endif
|
|
||||||
client->_rangeData._rangeRequest = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_WEBS
|
|
||||||
Serial.printf("Range (bytes) data parse error : start -> %u ; end -> %u\n", client->_rangeData._rangeStart, client->_rangeData._rangeEnd);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return; //No need to look further
|
|
||||||
}
|
|
||||||
|
|
||||||
//Content-length header
|
|
||||||
search = strstr((char *)client->_data, "ent-Length: ");
|
|
||||||
if(search != nullptr)
|
|
||||||
{
|
|
||||||
if(!fillContentLength(client))
|
|
||||||
{
|
|
||||||
#if 1//def DEBUG_WEBS
|
|
||||||
Serial.printf("Failed to parse content length\n");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if 1//def DEBUG_WEBS
|
|
||||||
Serial.printf("Parsed content length is :%u\n", client->_httpRequestData.contentLength);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return; //No need to look further
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This function fills the client's _rangeData struct with proper values
|
* This function fills the client's _rangeData struct with proper values
|
||||||
*/
|
*/
|
||||||
bool fillRangeByteStruct(T *client)
|
bool fillRangeByteStruct(T *client, char *rangeBytesData)
|
||||||
{
|
{
|
||||||
char *rangeStart = strchr((char *)client->_data, '='), *delimiter = strchr((char *)client->_data, '-'), *check(nullptr);
|
if(!rangeBytesData)return false;
|
||||||
|
|
||||||
|
char *rangeStart = strchr(rangeBytesData, '='), *delimiter = strchr(rangeBytesData, '-'), *check(nullptr);
|
||||||
|
|
||||||
if(!rangeStart)return false;
|
if(!rangeStart)return false;
|
||||||
|
|
||||||
@ -583,24 +654,14 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
//We parse the 2nd part of the range byte
|
//We parse the 2nd part of the range byte
|
||||||
client->_rangeData._rangeEnd = strtoull(rangeStart, &check, 10);
|
client->_rangeData._rangeEnd = strtoull(rangeStart, &check, 10);
|
||||||
if(*check != '\0')return false;
|
if(*check != '\0')return false;
|
||||||
|
|
||||||
|
client->_rangeData._rangeRequest = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function fills the client's _httpRequestData.contentLength attribut
|
|
||||||
*/
|
|
||||||
bool fillContentLength(T *client)
|
|
||||||
{
|
|
||||||
char *start(strchr((char *)client->_data, ':')), *check(nullptr);
|
|
||||||
if(!start)return false;
|
|
||||||
start++;
|
|
||||||
client->_httpRequestData.contentLength = strtoul(start, &check, 10);
|
|
||||||
|
|
||||||
if(*check != '\0')return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* This function is here to parse resources query parameters
|
* This function parses resources query parameters
|
||||||
*/
|
*/
|
||||||
boolean httpRsrcParamParser(T *client)
|
boolean httpRsrcParamParser(T *client)
|
||||||
{
|
{
|
||||||
@ -691,6 +752,46 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean httpCookiesParser(T *client)
|
||||||
|
{
|
||||||
|
//He we have the the Cookie field value like : TestCookie=TestValue; TestCookie2=; TestCookie3=TestValue3
|
||||||
|
//It is what we need to parse.
|
||||||
|
char *endOfKey(strchr((char *)client->_data, '='));
|
||||||
|
char *endOfValue(strchr((char *)client->_data, ';'));
|
||||||
|
char *failSafe(strstr((char *)client->_data, "\r\n"));
|
||||||
|
boolean notFinished(true);
|
||||||
|
|
||||||
|
//In the case where we have cookies followed by post data, there was an issue were we mistaken the = from the cookie separator with the = of the post data separator.
|
||||||
|
//Here just in case of the last cookie value finishing with a ';'
|
||||||
|
if(failSafe)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//There is at least one key/value pair
|
||||||
|
if(endOfKey)
|
||||||
|
{
|
||||||
|
*endOfKey = '\0';
|
||||||
|
endOfKey++;
|
||||||
|
|
||||||
|
if(endOfValue)
|
||||||
|
{
|
||||||
|
*endOfValue = '\0';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endOfValue = endOfKey + strlen(endOfKey) + 1;
|
||||||
|
notFinished = false;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_WEBS
|
||||||
|
Serial.printf("Key -> Value : #%s# Value : #%s#\n", ((char)*client->_data == ' ') ? (char*)client->_data + 1 : (char*)client->_data , endOfKey);
|
||||||
|
#endif
|
||||||
|
client->_httpRequestData.cookies.add(((char)*client->_data == ' ') ? (char*)client->_data + 1 : (char*)client->_data, new HttpCookie({endOfKey}));
|
||||||
|
//We dont forget to free the parsed data
|
||||||
|
client->freeDataBuffer((endOfValue + 1 - (char *)client->_data));
|
||||||
|
return notFinished;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void sendDataToClient(T *client)
|
void sendDataToClient(T *client)
|
||||||
{
|
{
|
||||||
if(!sendPageToClientFromApiDictio(client)) //Then we check if it is not a file that is requested
|
if(!sendPageToClientFromApiDictio(client)) //Then we check if it is not a file that is requested
|
||||||
@ -1109,10 +1210,19 @@ class WEBServer : public TCPServer<T>, public HttpConstants
|
|||||||
void *pData;
|
void *pData;
|
||||||
HttpRequestMethod HRM;
|
HttpRequestMethod HRM;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Server side http cookie handling
|
||||||
|
struct ServerHttpCookie : public HttpCookie
|
||||||
|
{
|
||||||
|
DictionaryHelper::StringEntity domain;
|
||||||
|
DictionaryHelper::StringEntity path;
|
||||||
|
int32_t sameSite : 1, httpOnly : 1, maxAge : 30;
|
||||||
|
//Need to add the expires field as well. Thinking about the best way of doing it.
|
||||||
|
};
|
||||||
|
|
||||||
Dictionary<ApiRoutine> _apiDictionary;
|
Dictionary<ApiRoutine> _apiDictionary;
|
||||||
Dictionary<DictionaryHelper::StringEntity> _httpHeadersDictionary;
|
Dictionary<DictionaryHelper::StringEntity> _httpHeadersDictionary;
|
||||||
Dictionary<HttpCookie> _setCookieDictionary;
|
Dictionary<ServerHttpCookie> _setCookieDictionary;
|
||||||
SDClass *_sdClass;
|
SDClass *_sdClass;
|
||||||
char *_WWWDir = nullptr; //Website root folder
|
char *_WWWDir = nullptr; //Website root folder
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user