Refactored multipart class

pull/193/head
cskarai 8 years ago committed by Thorsten von Eicken
parent 9e6d9932d3
commit c7ccfa33a0
  1. 7
      esp-link/cgiwebserver.c
  2. 146
      httpd/multipart.c
  3. 36
      httpd/multipart.h

@ -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);
}

@ -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)
{
if( len != 0 )
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)
{
os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, buff, 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;
@ -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;
}

@ -4,31 +4,29 @@
#include <httpd.h>
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 */

Loading…
Cancel
Save