From a1b4554dfb2a22f6ee5d430847a437c3e1dd54cb Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 30 Apr 2016 19:45:36 +0200 Subject: [PATCH] Multipart upload implementation --- esp-link/cgiwebserver.c | 31 +++++++ esp-link/cgiwebserver.h | 8 ++ esp-link/main.c | 2 + html/web-server.html | 12 ++- httpd/httpd.c | 2 + httpd/multipart.c | 190 ++++++++++++++++++++++++++++++++++++++++ httpd/multipart.h | 33 +++++++ 7 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 esp-link/cgiwebserver.c create mode 100644 esp-link/cgiwebserver.h create mode 100644 httpd/multipart.c create mode 100644 httpd/multipart.h diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c new file mode 100644 index 0000000..194b585 --- /dev/null +++ b/esp-link/cgiwebserver.c @@ -0,0 +1,31 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo + +#include +#include +#include "cgi.h" +#include "cgioptiboot.h" +#include "multipart.h" + +void webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) +{ + switch(cmd) + { + case FILE_START: + os_printf("CB: File start: %s\n", data); + break; + case FILE_DATA: + os_printf("CB: Data (%d): %s\n", position, data); + break; + case FILE_DONE: + os_printf("CB: Done\n"); + break; + } +} + +MultipartCtx webServerContext = {.callBack = webServerMultipartCallback, .position = 0, .recvPosition = 0, .startTime = 0, .boundaryBuffer = NULL}; + +int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) +{ + os_printf("WebServer upload\n"); + return multipartProcess(&webServerContext, connData); +} diff --git a/esp-link/cgiwebserver.h b/esp-link/cgiwebserver.h new file mode 100644 index 0000000..e3365be --- /dev/null +++ b/esp-link/cgiwebserver.h @@ -0,0 +1,8 @@ +#ifndef CGIWEBSERVER_H +#define CGIWEBSERVER_H + +#include + +int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData); + +#endif /* CGIWEBSERVER_H */ diff --git a/esp-link/main.c b/esp-link/main.c index afaa946..a5f78aa 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -19,6 +19,7 @@ #include "cgimqtt.h" #include "cgiflash.h" #include "cgioptiboot.h" +#include "cgiwebserver.h" #include "auth.h" #include "espfs.h" #include "uart.h" @@ -96,6 +97,7 @@ HttpdBuiltInUrl builtInUrls[] = { #ifdef MQTT { "/mqtt", cgiMqtt, NULL }, #endif + { "/web-server/upload", cgiWebServerUpload, NULL }, { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { NULL, NULL, NULL } }; diff --git a/html/web-server.html b/html/web-server.html index f509230..c73e59c 100644 --- a/html/web-server.html +++ b/html/web-server.html @@ -4,9 +4,15 @@
-

User defined web pages can be uploaded to esp-link. This is useful if esp-link acts as a web server while MCU provides - the measurement data.

-
+

User defined web pages can be uploaded to esp-link. This is useful if esp-link acts as a web server while MCU provides + the measurement data.

+
+ The custom web page to upload: + +
+ + + \ No newline at end of file diff --git a/httpd/httpd.c b/httpd/httpd.c index f3c5594..8602ce3 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -132,6 +132,7 @@ static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) { if (conn->post->buff != NULL) os_free(conn->post->buff); conn->cgi = NULL; conn->post->buff = NULL; + conn->post->multipartBoundary = NULL; } //Stupid li'l helper function that returns the value of a hex char. @@ -509,6 +510,7 @@ static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) { //Indicate we're done with the headers. conn->post->len = 0; + conn->post->multipartBoundary = NULL; //Reset url data conn->url = NULL; //Iterate over all received headers and parse them. diff --git a/httpd/multipart.c b/httpd/multipart.c new file mode 100644 index 0000000..e4a99a4 --- /dev/null +++ b/httpd/multipart.c @@ -0,0 +1,190 @@ +#include +#include + +#include "multipart.h" +#include "cgi.h" + +#define BOUNDARY_SIZE 100 + +void multipartAllocBoundaryBuffer(MultipartCtx * context) +{ + if( context->boundaryBuffer == NULL ) + context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1); + context->boundaryBufferPtr = 0; +} + +void multipartFreeBoundaryBuffer(MultipartCtx * context) +{ + if( context->boundaryBuffer != NULL ) + { + os_free(context->boundaryBuffer); + context->boundaryBuffer = NULL; + } +} + +void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +{ + if( len != 0 ) + { + os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, buff, len); + + context->boundaryBufferPtr += len; + context->boundaryBuffer[context->boundaryBufferPtr] = 0; + } + + while( context->boundaryBufferPtr > 0 ) + { + if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) + return; + + int dataSize = BOUNDARY_SIZE; + + char * loc = os_strstr( context->boundaryBuffer, boundary ); + if( loc != NULL ) + { + int pos = loc - context->boundaryBuffer; + if( pos > BOUNDARY_SIZE ) + loc = NULL; + else + dataSize = pos; + } + + if( dataSize != 0 ) + { + switch( context->state ) + { + case STATE_SEARCH_HEADER: + case STATE_SEARCH_HEADER_END: + { + char * chr = os_strchr( context->boundaryBuffer, '\n' ); + if( chr != NULL ) + { + int pos = chr - context->boundaryBuffer + 1; + if( pos < dataSize ) + { + dataSize = pos; + loc = NULL; // this is not yet the boundary + } + 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++; + if( context->boundaryBuffer[pos] == '"' ) + { + pos++; + int start = pos; + while( pos < context->boundaryBufferPtr ) + { + if( context->boundaryBuffer[pos] == '"' ) + break; + pos++; + } + if( pos < context->boundaryBufferPtr ) + { + context->boundaryBuffer[pos] = 0; + os_printf("Uploading file: %s\n", context->boundaryBuffer + start); + context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ); + context->boundaryBuffer[pos] = '"'; + 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) + context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ); + context->boundaryBuffer[dataSize] = c; + context->position += dataSize; + } + break; + default: + break; + } + } + + if( loc != NULL ) + { + dataSize += os_strlen(boundary); + if( context->state == STATE_UPLOAD_FILE ) + { + context->callBack( FILE_DONE, NULL, 0, context->position ); + os_printf("File upload done\n"); + } + + context->state = STATE_SEARCH_HEADER; + } + + context->boundaryBufferPtr -= dataSize; + os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr); + } +} + +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 different request + context->position = 0; + context->recvPosition = 0; + context->startTime = connData->startTime; + context->state = STATE_SEARCH_BOUNDARY; + + multipartAllocBoundaryBuffer(context); + } + + int feed = 0; + while( feed < post->buffLen ) + { + int len = post->buffLen - feed; + if( len > BOUNDARY_SIZE ) + len = BOUNDARY_SIZE; + multipartProcessBoundaryBuffer(context, post->multipartBoundary, post->buff + feed, len, 0); + feed += len; + } + + context->recvPosition += post->buffLen; + if( context->recvPosition < post->len ) + return HTTPD_CGI_MORE; + + multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1); + multipartFreeBoundaryBuffer( context ); + + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + else { + errorResponse(connData, 404, "Only multipart POST is supported"); + return HTTPD_CGI_DONE; + } +} diff --git a/httpd/multipart.h b/httpd/multipart.h new file mode 100644 index 0000000..76e15c4 --- /dev/null +++ b/httpd/multipart.h @@ -0,0 +1,33 @@ +#ifndef MULTIPART_H +#define MULTIPART_H + +#include + +typedef enum { + FILE_START, + FILE_DATA, + FILE_DONE, +} MultipartCmd; + +typedef enum { + STATE_SEARCH_BOUNDARY = 0, + STATE_SEARCH_HEADER, + STATE_SEARCH_HEADER_END, + STATE_UPLOAD_FILE +} MultipartState; + +typedef void (* 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; + +int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * post ); + +#endif /* MULTIPART_H */