diff --git a/src/app/WEBServer.h b/src/app/WEBServer.h index 99b9260..e570d50 100644 --- a/src/app/WEBServer.h +++ b/src/app/WEBServer.h @@ -14,7 +14,7 @@ class WEBServer : public TCPServer enum HttpRequestMethod {UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH}; enum HttpVersion {UNKNOWN, HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0}; enum HttpMIMEType{UNKNOWN_MIME, TEXT_PLAIN, TEXT_CSS, TEXT_HTML, TEXT_JAVASCRIPT, APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED, IMAGE_PNG, IMAGE_JPEG, AUDIO_MPEG, APPLICATION_OCTET_STREAM}; - enum HttpParserStatus {HTTP_VERB, HTTP_RESSOURCE, HTTP_VERSION, HTTP_PARAMS, POST_DATA}; + enum HttpParserStatus {HTTP_VERB, HTTP_RESSOURCE, HTTP_VERSION, HTTP_PARAMS, POST_DATA, HEADER_PARAMS}; enum WEBClientState {ACCEPTED, QUERY_PARSED, RESPONSE_SENT, DONE}; enum HTTP_CODE {_100, _101, _200, _400, _401, _403, _404, _405, _500, _501}; @@ -48,6 +48,18 @@ class WEBServer : public TCPServer { return _apiDictionary.remove(uri); } + + //Helper function used for the webApi + static void injectApiHeader(char *header, const char *contentType, char *content) + { + char *buffer = (char *)malloc(sizeof(char) * strlen(content) + 1); + if(buffer != NULL) + { + strcpy(buffer, content); + sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n%s",contentType,strlen(buffer), buffer); + free(buffer); + } + } protected: private: virtual T* createNewClient(WiFiClient wc) @@ -208,12 +220,12 @@ class WEBServer : public TCPServer Serial.println((char *)client->_data); #endif - client->_httpParserState = HttpParserStatus::POST_DATA; + client->_httpParserState = HttpParserStatus::HEADER_PARAMS; } } break; case HttpParserStatus::HTTP_PARAMS: //index.htm?var1=1&var2=2... - if(!httpParamParser(client)) + if(!httpRsrcParamParser(client)) { #ifdef DEBUG_WEBS Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource); @@ -226,9 +238,62 @@ class WEBServer : public TCPServer client->_httpParserState = HttpParserStatus::HTTP_VERSION; } break; + case HttpParserStatus::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"); + + if( pEndLine != NULL ) + { + *pEndLine = '\0'; + + httpHeaderParamParser(client); + + if(*(pEndLine+2) == '\r') //We got \r\n\r\n -> so we go to the post data section + { + client->_httpParserState = HttpParserStatus::POST_DATA; + client->freeDataBuffer((pEndLine - (char *)client->_data) +3); //client->_data must not be empty... + break; + } + + //Before in the buffer : key1: value1\0\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::POST_DATA; + } + } + break; case HttpParserStatus::POST_DATA: + + switch(client->_httpRequestData.HMT) + { + case APPLICATION_X_WWW_FORM_URLENCODED: + #ifdef DEBUG_WEBS + Serial.printf("Post data : APPLICATION_X_WWW_FORM_URLENCODED\n"); + Serial.printf("Post data : %s\n", client->_data); + #endif + + //we parse it ! + if(!httpPostParamParser(client)) + { + //Parsing done! + #ifdef DEBUG_WEBS + Serial.println("Post params :"); + for(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()); + } + #endif + client->_WEBClientState = WEBClientState::QUERY_PARSED; + } + + break; + default : + client->_WEBClientState = WEBClientState::QUERY_PARSED; + } - client->_WEBClientState = WEBClientState::QUERY_PARSED; break; default : sendInfoResponse(HTTP_CODE::_500, client, "WEB server error"); @@ -237,7 +302,44 @@ class WEBServer : public TCPServer } } - boolean httpParamParser(T *client) + /* + * This function parses the header parameters in order to find particular parameters. + * For example we look for the "Content-Type" header or for the "Range: bytes=" header + */ + void httpHeaderParamParser(T *client) + { + #ifdef DEBUG_WEBS + Serial.printf("Header param : %s\n",(char *)client->_data); + #endif + + //Here we check if we have interesting params + char *contentTypeP = strstr((char *)client->_data, "t-Type: application/x-www-form-urlen"); + + if(contentTypeP != NULL) + { + #ifdef DEBUG_WEBS + Serial.printf("Data is of type : APPLICATION_X_WWW_FORM_URLENCODED\n"); + #endif + client->_httpRequestData.HMT = APPLICATION_X_WWW_FORM_URLENCODED; + } + + //Range part for file downloads + /*char *startingP = strstr((char *)client->_data, "nge: bytes="), *endP = strchr((char *)client->_data, '-'); + + if(startingP != NULL && endP != NULL) + { + endP = '\0'; + client->_range = strtoul(startingP + 11,NULL, 10); + #ifdef DEBUG_WEBS + Serial.printf("Range : %d\n", client->_range); + #endif + }*/ + } + + /* + * This function is here to parse resources parameters + */ + boolean httpRsrcParamParser(T *client) { char *pGetParam = strchr((char *)client->_httpRequestData.httpResource, '?'); @@ -264,7 +366,7 @@ class WEBServer : public TCPServer *value = '\0'; client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key > value ? NULL : key + 1)); - strcpy(client->_httpRequestData.getParamsDataPointer,value+1); + memmove(client->_httpRequestData.getParamsDataPointer, value+1, strlen(value+1)+1); #ifdef DEBUG_WEBS Serial.print("Params pointer : ");Serial.println(client->_httpRequestData.getParamsDataPointer); @@ -283,13 +385,48 @@ class WEBServer : public TCPServer *value = '\0'; client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL)); - strcpy(client->_httpRequestData.getParamsDataPointer,value+1); + memmove(client->_httpRequestData.getParamsDataPointer, value+1, strlen(value+1)+1); } } else //nothing to parse or done { return false; } + + return true; + } + + boolean httpPostParamParser(T* client) + { + if(client->_httpRequestData.postParamsDataPointer == NULL) + { + client->_httpRequestData.postParamsDataPointer = (char *)client->_data + 1;//We save the starting position of the string to parse and we ignore the \n + } + + char *key = strchr(client->_httpRequestData.postParamsDataPointer, '='); + char *value = strchr(client->_httpRequestData.postParamsDataPointer, '&'); + + if(key == NULL && value == NULL) //Nothing to parse or done + { + return false; + } + else if(key != NULL && value == NULL) //Only one key is present + { + *key = '\0'; + client->_httpRequestData.postParams.add(client->_httpRequestData.postParamsDataPointer, new DictionaryHelper::StringEntity(key+1)); + return false; + } + else if(key != NULL && value != NULL) + { + *key = '\0'; + *value = '\0'; + client->_httpRequestData.postParams.add(client->_httpRequestData.postParamsDataPointer, new DictionaryHelper::StringEntity(key+1)); + memmove(client->_httpRequestData.postParamsDataPointer, value +1, strlen(value+1) + 1); + } + else if(key == NULL && value != NULL)//Should never happen + return false; + + return true; } void sendDataToClient(T *client) @@ -387,7 +524,7 @@ class WEBServer : public TCPServer return false; } - if(client->_fileSentBytes == 0) + if(client->_fileSentBytes == 0 /*&& client->_range == 0*/) { char *fileName = (char *) malloc(sizeof(char) * strlen(pageToSend.name()) + 1); @@ -411,6 +548,11 @@ class WEBServer : public TCPServer client->_client.print(header); free(header);header = NULL; } + /*else if(client->_fileSentBytes == 0 && (!client->_range == 0)) + { + client->_fileSentBytes = client->_range-500; + Serial.println("RANGE SET"); + }*/ else { pageToSend.seek(client->_fileSentBytes); @@ -438,6 +580,7 @@ class WEBServer : public TCPServer break; default: //If not supported sendInfoResponse(HTTP_CODE::_500, client, "The method used is not allowed"); + return false; break; } }else @@ -448,8 +591,10 @@ class WEBServer : public TCPServer return true; } + + /*Static helper methods*/ - void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message) + static void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message) { uint16_t code(0); char codeLiteral[100]; @@ -457,39 +602,32 @@ class WEBServer : public TCPServer { case _400: code = 400; - strcpy_P(codeLiteral,(PGM_P)F("Bad Request")); + strcpy_P(codeLiteral,PSTR("Bad Request")); break; case _404: code = 404; - strcpy_P(codeLiteral,(PGM_P)F("Not Found")); + strcpy_P(codeLiteral,PSTR("Not Found")); break; case _403: code = 403; - strcpy_P(codeLiteral,(PGM_P)F("Forbidden")); + strcpy_P(codeLiteral,PSTR("Forbidden")); break; case _405: code = 405; - strcpy_P(codeLiteral,(PGM_P)F("Method Not Allowed")); + strcpy_P(codeLiteral,PSTR("Method Not Allowed")); break; case _500: code = 500; - strcpy_P(codeLiteral,(PGM_P)F("Internal Server Error")); + strcpy_P(codeLiteral,PSTR("Internal Server Error")); break; default: code = 000; - strcpy_P(codeLiteral,(PGM_P)F("Error Not Defined")); + strcpy_P(codeLiteral,PSTR("Error Not Defined")); break; } - - client->_client.print(F("HTTP/1.1 "));client->_client.print(code);client->_client.print(F(" "));client->_client.print(codeLiteral);client->_client.print(F("\r\nContent-Type: text/html\r\nContent-Length: ")); - client->_client.print(strlen(message) + 59); - client->_client.print(F("\r\n\r\n\r\n\r\n

Error "));client->_client.print(code);client->_client.print(F("

")); - client->_client.print(message); - client->_client.print(F("

\r\n")); + 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"), code, codeLiteral, strlen(message) + 56 + (code != 0 ? 3:1), code , message); } - /*Static helper methods*/ - static HttpRequestMethod getHttpVerbEnumValue(const char *parseBuffer) { if(parseBuffer == NULL)return HttpRequestMethod::UNDEFINED; @@ -523,43 +661,48 @@ class WEBServer : public TCPServer else return UNKNOWN_MIME; } - char *getHTTPHeader(HttpMIMEType httpMIMEType, unsigned long size) + static char *getHTTPHeader(HttpMIMEType httpMIMEType, size_t size) { - char *header = (char *) malloc(sizeof(char) + strlen("HTTP/1.1 200 OK\r\nContent-Type: \r\nContent-Length: \r\n\r\n") + 74/*Longest MIME-TYPE*/ + 10 /*Max unsigned long footprint*/ + 1); + char *header = (char *) malloc(sizeof(char) /*strlen("HTTP/1.1 200 OK\r\nContent-Type: \r\nContent-Length: \r\nCache-Control: max-age=31536000\r\n\r\n")*/* (86 + 74/*Longest MIME-TYPE*/ + 10 /*Max unsigned long footprint*/ + 1)); switch(httpMIMEType) { case TEXT_HTML: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/html",size); + injectHeaderLayout(header,"text/html", size); break; case TEXT_CSS: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/css",size); + injectHeaderLayout(header,"text/css", size); break; case TEXT_JAVASCRIPT: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/javascript",size); + injectHeaderLayout(header,"text/javascript", size); break; case IMAGE_PNG: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/png",size); + injectHeaderLayout(header,"image/png", size); break; case IMAGE_JPEG: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/jpeg",size); + injectHeaderLayout(header,"image/jpeg", size); break; case TEXT_PLAIN: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/plain",size); + injectHeaderLayout(header,"text/plain", size); break; case AUDIO_MPEG: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","audio/mpeg",size); + injectHeaderLayout(header,"audio/mpeg", size); break; case APPLICATION_OCTET_STREAM: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size); + injectHeaderLayout(header,"application/octet-stream", size); break; default: - sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size); + injectHeaderLayout(header,"application/octet-stream", size); break; } return header; } + + static void injectHeaderLayout(char *header, const char *contentType, size_t size) + { + sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\nCache-Control: max-age=31536000\r\n\r\n",contentType,size); + } static char *getFileExtension(char *name) {