diff options
author | Wolfgang Draxinger <Wolfgang.Draxinger@physik.uni-muenchen.de> | 2013-07-19 17:33:47 +0200 |
---|---|---|
committer | Wolfgang Draxinger <Wolfgang.Draxinger@physik.uni-muenchen.de> | 2013-07-19 17:33:47 +0200 |
commit | 1f958fc608ea5c468c04d27e0029ae298f920134 (patch) | |
tree | c0ddd226fd86be6e087e4b207ddda604ec2c7416 | |
parent | a32672d18e889fe5020400edc591350f50f2af41 (diff) | |
parent | d62d604877249f1eeaf920b3f149eb906b24cd16 (diff) | |
download | litheweb-1f958fc608ea5c468c04d27e0029ae298f920134.tar.gz litheweb-1f958fc608ea5c468c04d27e0029ae298f920134.tar.bz2 |
merged
-rw-r--r-- | picohttp.c | 722 | ||||
-rw-r--r-- | picohttp.h | 106 | ||||
-rw-r--r-- | test/Makefile | 9 | ||||
-rw-r--r-- | test/bsdsocket.c | 259 | ||||
-rw-r--r-- | test/bufbsdsocket.c (renamed from test/bsd_socket.c) | 142 |
5 files changed, 1129 insertions, 109 deletions
@@ -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; +} @@ -5,12 +5,31 @@ #include <stddef.h> #include <stdint.h> -#define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) -#define PICOHTTP_MINORVERSION(x) ( (x & 0x007f) ) +/* max 70 for boundary + 4 chars for "<CR><LF>--" */ +#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 74 +#define PICOHTTP_DISPOSITION_NAME_MAX 48 #define PICOHTTP_METHOD_GET 1 #define PICOHTTP_METHOD_HEAD 2 -#define PICOHTTP_METHOD_POST 3 +#define PICOHTTP_METHOD_POST 4 + +#define PICOHTTP_CONTENTTYPE_APPLICATION 0x1000 +#define PICOHTTP_CONTENTTYPE_APPLICATION_OCTETSTREAM 0x1000 + +#define PICOHTTP_CONTENTTYPE_AUDIO 0x2000 +#define PICOHTTP_CONTENTTYPE_IMAGE 0x3000 +#define PICOHTTP_CONTENTTYPE_MESSAGE 0x4000 +#define PICOHTTP_CONTENTTYPE_MODEL 0x5000 + +#define PICOHTTP_CONTENTTYPE_MULTIPART 0x6000 +#define PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA 0x6004 + +#define PICOHTTP_CONTENTTYPE_TEXT 0x7000 +#define PICOHTTP_CONTENTTYPE_TEXT_CSV 0x7003 +#define PICOHTTP_CONTENTTYPE_TEXT_HTML 0x7004 +#define PICOHTTP_CONTENTTYPE_TEXT_PLAIN 0x7006 + +#define PICOHTTP_CONTENTTYPE_VIDEO 0x8000 #define PICOHTTP_CODING_IDENTITY 0 #define PICOHTTP_CODING_COMPRESS 1 @@ -28,20 +47,14 @@ #define PICOHTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED 505 struct picohttpIoOps { - int (*read)(size_t /*count*/, char* /*buf*/, void*); - int (*write)(size_t /*count*/, char const* /*buf*/, void*); - int16_t (*getch)(void*); // returns -1 on error - int (*putch)(char, void*); + int (*read)(size_t /*count*/, void* /*buf*/, void*); + int (*write)(size_t /*count*/, void const* /*buf*/, void*); + int (*getch)(void*); // returns negative value on error + int (*putch)(int, void*); int (*flush)(void*); void *data; }; -#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)) - enum picohttpVarType { PICOHTTP_TYPE_UNDEFINED = 0, PICOHTTP_TYPE_INTEGER = 1, @@ -75,8 +88,19 @@ struct picohttpURLRoute { char const * urlhead; struct picohttpVarSpec const * get_vars; picohttpHandler handler; - uint16_t max_urltail_len; - int16_t allowed_methods; + unsigned int max_urltail_len; + int allowed_methods; +}; + +#define PICOHTTP_EPOCH_YEAR 1970 + +struct picohttpDateTime { + unsigned int Y:7; /* EPOCH + 127 years */ + unsigned int M:4; + unsigned int D:5; + unsigned int h:5; + unsigned int m:6; + unsigned int s:5; /* seconds / 2 */ }; #define PICOHTTP_EPOCH_YEAR 1980 @@ -96,39 +120,61 @@ struct picohttpRequest { struct picohttpVar *get_vars; char *url; char *urltail; - int16_t status; - int16_t method; + int status; + int method; struct { uint8_t major; uint8_t minor; } httpversion; struct { - char const *contenttype; + int contenttype; size_t contentlength; - uint8_t contentcoding; - uint8_t te; + uint8_t contentencoding; + uint8_t transferencoding; + unsigned char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; + unsigned char prev_ch[5]; + size_t chunklength; } query; struct { char const *contenttype; char const *disposition; struct picohttpDateTime lastmodified; - uint16_t max_age; + int max_age; size_t contentlength; uint8_t contentencoding; uint8_t transferencoding; } response; + size_t received_octets; struct { size_t octets; uint8_t header; } sent; }; +struct picohttpMultipart { + struct picohttpRequest *req; + uint8_t finished; + int contenttype; + struct { + char name[PICOHTTP_DISPOSITION_NAME_MAX+1]; + } disposition; + int in_boundary; + int replay; + int replayhead; + int mismatch; +}; + +typedef void (*picohttpHeaderFieldCallback)( + void * const data, + char const *headername, + char const *headervalue); + void picohttpProcessRequest( struct picohttpIoOps const * const ioops, struct picohttpURLRoute const * const routes ); void picohttpStatusResponse( - struct picohttpRequest *req, int16_t status ); + struct picohttpRequest *req, int status ); int picohttpResponseSendHeader ( struct picohttpRequest * const req ); @@ -138,4 +184,20 @@ int picohttpResponseWrite ( size_t len, char const *buf ); +int picohttpGetch(struct picohttpRequest * const req); + +struct picohttpMultipart picohttpMultipartStart( + struct picohttpRequest * const req); + +int picohttpMultipartNext( + struct picohttpMultipart * const mp); + +int picohttpMultipartGetch( + struct picohttpMultipart * const mp); + +int picohttpMultipartRead( + struct picohttpMultipart * const mp, + size_t len, + char * const buf); + #endif/*PICOHTTP_H_HEADERGUARD*/ diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..e96cb0f --- /dev/null +++ b/test/Makefile @@ -0,0 +1,9 @@ +.PHONY: all + +all: bsdsocket bsdsocket_nhd + +bsdsocket: bsdsocket.c ../picohttp.c ../picohttp.h + $(CC) -std=c99 -DHOST_DEBUG -O0 -g3 -I../ -o bsdsocket ../picohttp.c bsdsocket.c + +bsdsocket_nhd: bsdsocket.c ../picohttp.c ../picohttp.h + $(CC) -std=c99 -O0 -g3 -I../ -o bsdsocket_nhd ../picohttp.c bsdsocket.c diff --git a/test/bsdsocket.c b/test/bsdsocket.c new file mode 100644 index 0000000..5350db9 --- /dev/null +++ b/test/bsdsocket.c @@ -0,0 +1,259 @@ +#define _BSD_SOURCE + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include <unistd.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/ip.h> + +#include "../picohttp.h" + +int bsdsock_read(size_t count, void *buf, void *data) +{ + int fd = *((int*)data); + + ssize_t rb = 0; + ssize_t r = 0; + do { + r = read(fd, (unsigned char*)buf + rb, count-rb); + if( 0 < r ) { + rb += r; + continue; + } + if( !r ) { + break; + } + + if( EAGAIN == errno || + EWOULDBLOCK == errno ) { + usleep(100); + continue; + } + return -3 + errno; + } while( rb < count ); + return rb; +} + +int bsdsock_write(size_t count, void const *buf, void *data) +{ + int fd = *((int*)data); + + ssize_t wb = 0; + ssize_t w = 0; + do { + w = write(fd, (unsigned char*)buf + wb, count-wb); + if( 0 < w ) { + wb += w; + continue; + } + if( !w ) { + break; + } + + if( EAGAIN == errno || + EWOULDBLOCK == errno ) { + usleep(100); + continue; + } + return -3 + errno; + } while( wb < count ); + return wb; +} + +int bsdsock_getch(void *data) +{ + unsigned char ch; + int err; + if( 1 != (err = bsdsock_read(1, &ch, data)) ) + return err; + return ch; +} + +int bsdsock_putch(int ch, void *data) +{ + char ch_ = ch; + return bsdsock_write(1, &ch_, data); +} + +int bsdsock_flush(void* data) +{ + return 0; +} + +int sockfd = -1; + +void bye(void) +{ + fputs("exiting\n", stderr); + int const one = 1; + /* allows for immediate reuse of address:port + * after program termination */ + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + shutdown(sockfd, SHUT_RDWR); + close(sockfd); +} + +void rhRoot(struct picohttpRequest *req) +{ + fprintf(stderr, "handling request /%s\n", req->urltail); + + req->response.contenttype = "text/html"; + + char http_test[] = +"<html><head><title>handling request /</title></head><body>\n" +"<a href=\"/test\">/test</a>\n" +"<form action=\"/upload\" enctype=\"multipart/form-data\" method=\"post\">\n" +"<label for=\"name\">Name: </label><input type=\"text\" name=\"name\"></input><br/>\n" +"<label for=\"file1\">File: </label><input type=\"file\" name=\"file1\"></input><br/>\n" +"<label for=\"file2\">File: </label><input type=\"file\" name=\"file2\"></input><br/>\n" +"<input type=\"submit\" value=\"Upload\"></input>\n" +"</form>\n" +"</body></html>\n"; + + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); +} + +void rhTest(struct picohttpRequest *req) +{ + fprintf(stderr, "handling request /test%s\n", req->urltail); + char http_header[] = "HTTP/x.x 200 OK\r\nServer: picoweb\r\nContent-Type: text/text\r\n\r\n"; + http_header[5] = '0'+req->httpversion.major; + http_header[7] = '0'+req->httpversion.minor; + picohttpResponseWrite(req, sizeof(http_header)-1, http_header); + char http_test[] = "handling request /test"; + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); + if(req->urltail) { + picohttpResponseWrite(req, strlen(req->urltail), req->urltail); + } +} + +void rhUpload(struct picohttpRequest *req) +{ + fprintf(stderr, "handling request /upload%s\n", req->urltail); + + if( PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA != req->query.contenttype ) + return; + + char http_test[] = "handling request /upload"; + + struct picohttpMultipart mp = picohttpMultipartStart(req); + + chdir("/tmp/uploadtest"); + while( !picohttpMultipartNext(&mp) ) { + fprintf(stderr, "\nprocessing form field \"%s\"\n", mp.disposition.name); + FILE *fil = fopen(mp.disposition.name, "wb"); + if(!fil) { + continue; + } + + for(int ch = picohttpMultipartGetch(&mp); + 0 <= ch; + ch = picohttpMultipartGetch(&mp) ) { + fputc(ch, fil); + + #if HOST_DEBUG + fputs("\e[32m", stderr); + switch(ch) { + case '\r': + fputs("[CR]", stderr); break; + case '\n': + fputs("[LF]", stderr); break; + + default: + fputc(ch, stderr); + } + fputs("\e[0m", stderr); + #endif/*HOST_DEBUG*/ + } + + fclose(fil); + if( !mp.finished ) { + break; + } + } + if( !mp.finished ) { + } + + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); + if(req->urltail) { + picohttpResponseWrite(req, strlen(req->urltail), req->urltail); + } +} + +int main(int argc, char *argv[]) +{ + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if( -1 == sockfd ) { + perror("socket"); + return -1; + } +#if 0 + if( atexit(bye) ) { + return -1; + } +#endif + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(8000), + .sin_addr = 0 + }; + + int const one = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if( -1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) ) { + perror("bind"); + return -1; + } + + if( -1 == listen(sockfd, 2) ) { + perror("listen"); + return -1; + } + + for(;;) { + socklen_t addrlen = 0; + int confd = accept(sockfd, (struct sockaddr*)&addr, &addrlen); + if( -1 == confd ) { + if( EAGAIN == errno || + EWOULDBLOCK == errno ) { + usleep(1000); + continue; + } else { + perror("accept"); + return -1; + } + } + + struct picohttpIoOps ioops = { + .read = bsdsock_read, + .write = bsdsock_write, + .getch = bsdsock_getch, + .putch = bsdsock_putch, + .flush = bsdsock_flush, + .data = &confd + }; + + struct picohttpURLRoute routes[] = { + { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, + { "/upload", 0, rhUpload, 16, PICOHTTP_METHOD_POST }, + { "/|", 0, rhRoot, 0, PICOHTTP_METHOD_GET }, + { NULL, 0, 0, 0, 0 } + }; + + picohttpProcessRequest(&ioops, routes); + + shutdown(confd, SHUT_RDWR); + close(confd); + } + + return 0; +} + diff --git a/test/bsd_socket.c b/test/bufbsdsocket.c index 02ee592..22a4a99 100644 --- a/test/bsd_socket.c +++ b/test/bufbsdsocket.c @@ -6,81 +6,118 @@ #include <stdio.h> #include <errno.h> #include <string.h> +#include <assert.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> +#include <sys/ioctl.h> #include <netinet/ip.h> +#include <poll.h> #include "../picohttp.h" -int bsdsock_read(size_t count, char *buf, void *data) +#define SENDBUF_LEN 256 + +struct bufbsdsockData { + char * recvbuf; + size_t recvbuf_len; + size_t recvbuf_pos; + char sendbuf[SENDBUF_LEN]; + size_t sendbuf_pos; + int fd; +}; + +int bufbsdsock_read(size_t count, char *buf, void *data_) { - int fd = *((int*)data); - + struct bufbsdsockData *data = data_; + ssize_t rb = 0; ssize_t r = 0; do { - r = read(fd, buf+rb, count-rb); - if( 0 < r ) { - rb += r; - continue; - } - if( !r ) { - break; - } + size_t len = 0; + + if( !data->recvbuf || + data->recvbuf_pos >= data->recvbuf_len ) { + if( data->recvbuf ) + free( data->recvbuf ); + data->recvbuf_len = 0; + data->recvbuf_pos = 0; + + int avail = 0; + do { + struct pollfd pfd = { + .fd = data->fd, + .events = POLLIN | POLLPRI, + .revents = 0 + }; + + int const pret = poll(&pfd, 1, -1); + if( 0 >= pret ) { + return -1; + } + + assert(pfd.revents & (POLLIN | POLLPRI)); + + if( -1 == ioctl(data->fd, FIONREAD, &avail) ) { + perror("ioctl(FIONREAD)"); + return -1; + } + } while( !avail ); + + data->recvbuf = malloc( avail); + + int r; + while( 0 > (r = read(data->fd, data->recvbuf, avail)) ) { + if( EINTR == errno ) + continue; + + if( EAGAIN == errno || + EWOULDBLOCK == errno ) { + usleep(200); + continue; + } - if( EAGAIN == errno || - EWOULDBLOCK == errno ) { - usleep(100); - continue; + return -1; + } + data->recvbuf_len += r; } - return -3 + errno; + + len = data->recvbuf_len - data->recvbuf_pos; + if( len > count ) + len = count; + + rb += len; } while( rb < count ); return rb; } -int bsdsock_write(size_t count, char const *buf, void *data) +int bufbsdsock_write(size_t count, char const *buf, void *data) { int fd = *((int*)data); ssize_t wb = 0; ssize_t w = 0; do { - w = write(fd, buf+wb, count-wb); - if( 0 < w ) { - wb += w; - continue; - } - if( !w ) { - break; - } - - if( EAGAIN == errno || - EWOULDBLOCK == errno ) { - usleep(100); - continue; - } - return -3 + errno; } while( wb < count ); return wb; } -int16_t bsdsock_getch(void *data) +int16_t bufbsdsock_getch(void *data) { char ch; - if( 1 != bsdsock_read(1, &ch, data) ) + if( 1 != bufbsdsock_read(1, &ch, data) ) return -1; return ch; } -int bsdsock_putch(char ch, void *data) +int bufbsdsock_putch(char ch, void *data) { - return bsdsock_write(1, &ch, data); + return bufbsdsock_write(1, &ch, data); } -int bsdsock_flush(void* data) +int bufbsdsock_flush(void *data) { return 0; } @@ -104,7 +141,14 @@ void rhRoot(struct picohttpRequest *req) req->response.contenttype = "text/html"; - char http_test[] = "<html><head><title>handling request /</title></head>\n<body><a href=\"/test\">/test</a></body></html>\n"; + char http_test[] = +"<html><head><title>handling request /</title></head><body>\n" +"<a href=\"/test\">/test</a>" +"<form action=\"/upload\" enctype=\"multipart/form-data\" method=\"post\">" +"<label for=\"file\">File: </label><input type=\"file\" name=\"file\"></input>" +"<input type=\"submit\" value=\"Upload\"></input>" +"</form>" +"</body></html>\n"; picohttpResponseWrite(req, sizeof(http_test)-1, http_test); } @@ -120,6 +164,17 @@ void rhTest(struct picohttpRequest *req) } } +void rhUpload(struct picohttpRequest *req) +{ + fprintf(stderr, "handling request /upload%s\n", req->urltail); + + char http_test[] = "handling request /upload"; + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); + if(req->urltail) { + picohttpResponseWrite(req, strlen(req->urltail), req->urltail); + } +} + static uint8_t const favicon_ico[] = { 0x00,0x00,0x01,0x00,0x01,0x00,0x10,0x10,0x10,0x00,0x01,0x00,0x04,0x00,0x28,0x01, 0x00,0x00,0x16,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x20,0x00, @@ -198,17 +253,18 @@ int main(int argc, char *argv[]) } struct picohttpIoOps ioops = { - .read = bsdsock_read, - .write = bsdsock_write, - .getch = bsdsock_getch, - .putch = bsdsock_putch, - .flush = bsdsock_flush, + .read = bufbsdsock_read, + .write = bufbsdsock_write, + .getch = bufbsdsock_getch, + .putch = bufbsdsock_putch, + .flush = bufbsdsock_flush, .data = &confd }; struct picohttpURLRoute routes[] = { {"/favicon.ico|", 0, rhFavicon, 0, PICOHTTP_METHOD_GET}, { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, + { "/upload|", 0, rhUpload, 0, PICOHTTP_METHOD_GET }, { "/|", 0, rhRoot, 0, PICOHTTP_METHOD_GET }, { NULL, 0, 0, 0, 0 } }; |