123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- #include <esp8266.h>
- #include <osapi.h>
- #include "multipart.h"
- #include "cgi.h"
- #define BOUNDARY_SIZE 128
- 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 )
- context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1);
- context->boundaryBufferPtr = 0;
- }
- // for freeing multipart buffer
- void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context)
- {
- if( context->boundaryBuffer != NULL )
- {
- os_free(context->boundaryBuffer);
- context->boundaryBuffer = NULL;
- }
- }
- // 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 ) // add data to the boundary buffer
- {
- os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len);
-
- context->boundaryBufferPtr += len;
- context->boundaryBuffer[context->boundaryBufferPtr] = 0;
- }
-
- while( context->boundaryBufferPtr > 0 )
- {
- 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 * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) );
- if( boundaryLoc != NULL )
- {
- int pos = boundaryLoc - context->boundaryBuffer;
- if( pos > BOUNDARY_SIZE ) // process in the next call
- boundaryLoc = NULL;
- else
- dataSize = pos;
- }
-
- if( dataSize != 0 ) // data to process
- {
- switch( context->state )
- {
- case STATE_SEARCH_HEADER:
- case STATE_SEARCH_HEADER_END:
- {
- 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 chop smaller than the dataSize, delete the boundary
- {
- dataSize = pos;
- boundaryLoc = NULL; // process boundary next time
- }
- if( context->state == STATE_SEARCH_HEADER_END )
- {
- if( pos == 1 || ( ( pos == 2 ) && ( context->boundaryBuffer[0] == '\r' ) ) ) // empty line?
- {
- context->state = STATE_UPLOAD_FILE;
- context->position = 0;
- }
- }
- else if( os_strncmp( context->boundaryBuffer, "Content-Disposition:", 20 ) == 0 )
- {
- char * fnam = os_strstr( context->boundaryBuffer, "filename=" );
- if( fnam != NULL )
- {
- int pos = fnam - context->boundaryBuffer + 9;
- if( pos < dataSize )
- {
- 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] == '"' ) // quote end
- break;
- pos++;
- }
- if( pos < context->boundaryBufferPtr )
- {
- 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 ) ) // FILE_START callback
- return 1; // if an error happened
- context->boundaryBuffer[pos] = '"'; // restore the original quote
- context->state = STATE_SEARCH_HEADER_END;
- }
- }
- }
- }
- }
- }
- }
- break;
- case STATE_UPLOAD_FILE:
- {
- 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 ) ) // FILE_DATA callback
- return 1;
- context->boundaryBuffer[dataSize] = c;
- context->position += dataSize;
- }
- break;
- default:
- break;
- }
- }
-
- if( boundaryLoc != NULL ) // boundary found?
- {
- dataSize += os_strlen(boundary); // jump over the boundary
- if( context->state == STATE_UPLOAD_FILE )
- {
- 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; // 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.
-
- if (connData->requestType == HTTPD_METHOD_POST) {
- HttpdPostData *post = connData->post;
-
- if( post->multipartBoundary == NULL )
- {
- errorResponse(connData, 404, "Only multipart POST is supported");
- return HTTPD_CGI_DONE;
- }
- if( connData->startTime != context->startTime )
- {
- // reinitialize, as this is a new request
- context->position = 0;
- context->recvPosition = 0;
- context->startTime = connData->startTime;
- context->state = STATE_SEARCH_BOUNDARY;
-
- multipartAllocBoundaryBuffer(context);
-
- if( context->callBack( FILE_UPLOAD_START, NULL, 0, context->position ) ) // start uploading files
- context->state = STATE_ERROR;
- }
- if( context->state != STATE_ERROR )
- {
- int feed = 0;
- while( feed < post->buffLen )
- {
- int len = post->buffLen - feed;
- if( len > BOUNDARY_SIZE )
- len = BOUNDARY_SIZE;
- if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) )
- {
- context->state = STATE_ERROR;
- break;
- }
- feed += len;
- }
- }
-
- context->recvPosition += post->buffLen;
- if( context->recvPosition < post->len )
- return HTTPD_CGI_MORE;
- if( context->state != STATE_ERROR )
- {
- // this is the last package, process the remaining data
- if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) )
- context->state = STATE_ERROR;
- else if( context->callBack( FILE_UPLOAD_DONE, NULL, 0, context->position ) ) // done with files
- context->state = STATE_ERROR;
- }
-
- multipartFreeBoundaryBuffer( context );
-
- if( context->state == STATE_ERROR )
- errorResponse(connData, 400, "Invalid file upload!");
- else
- {
- httpdStartResponse(connData, 204);
- httpdEndHeaders(connData);
- }
- return HTTPD_CGI_DONE;
- }
- else {
- errorResponse(connData, 404, "Only multipart POST is supported");
- return HTTPD_CGI_DONE;
- }
- }
|