Multipart upload implementation

pull/193/head
Karai Csaba 9 years ago committed by Thorsten von Eicken
parent aa35923863
commit 40e7fba786
  1. 31
      esp-link/cgiwebserver.c
  2. 8
      esp-link/cgiwebserver.h
  3. 2
      esp-link/main.c
  4. 12
      html/web-server.html
  5. 2
      httpd/httpd.c
  6. 190
      httpd/multipart.c
  7. 33
      httpd/multipart.h

@ -0,0 +1,31 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
#include <esp8266.h>
#include <osapi.h>
#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);
}

@ -0,0 +1,8 @@
#ifndef CGIWEBSERVER_H
#define CGIWEBSERVER_H
#include <httpd.h>
int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData);
#endif /* CGIWEBSERVER_H */

@ -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"
@ -97,6 +98,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 }
};

@ -4,9 +4,15 @@
</div>
<div class="content">
<p>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.</p>
</div>
<p>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.</p>
<form method="post" action="web-server/upload" name="submit" enctype="multipart/form-data">
The custom web page to upload: <input type="file" name="webpage">
<input type="submit" name="submit" value="Submit">
</form>
</div>
</div>

@ -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.

@ -0,0 +1,190 @@
#include <esp8266.h>
#include <osapi.h>
#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;
}
}

@ -0,0 +1,33 @@
#ifndef MULTIPART_H
#define MULTIPART_H
#include <httpd.h>
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 */
Loading…
Cancel
Save