diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index cd3a61d..2c8b6b4 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -139,9 +139,12 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i return 0; } -MultipartCtx webServerContext = {.callBack = webServerMultipartCallback, .position = 0, .recvPosition = 0, .startTime = 0, .boundaryBuffer = NULL}; +MultipartCtx * webServerContext = NULL; int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) { - return multipartProcess(&webServerContext, connData); + if( webServerContext == NULL ) + webServerContext = multipartCreateContext( webServerMultipartCallback ); + + return multipartProcess(webServerContext, connData); } diff --git a/httpd/multipart.c b/httpd/multipart.c index 1198579..d709798 100644 --- a/httpd/multipart.c +++ b/httpd/multipart.c @@ -6,6 +6,36 @@ #define BOUNDARY_SIZE 100 +typedef enum { + STATE_SEARCH_BOUNDARY = 0, // state: searching multipart boundary + STATE_SEARCH_HEADER, // state: search multipart file header + STATE_SEARCH_HEADER_END, // state: search the end of the file header + STATE_UPLOAD_FILE, // state: read file content + STATE_ERROR, // state: error (stop processing) +} MultipartState; + +struct _MultipartCtx { + MultipartCallback callBack; // callback for multipart events + int position; // current file position + int startTime; // timestamp when connection was initiated + int recvPosition; // receive position (how many bytes was processed from the HTTP post) + char * boundaryBuffer; // buffer used for boundary detection + int boundaryBufferPtr; // pointer in the boundary buffer + MultipartState state; // multipart processing state +}; + +// this method is responsible for creating the multipart context +MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback) +{ + MultipartCtx * ctx = (MultipartCtx *)os_malloc(sizeof(MultipartCtx)); + ctx->callBack = callback; + ctx->position = ctx->startTime = ctx->recvPosition = ctx->boundaryBufferPtr = 0; + ctx->boundaryBuffer = NULL; + ctx->state = STATE_SEARCH_BOUNDARY; + return ctx; +} + +// for allocating buffer for multipart upload void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context) { if( context->boundaryBuffer == NULL ) @@ -13,6 +43,7 @@ void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context) context->boundaryBufferPtr = 0; } +// for freeing multipart buffer void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context) { if( context->boundaryBuffer != NULL ) @@ -22,11 +53,66 @@ void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context) } } -int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +// for destroying the context +void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context) +{ + multipartFreeBoundaryBuffer(context); + os_free(context); +} + +// this is because of os_memmem is missing +void * mp_memmem(const void *l, size_t l_len, const void *s, size_t s_len) +{ + register char *cur, *last; + const char *cl = (const char *)l; + const char *cs = (const char *)s; + + /* we need something to compare */ + if (l_len == 0 || s_len == 0) + return NULL; + + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, (int)*cs, l_len); + + /* the last position where its possible to find "s" in "l" */ + last = (char *)cl + l_len - s_len; + + for (cur = (char *)cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return cur; + + return NULL; +} + + +// this method is for processing data coming from the HTTP post request +// context: the multipart context +// boundary: a string which indicates boundary +// data: the received data +// len: the received data length (can't be bigger than BOUNDARY_SIZE) +// last: last packet indicator +// +// Detecting a boundary is not easy. One has to take care of boundaries which are splitted in 2 packets +// [Packet 1, 5 bytes of the boundary][Packet 2, remaining 10 bytes of the boundary]; +// +// Algorythm: +// - create a buffer which size is 3*BOUNDARY_SIZE +// - put data into the buffer as long as the buffer size is smaller than 2*BOUNDARY_SIZE +// - search boundary in the received buffer, if found: boundary reached -> process data before boundary -> process boundary +// - if not found -> process the first BOUNDARY_SIZE amount of bytes from the buffer +// - remove processed data from the buffer +// this algorythm guarantees that no boundary loss will happen + +int ICACHE_FLASH_ATTR multipartProcessData(MultipartCtx * context, char * boundary, char * data, int len, int last) { - if( len != 0 ) + if( len != 0 ) // add data to the boundary buffer { - os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, buff, len); + os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len); context->boundaryBufferPtr += len; context->boundaryBuffer[context->boundaryBufferPtr] = 0; @@ -34,22 +120,22 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha while( context->boundaryBufferPtr > 0 ) { - if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) + if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) // return if buffer is too small and not the last packet is processed return 0; int dataSize = BOUNDARY_SIZE; - char * loc = os_strstr( context->boundaryBuffer, boundary ); - if( loc != NULL ) + char * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) ); + if( boundaryLoc != NULL ) { - int pos = loc - context->boundaryBuffer; - if( pos > BOUNDARY_SIZE ) - loc = NULL; + int pos = boundaryLoc - context->boundaryBuffer; + if( pos > BOUNDARY_SIZE ) // process in the next call + boundaryLoc = NULL; else dataSize = pos; } - if( dataSize != 0 ) + if( dataSize != 0 ) // data to process { switch( context->state ) { @@ -59,11 +145,12 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha char * chr = os_strchr( context->boundaryBuffer, '\n' ); if( chr != NULL ) { + // chop datasize to contain only one line int pos = chr - context->boundaryBuffer + 1; - if( pos < dataSize ) + if( pos < dataSize ) // if chop smaller than the dataSize, delete the boundary { dataSize = pos; - loc = NULL; // this is not yet the boundary + boundaryLoc = NULL; // process boundary next time } if( context->state == STATE_SEARCH_HEADER_END ) { @@ -81,24 +168,24 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha int pos = fnam - context->boundaryBuffer + 9; if( pos < dataSize ) { - while(context->boundaryBuffer[pos] == ' ') pos++; - if( context->boundaryBuffer[pos] == '"' ) + while(context->boundaryBuffer[pos] == ' ') pos++; // skip spaces + if( context->boundaryBuffer[pos] == '"' ) // quote start { pos++; int start = pos; while( pos < context->boundaryBufferPtr ) { - if( context->boundaryBuffer[pos] == '"' ) + if( context->boundaryBuffer[pos] == '"' ) // quote end break; pos++; } if( pos < context->boundaryBufferPtr ) { - context->boundaryBuffer[pos] = 0; + context->boundaryBuffer[pos] = 0; // terminating zero for the file name os_printf("Uploading file: %s\n", context->boundaryBuffer + start); - if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) - return 1; - context->boundaryBuffer[pos] = '"'; + if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) // FILE_START callback + return 1; // if an error happened + context->boundaryBuffer[pos] = '"'; // restore the original quote context->state = STATE_SEARCH_HEADER_END; } } @@ -112,7 +199,7 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha { char c = context->boundaryBuffer[dataSize]; context->boundaryBuffer[dataSize] = 0; // add terminating zero (for easier handling) - if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) + if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) // FILE_DATA callback return 1; context->boundaryBuffer[dataSize] = c; context->position += dataSize; @@ -123,25 +210,27 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha } } - if( loc != NULL ) + if( boundaryLoc != NULL ) // boundary found? { - dataSize += os_strlen(boundary); + dataSize += os_strlen(boundary); // jump over the boundary if( context->state == STATE_UPLOAD_FILE ) { - if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) - return 1; + if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) // file done callback + return 1; // if an error happened os_printf("File upload done\n"); } - context->state = STATE_SEARCH_HEADER; + context->state = STATE_SEARCH_HEADER; // search the next header } + // move the buffer back with dataSize context->boundaryBufferPtr -= dataSize; os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr); } return 0; } +// for processing multipart requests int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * connData ) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -157,7 +246,7 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c if( connData->startTime != context->startTime ) { - // reinitialize, as this is a different request + // reinitialize, as this is a new request context->position = 0; context->recvPosition = 0; context->startTime = connData->startTime; @@ -174,7 +263,7 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c int len = post->buffLen - feed; if( len > BOUNDARY_SIZE ) len = BOUNDARY_SIZE; - if( multipartProcessBoundaryBuffer(context, post->multipartBoundary, post->buff + feed, len, 0) ) + if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) ) { context->state = STATE_ERROR; break; @@ -189,7 +278,8 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c if( context->state != STATE_ERROR ) { - if( multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1) ) + // this is the last package, process the remaining data + if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) ) context->state = STATE_ERROR; } diff --git a/httpd/multipart.h b/httpd/multipart.h index d22815d..87a4bb2 100644 --- a/httpd/multipart.h +++ b/httpd/multipart.h @@ -4,31 +4,29 @@ #include typedef enum { - FILE_START, - FILE_DATA, - FILE_DONE, + FILE_START, // multipart: the start of a new file + FILE_DATA, // multipart: file data + FILE_DONE, // multipart: file end } MultipartCmd; -typedef enum { - STATE_SEARCH_BOUNDARY = 0, - STATE_SEARCH_HEADER, - STATE_SEARCH_HEADER_END, - STATE_UPLOAD_FILE, - STATE_ERROR, -} MultipartState; +// multipart callback +// -> FILE_START : data+dataLen contains the filename, position is 0 +// -> FILE_DATA : data+dataLen contains file data, position is the file position +// -> FILE_DONE : data+dataLen is 0, position is the complete file size typedef int (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position); -typedef struct { - MultipartCallback callBack; - int position; - int startTime; - int recvPosition; - char * boundaryBuffer; - int boundaryBufferPtr; - MultipartState state; -} MultipartCtx; +struct _MultipartCtx; // the context for multipart listening + +typedef struct _MultipartCtx MultipartCtx; + +// use this for creating a multipart context +MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback); + +// for destroying multipart context +void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context); +// use this function for processing HTML multipart updates int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * post ); #endif /* MULTIPART_H */