aboutsummaryrefslogtreecommitdiff
path: root/picohttp.c
diff options
context:
space:
mode:
Diffstat (limited to 'picohttp.c')
-rw-r--r--picohttp.c722
1 files changed, 678 insertions, 44 deletions
diff --git a/picohttp.c b/picohttp.c
index 43d4652..476630e 100644
--- a/picohttp.c
+++ b/picohttp.c
@@ -1,21 +1,47 @@
#include "picohttp.h"
+#define picohttpIoWrite(ioops,size,buf) (ioops->write(size, buf, ioops->data))
+#define picohttpIoRead(ioops,size,buf) (ioops->read(size, buf, ioops->data))
+#define picohttpIoGetch(ioops) (ioops->getch(ioops->data))
+#define picohttpIoPutch(ioops,c) (ioops->putch(c, ioops->data))
+#define picohttpIoFlush(ioops) (ioops->flush(ioops->data))
+
+#ifdef HOST_DEBUG
+#include <stdio.h>
+#define debug_printf(...) do{fprintf(stderr, __VA_ARGS__);}while(0)
+#define debug_putc(x) do{fputc(x, stderr);}while(0)
+#else
+#define debug_printf(...) do{}while(0)
+#define debug_putc(x) do{}while(0)
+#endif
+
#include <alloca.h>
#include <string.h>
+#include <stdlib.h>
static char const PICOHTTP_STR_CRLF[] = "\r\n";
static char const PICOHTTP_STR_CLSP[] = ": ";
static char const PICOHTTP_STR_HTTP_[] = "HTTP/";
+static char const PICOHTTP_STR_HOST[] = "Host";
static char const PICOHTTP_STR_SERVER[] = "Server";
static char const PICOHTTP_STR_PICOWEB[] = "picoweb/0.1";
static char const PICOHTTP_STR_ACCEPT[] = "Accept";
+static char const PICOHTTP_STR_TRANSFER[] = "Transfer";
+
static char const PICOHTTP_STR__ENCODING[] = "-Encoding";
static char const PICOHTTP_STR_CONTENT[] = "Content";
static char const PICOHTTP_STR__TYPE[] = "-Type";
static char const PICOHTTP_STR__LENGTH[] = "-Length";
static char const PICOHTTP_STR__CODING[] = "-Coding";
+static char const PICOHTTP_STR__DISPOSITION[] = "-Disposition";
+
+static char const PICOHTTP_STR_APPLICATION_[] = "application/";
+static char const PICOHTTP_STR_TEXT_[] = "text/";
+static char const PICOHTTP_STR_MULTIPART_[] = "multipart/";
+
+static char const PICOHTTP_STR_FORMDATA[] = "form-data";
static char const PICOHTTP_STR_CACHECONTROL[] = "Cache-Control";
@@ -23,9 +49,21 @@ static char const PICOHTTP_STR_CONNECTION[] = "Connection";
static char const PICOHTTP_STR_CLOSE[] = "close";
static char const PICOHTTP_STR_DATE[] = "Date";
-
static char const PICOHTTP_STR_EXPECT[] = "Expect";
+static char const PICOHTTP_STR_BOUNDARY[] = " boundary=";
+static char const PICOHTTP_STR_NAME__[] = " name=\"";
+
+static char const PICOHTTP_STR_CHUNKED[] = "chunked";
+
+/* compilation unit local function forward declarations */
+static int picohttpProcessHeaders (
+ struct picohttpRequest * const req,
+ picohttpHeaderFieldCallback headerfieldcallback,
+ void * const data,
+ int ch );
+
+/* compilation unit local helper functions */
#if !defined(PICOHTTP_CONFIG_HAVE_LIBDJB)
/* Number formating functions modified from libdjb by
* Daniel J. Bernstein, packaged at http://www.fefe.de/djb/
@@ -61,7 +99,7 @@ static size_t picohttp_fmt_int(char *dest,int i) {
#define picohttp_fmt_int fmt_long
#endif
-static char const * const picohttpStatusString(int16_t code)
+static char const * const picohttpStatusString(int code)
{
switch(code) {
case 200:
@@ -83,14 +121,14 @@ static char const * const picohttpStatusString(int16_t code)
}
void picohttpStatusResponse(
- struct picohttpRequest *req, int16_t status )
+ struct picohttpRequest *req, int status )
{
req->status = status;
char const * const c = picohttpStatusString(req->status);
picohttpResponseWrite(req, strlen(c), c);
}
-static uint8_t picohttpIsCRLF(int16_t ch)
+static uint8_t picohttpIsCRLF(int ch)
{
switch(ch) {
case '\r':
@@ -100,37 +138,42 @@ static uint8_t picohttpIsCRLF(int16_t ch)
return 0;
}
-static uint8_t picohttpIsLWS(int16_t ch)
+static uint8_t picohttpIsLWS(int ch)
{
return
picohttpIsCRLF(ch) ||
' ' == ch || '\t' == ch;
}
-static int16_t picohttpIoSkipSpace (
+static int picohttpIoSkipSpace (
struct picohttpIoOps const * const ioops,
- int16_t ch)
+ int ch)
{
- for(;;ch = 0) {
- if(!ch)
+ for(;;ch = -1) {
+ if(0 > ch) {
ch = picohttpIoGetch(ioops);
- if( 0 >= ch ||
+ }
+
+ if( 0 > ch ||
( ' ' != ch && '\t' != ch ) )
break;
}
return ch;
}
-static int16_t picohttpIoSkipOverCRLF (
+static int picohttpIoSkipOverCRLF (
struct picohttpIoOps const * const ioops,
- int16_t ch)
+ int ch)
{
- for(;;ch = 0) {
- if(!ch)
+ for(;;ch = -1) {
+ if(0 > ch) {
ch = picohttpIoGetch(ioops);
+ }
+
if( ch < 0 ) {
return -1;
}
+
if( ch == '\n' ) {
break;
}
@@ -149,12 +192,29 @@ static int16_t picohttpIoSkipOverCRLF (
return ch;
}
-static int16_t picohttpIoB10ToU8 (
+static int picohttpIoB10ToU8 (
uint8_t *i,
struct picohttpIoOps const * const ioops,
- int16_t ch )
+ int ch )
+{
+ if( 0 > ch )
+ ch = picohttpIoGetch(ioops);
+
+ while( ch >= '0' && ch <= '9' ) {
+ *i *= 10;
+ *i += (ch & 0x0f);
+ ch = picohttpIoGetch(ioops);
+ }
+
+ return ch;
+}
+
+static int picohttpIoB10ToU64 (
+ uint64_t *i,
+ struct picohttpIoOps const * const ioops,
+ int ch )
{
- if( !ch )
+ if( 0 > ch )
ch = picohttpIoGetch(ioops);
while( ch >= '0' && ch <= '9' ) {
@@ -166,11 +226,11 @@ static int16_t picohttpIoB10ToU8 (
return ch;
}
-static int16_t picohttpIoGetPercentCh(
+static int picohttpIoGetPercentCh(
struct picohttpIoOps const * const ioops )
{
char ch=0;
- int16_t chr;
+ int chr;
if( 0 > (chr = picohttpIoGetch(ioops)))
return chr;
@@ -196,11 +256,168 @@ static int16_t picohttpIoGetPercentCh(
return ch;
}
+int picohttpGetch(struct picohttpRequest * const req)
+{
+ int ch;
+ /* skipping over Chunked Transfer Boundaries
+ * if Chunked Transfer Encoding is used */
+ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) {
+ if( !req->query.chunklength ) {
+ /* this is a new chunk;
+ * read the length and skip to after <CR><LF> */
+ if( 0 > (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ uint64_t len;
+ if( 0 > (ch = picohttpIoB10ToU64(
+ &len,
+ req->ioops,
+ ch))
+ ) {
+ return ch;
+ }
+ if( 0 > (ch = picohttpIoSkipOverCRLF(req->ioops, ch)) ) {
+ return ch;
+ }
+ req->query.chunklength = len;
+ return ch;
+ }
+
+ if( req->query.chunklength <= req->received_octets ) {
+ /* If this happens the data is corrupted, or
+ * the client is nonconforming, or an attack is
+ * underway, or something entierely different,
+ * or all of that.
+ * Abort processing the query!
+ */
+ return -1;
+ }
+ }
+
+ if( 0 <= (ch = picohttpIoGetch(req->ioops)) ) {
+ memmove(req->query.prev_ch + 1, req->query.prev_ch, 4);
+ req->query.prev_ch[0] = ch;
+ req->received_octets++;
+ } else {
+ return ch;
+ }
+
+ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) {
+ if( !req->query.chunklength <= req->received_octets ) {
+ /* end of chunk;
+ * skip over <CR><LF>, make sure the trailing '0' is
+ * there and read the headers, err, I mean footers
+ * (whatever, the header processing code will do for
+ * chunk footers just fine).
+ */
+ if( '0' != (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ if( 0 > (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ if( 0 > (ch = picohttpProcessHeaders(
+ req,
+ NULL, NULL,
+ ch))
+ ) {
+ return ch;
+ }
+
+ req->received_octets =
+ req->query.chunklength = 0;
+ }
+ }
+
+ return ch;
+}
+
+int picohttpRead(struct picohttpRequest * const req, size_t len, char * const buf)
+{
+ /* skipping over Chunked Transfer Boundaries
+ * if Chunked Transfer Encoding is used */
+ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) {
+ if( !req->query.chunklength ) {
+ int ch;
+ /* this is a new chunk;
+ * read the length and skip to after <CR><LF> */
+ if( 0 > (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ uint64_t len;
+ if( 0 > (ch = picohttpIoB10ToU64(
+ &len,
+ req->ioops,
+ ch))
+ ) {
+ return ch;
+ }
+ if( 0 > (ch = picohttpIoSkipOverCRLF(req->ioops, ch)) ) {
+ return ch;
+ }
+ req->query.chunklength = len;
+ return ch;
+ }
+
+ if( req->query.chunklength <= req->received_octets ) {
+ /* If this happens the data is corrupted, or
+ * the client is nonconforming, or an attack is
+ * underway, or something entierely different,
+ * or all of that.
+ * Abort processing the query!
+ */
+ return -1;
+ }
+ }
+
+ if( req->received_octets + len > req->query.chunklength ) {
+ len = req->query.chunklength - req->received_octets;
+ }
+
+ int r = picohttpIoRead(req->ioops, len, buf);
+ if( 5 < r ) {
+ memmove(req->query.prev_ch + r, req->query.prev_ch, 5-r);
+ memcpy(req->query.prev_ch, buf, r);
+ } else if (0 < r) {
+ memcpy(req->query.prev_ch, buf + len - 5, 5);
+ }
+
+ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) {
+ if( !req->query.chunklength <= req->received_octets ) {
+ int ch;
+ /* end of chunk;
+ * skip over <CR><LF>, make sure the trailing '0' is
+ * there and read the headers, err, I mean footers
+ * (whatever, the header processing code will do for
+ * chunk footers just fine).
+ */
+ if( '0' != (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ if( 0 > (ch = picohttpIoGetch(req->ioops)) ) {
+ return -1;
+ }
+ if( 0 > (ch = picohttpProcessHeaders(
+ req,
+ NULL, NULL,
+ ch))
+ ) {
+ return ch;
+ }
+
+ req->received_octets =
+ req->query.chunklength = 0;
+ }
+ }
+
+ return r;
+}
+
/* TODO:
* It is possible to do in-place pattern matching on the route definition
* array, without first reading in the URL and then processing it here.
*
- * Implement this to imporove memory footprint reduction.
+ * Implement this to improve memory footprint reduction.
*/
static size_t picohttpMatchURL(
char const * const urlhead,
@@ -236,7 +453,7 @@ static size_t picohttpMatchURL(
return j;
}
-static int8_t picohttpMatchRoute(
+static int picohttpMatchRoute(
struct picohttpRequest * const req,
struct picohttpURLRoute const * const routes )
{
@@ -253,10 +470,10 @@ static int8_t picohttpMatchRoute(
return 0;
}
-static int16_t picohttpProcessRequestMethod (
+static int picohttpProcessRequestMethod (
struct picohttpIoOps const * const ioops )
{
- int16_t method = 0;
+ int method = 0;
/* Poor man's string matching tree; trade RAM for code */
switch( picohttpIoGetch(ioops) ) {
@@ -316,10 +533,10 @@ static int16_t picohttpProcessRequestMethod (
return method;
}
-static int16_t picohttpProcessURL (
+static int picohttpProcessURL (
struct picohttpRequest * const req,
size_t const url_max_length,
- int16_t ch )
+ int ch )
{
/* copy url up to the first query component; note that this is not
* fully compliant to RFC 3986, which permits query components in each
@@ -355,9 +572,9 @@ static int16_t picohttpProcessURL (
return ch;
}
-static int16_t picohttpProcessQuery (
+static int picohttpProcessQuery (
struct picohttpRequest * const req,
- int16_t ch )
+ int ch )
{
size_t var_max_length = 0;
if(req->route->get_vars) {
@@ -423,6 +640,7 @@ static int16_t picohttpProcessQuery (
ch = picohttpIoGetch(req->ioops);
}
if( '=' == ch ) {
+ debug_printf("set variable '%s'\n", var);
} else {
}
}
@@ -433,9 +651,9 @@ static int16_t picohttpProcessQuery (
return ch;
}
-static int16_t picohttpProcessHTTPVersion (
+static int picohttpProcessHTTPVersion (
struct picohttpRequest * const req,
- int16_t ch )
+ int ch )
{
if( !picohttpIsCRLF(ch) ) {
@@ -464,7 +682,7 @@ static int16_t picohttpProcessHTTPVersion (
ch = picohttpIoB10ToU8(
&req->httpversion.minor,
req->ioops,
- 0 );
+ -1 );
if( ch < 0 ) {
return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
}
@@ -485,23 +703,172 @@ static int16_t picohttpProcessHTTPVersion (
return ch;
}
-static int16_t picohttpProcessHeaders (
+static int picohttpProcessContentType(
+ char const **contenttype)
+{
+ int ct = 0;
+ if(!strncmp(*contenttype,
+ PICOHTTP_STR_APPLICATION_, sizeof(PICOHTTP_STR_APPLICATION_)-1)) {
+ ct = PICOHTTP_CONTENTTYPE_APPLICATION;
+ }
+
+ if(!strncmp(*contenttype,
+ PICOHTTP_STR_TEXT_, sizeof(PICOHTTP_STR_TEXT_)-1)) {
+ ct = PICOHTTP_CONTENTTYPE_TEXT;
+ }
+
+ if(!strncmp(*contenttype, PICOHTTP_STR_MULTIPART_,
+ sizeof(PICOHTTP_STR_MULTIPART_)-1)) {
+ ct = PICOHTTP_CONTENTTYPE_MULTIPART;
+ *contenttype += sizeof(PICOHTTP_STR_MULTIPART_)-1;
+
+ if(!strncmp(*contenttype,PICOHTTP_STR_FORMDATA,
+ sizeof(PICOHTTP_STR_FORMDATA)-1)) {
+ *contenttype += sizeof(PICOHTTP_STR_FORMDATA)-1;
+
+ ct = PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA;
+ }
+ }
+
+ return ct;
+}
+
+static void picohttpProcessHeaderContentType(
struct picohttpRequest * const req,
- int16_t ch )
+ char const *contenttype )
{
-#define PICOHTTP_HEADERNAME_MAX_LEN 32
- char headername[PICOHTTP_HEADERNAME_MAX_LEN] = {0,};
+ req->query.contenttype = picohttpProcessContentType(&contenttype);
+
+ if( PICOHTTP_CONTENTTYPE_MULTIPART == (req->query.contenttype & 0xf000) ) {
+ char *boundary = strstr(contenttype, PICOHTTP_STR_BOUNDARY);
+ if(boundary) {
+ /* see RFC1521 regarding maximum length of boundary */
+ memset(req->query.multipartboundary, 0,
+ PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1);
+ memcpy(req->query.multipartboundary, "\r\n--", 4);
+ strncpy(req->query.multipartboundary+4,
+ boundary + sizeof(PICOHTTP_STR_BOUNDARY)-1,
+ PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN);
+ }
+ }
+}
+static void picohttpProcessHeaderField(
+ void * const data,
+ char const *headername,
+ char const *headervalue)
+{
+ struct picohttpRequest * const req = data;
+ debug_printf("%s: %s\n", headername, headervalue);
+ if(!strncmp(headername,
+ PICOHTTP_STR_CONTENT,
+ sizeof(PICOHTTP_STR_CONTENT)-1)) {
+ headername += sizeof(PICOHTTP_STR_CONTENT)-1;
+ /* Content Length */
+ if(!strncmp(headername,
+ PICOHTTP_STR__LENGTH, sizeof(PICOHTTP_STR__LENGTH)-1)) {
+ req->query.contentlength = atol(headervalue);
+ return;
+ }
- /* FIXME: Add Header handling here */
- while( !picohttpIsCRLF(ch) ) {
- while( !picohttpIsCRLF( ch=picohttpIoSkipSpace(req->ioops, ch)) ){
- if( 0 > ( ch=picohttpIoGetch(req->ioops) ) ) {
- return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
+ /* Content Type */
+ if(!strncmp(headername,
+ PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) {
+ picohttpProcessHeaderContentType(req, headervalue);
+ return;
+ }
+ return;
+ }
+
+ if(!strncmp(headername,
+ PICOHTTP_STR_TRANSFER,
+ sizeof(PICOHTTP_STR_TRANSFER)-1)) {
+ headername += sizeof(PICOHTTP_STR_TRANSFER)-1;
+ /* Transfer Encoding */
+ if(!strncmp(headername,
+ PICOHTTP_STR__ENCODING, sizeof(PICOHTTP_STR__ENCODING)-1)) {
+ if(!strncmp(headervalue,
+ PICOHTTP_STR_CHUNKED,
+ sizeof(PICOHTTP_STR_CHUNKED)-1)) {
+ req->query.transferencoding = PICOHTTP_CODING_CHUNKED;
+ req->query.chunklength = 0;
}
+ return;
}
+ return;
+ }
+}
+
+static int picohttpProcessHeaders (
+ struct picohttpRequest * const req,
+ picohttpHeaderFieldCallback headerfieldcallback,
+ void * const data,
+ int ch )
+{
+#define PICOHTTP_HEADERNAME_MAX_LEN 32
+ char headername[PICOHTTP_HEADERNAME_MAX_LEN+1] = {0,};
+#define PICOHTTP_HEADERVALUE_MAX_LEN 224
+ char headervalue[PICOHTTP_HEADERVALUE_MAX_LEN+1] = {0,};
+ char *hn;
+ char *hv;
+
+ /* TODO: Add Header handling here */
+ while( !picohttpIsCRLF(ch) ) {
+ /* Beginning of new header line */
+ if( 0 < ch && !picohttpIsCRLF(ch) ){
+ /* new header field OR field continuation */
+ if( picohttpIsLWS(ch) ) {
+ /* continuation */
+ /* skip space */
+ if( 0 > (ch = picohttpIoSkipSpace(req->ioops, ch)) )
+ return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
+
+ /* read until EOL */
+ for(;
+ 0 < ch && !picohttpIsCRLF(ch);
+ hv = (hv - headervalue) <
+ PICOHTTP_HEADERVALUE_MAX_LEN ?
+ hv+1 : 0 ) {
+ /* add to header field content */
+
+ if(hv)
+ *hv = ch;
- ch = picohttpIoSkipOverCRLF(req->ioops, ch);
+ ch = picohttpIoGetch(req->ioops);
+ }
+ } else {
+ if( *headername && *headervalue && headerfieldcallback )
+ headerfieldcallback(
+ data,
+ headername,
+ headervalue );
+ /* new header field */
+ memset(headername, 0, PICOHTTP_HEADERNAME_MAX_LEN+1);
+ hn = headername;
+
+ memset(headervalue, 0, PICOHTTP_HEADERVALUE_MAX_LEN+1);
+ hv = headervalue;
+ /* read until ':' or EOL */
+ for(;
+ 0 < ch && ':' != ch && !picohttpIsCRLF(ch);
+ hn = (hn - headername) <
+ PICOHTTP_HEADERNAME_MAX_LEN ?
+ hn+1 : 0 ) {
+ /* add to header name */
+ if(hn)
+ *hn = ch;
+
+ ch = picohttpIoGetch(req->ioops);
+ }
+ }
+ }
+ if( 0 > ch ) {
+ return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
+ }
+ if( picohttpIsCRLF(ch) )
+ ch = picohttpIoSkipOverCRLF(req->ioops, ch);
+ else
+ ch = picohttpIoGetch(req->ioops);
if( 0 > ch ) {
return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
}
@@ -509,6 +876,12 @@ static int16_t picohttpProcessHeaders (
return -PICOHTTP_STATUS_400_BAD_REQUEST;
}
}
+ if( *headername && *headervalue && headerfieldcallback)
+ headerfieldcallback(
+ data,
+ headername,
+ headervalue );
+
return ch;
}
@@ -517,7 +890,7 @@ void picohttpProcessRequest (
struct picohttpURLRoute const * const routes )
{
- int16_t ch;
+ int ch;
struct picohttpRequest request = {0,};
size_t url_max_length = 0;
@@ -548,6 +921,7 @@ void picohttpProcessRequest (
request.httpversion.minor = 0;
request.sent.header = 0;
request.sent.octets = 0;
+ request.received_octets = 0;
request.method = picohttpProcessRequestMethod(ioops);
if( !request.method ) {
@@ -559,7 +933,7 @@ void picohttpProcessRequest (
goto http_error;
}
- if( 0 > (ch = picohttpIoSkipSpace(ioops, 0)) ) {
+ if( 0 > (ch = picohttpIoSkipSpace(ioops, -1)) ) {
ch = -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR;
goto http_error;
}
@@ -567,7 +941,6 @@ void picohttpProcessRequest (
if( 0 > (ch = picohttpProcessURL(&request, url_max_length, ch)) )
goto http_error;
-
if( !picohttpMatchRoute(&request, routes) || !request.route ) {
ch = -PICOHTTP_STATUS_404_NOT_FOUND;
goto http_error;
@@ -589,9 +962,28 @@ void picohttpProcessRequest (
goto http_error;
}
- if( 0 > (ch = picohttpProcessHeaders(&request, ch)) )
+ if( 0 > (ch = picohttpProcessHeaders(
+ &request,
+ picohttpProcessHeaderField,
+ &request,
+ ch)) )
goto http_error;
+ if( '\r' == ch ) {
+ if( 0 > (ch = picohttpIoGetch(ioops)) )
+ goto http_error;
+ if( '\n' != ch ) {
+ ch = -PICOHTTP_STATUS_400_BAD_REQUEST;
+ goto http_error;
+ }
+ }
+
+ request.query.prev_ch[0] = '\n';
+ request.query.prev_ch[1] = '\r';
+ request.query.prev_ch[2] =
+ request.query.prev_ch[3] =
+ request.query.prev_ch[4] = 0;
+
request.status = PICOHTTP_STATUS_200_OK;
request.route->handler(&request);
@@ -721,3 +1113,245 @@ int picohttpResponseWrite (
return len;
}
+int picohttpMultipartGetch(
+ struct picohttpMultipart * const mp)
+{
+ int ch;
+ if( mp->finished ) {
+ return -1;
+ } else
+ if( 0 <= mp->mismatch ) {
+replay:
+ if( mp->replayhead < mp->replay ) {
+ ch = mp->req->query.multipartboundary[mp->replayhead];
+ mp->replayhead++;
+ return ch;
+ } else {
+ ch = mp->mismatch;
+ mp->mismatch = -1;
+ mp->replayhead = 0;
+ return ch;
+ }
+ } else {
+ ch = picohttpGetch(mp->req);
+
+ /* picohttp's query and header parsing is forgiving
+ * regarding line termination. <CR><LF> or just <LF>
+ * are accepted.
+ * However multipart boundaries are to start with
+ * a <CR><LF> sequence.
+ */
+
+ while( 0 <= ch ) {
+
+ if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) {
+ if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) {
+ mp->in_boundary = 0;
+ /* matched boundary */
+ char trail[2] = {0, 0};
+ for(int i=0; i<2; i++) {
+ trail[i] = picohttpGetch(mp->req);
+ if( 0 > trail[i] )
+ return -1;
+ }
+
+ if(trail[0] == '\r' && trail[1] == '\n') {
+ mp->finished = 1;
+ }
+
+ /* TODO: Technically the last boundary is followed by a
+ * terminating <CR><LF> sequence... we should check for
+ * this as well, just for completeness
+ */
+ if(trail[0] == '-' && trail[1] == '-') {
+ mp->finished = 2;
+ }
+
+ return -1;
+ }
+ mp->in_boundary++;
+ } else {
+ if( mp->in_boundary )
+ {
+#if 1
+ if( '\r' == ch ) {
+ mp->replay = mp->in_boundary-1;
+ mp->mismatch = mp->req->query.multipartboundary[mp->replay];
+ mp->in_boundary = 1;
+ } else
+#endif
+ {
+ mp->mismatch = ch;
+ mp->replay = mp->in_boundary;
+ mp->in_boundary = 0;
+ }
+
+ goto replay;
+
+ }
+ return ch;
+ }
+ ch = picohttpGetch(mp->req);
+ }
+ }
+ return ch;
+}
+
+int picohttpMultipartRead(
+ struct picohttpMultipart * const mp,
+ size_t len,
+ char * const buf)
+{
+/* TODO: Replace this with a dedicated variant processing whole buffers?
+ * Probably a lot of code would be shared with the ...Getch variant
+ * and could be placed into a commonly used function.
+ */
+ int ch;
+ size_t i;
+ for(i = 0; i < len; i++) {
+ if( 0 > (ch = picohttpMultipartGetch(mp)) ) {
+ if( mp->finished )
+ return 0;
+ else
+ return ch;
+ }
+
+ buf[i] = ch;
+ }
+ return i;
+}
+
+static void picohttpProcessMultipartContentType(
+ struct picohttpMultipart * const mp,
+ char const *contenttype )
+{
+ mp->contenttype = picohttpProcessContentType(&contenttype);
+
+ if( PICOHTTP_CONTENTTYPE_MULTIPART == (mp->contenttype & 0xf000) ) {
+ }
+}
+
+static void picohttpProcessMultipartContentDisposition(
+ struct picohttpMultipart * const mp,
+ char const *disposition )
+{
+ char const *name = strstr(disposition, PICOHTTP_STR_NAME__);
+ if(name) {
+ name += sizeof(PICOHTTP_STR_NAME__)-1;
+ char const * const nameend = strchr(name, '"');
+ if(nameend) {
+ size_t len = nameend - name;
+ if( PICOHTTP_DISPOSITION_NAME_MAX < len )
+ len = PICOHTTP_DISPOSITION_NAME_MAX;
+
+ strncpy(mp->disposition.name, name, len);
+ }
+ }
+}
+
+static void picohttpMultipartHeaderField(
+ void * const data,
+ char const *headername,
+ char const *headervalue)
+{
+ struct picohttpMultipart * const mp = data;
+ if(!strncmp(headername,
+ PICOHTTP_STR_CONTENT,
+ sizeof(PICOHTTP_STR_CONTENT)-1)) {
+ headername += sizeof(PICOHTTP_STR_CONTENT)-1;
+ /* Content Length
+ * TODO: Is this a header actually defined for multipart bodies?
+ * Anyway, even if it's not defined, this has not negative
+ * side effects, so why care about it. Worst it can do it
+ * be usefull later.
+ */
+ if(!strncmp(headername,
+ PICOHTTP_STR__LENGTH, sizeof(PICOHTTP_STR__LENGTH)-1)) {
+ return;
+ }
+
+ /* Content Disposition */
+ if(!strncmp(headername,
+ PICOHTTP_STR__DISPOSITION, sizeof(PICOHTTP_STR__DISPOSITION)-1)) {
+ picohttpProcessMultipartContentDisposition(mp, headervalue);
+ return;
+ }
+
+ /* Content Type */
+ if(!strncmp(headername,
+ PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) {
+ picohttpProcessMultipartContentType(mp, headervalue);
+ return;
+ }
+ return;
+ }
+}
+
+struct picohttpMultipart picohttpMultipartStart(
+ struct picohttpRequest * const req)
+{
+ struct picohttpMultipart mp = {
+ .req = req,
+ .finished = 0,
+ .contenttype = 0,
+ .disposition = { .name = {0,} },
+ .in_boundary = 2,
+ .replayhead = 2,
+ .mismatch = -1,
+ };
+
+ return mp;
+}
+
+int picohttpMultipartNext(
+ struct picohttpMultipart * const mp)
+{
+ if( 2 == mp->finished ) {
+ return -1;
+ }
+
+ for(;;) {
+ int ch = picohttpMultipartGetch(mp);
+ if( 0 > ch ) {
+ if( 2 == mp->finished ) {
+ return -1;
+ }
+
+ if( 1 == mp->finished ) {
+ mp->finished = 0;
+
+ if( 0 > (ch = picohttpGetch(mp->req)) )
+ return ch;
+
+ if( 0 > (ch = picohttpProcessHeaders(
+ mp->req,
+ picohttpMultipartHeaderField,
+ mp,
+ ch)) )
+ return ch;
+
+ if( '\r' == ch ) {
+ if( 0 > (ch = picohttpIoGetch(mp->req->ioops)) )
+ return ch;
+ if( '\n' != ch ) {
+ return -1;
+ }
+ }
+ mp->mismatch = -1;
+ mp->in_boundary =
+ mp->replayhead = 0;
+
+ mp->req->query.prev_ch[0] = '\n';
+ mp->req->query.prev_ch[1] = '\r';
+ mp->req->query.prev_ch[2] =
+ mp->req->query.prev_ch[3] =
+ mp->req->query.prev_ch[4] = 0;
+
+ return 0;
+ }
+ }
+
+ }
+
+ return -1;
+}