From db106357956d84283dc85ea3e47d512da5113db3 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 14 Mar 2013 12:11:06 +0100 Subject: Thu Mar 14 12:11:06 CET 2013 --- test/bsd_socket.c | 190 ------------------------------------------- test/bsdsocket.c | 190 +++++++++++++++++++++++++++++++++++++++++++ test/bufbsdsocket.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+), 190 deletions(-) delete mode 100644 test/bsd_socket.c create mode 100644 test/bsdsocket.c create mode 100644 test/bufbsdsocket.c diff --git a/test/bsd_socket.c b/test/bsd_socket.c deleted file mode 100644 index d4bd5a2..0000000 --- a/test/bsd_socket.c +++ /dev/null @@ -1,190 +0,0 @@ -#define _BSD_SOURCE - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "../picohttp.h" - -int bsdsock_read(size_t count, char *buf, void *data) -{ - int fd = *((int*)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; - } - - if( EAGAIN == errno || - EWOULDBLOCK == errno ) { - usleep(100); - continue; - } - return -3 + errno; - } while( rb < count ); - return rb; -} - -int bsdsock_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) -{ - char ch; - if( 1 != bsdsock_read(1, &ch, data) ) - return -1; - return ch; -} - -int bsdsock_putch(char ch, void *data) -{ - return bsdsock_write(1, &ch, data); -} - -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); - - char http_header[] = "HTTP/x.x 200 OK\r\nServer: picoweb\r\nContent-Type: text/html\r\n\r\n"; - http_header[5] = '0'+req->httpversion.major; - http_header[7] = '0'+req->httpversion.minor; - picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); - char http_test[] = "handling request /\n/test\n"; - - picohttpIoWrite(req->ioops, 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; - picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); - char http_test[] = "handling request /test"; - picohttpIoWrite(req->ioops, sizeof(http_test)-1, http_test); - if(req->urltail) { - picohttpIoWrite(req->ioops, 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, - .data = &confd - }; - - struct picohttpURLRoute routes[] = { - { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, - { "/|", 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/bsdsocket.c b/test/bsdsocket.c new file mode 100644 index 0000000..d4bd5a2 --- /dev/null +++ b/test/bsdsocket.c @@ -0,0 +1,190 @@ +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "../picohttp.h" + +int bsdsock_read(size_t count, char *buf, void *data) +{ + int fd = *((int*)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; + } + + if( EAGAIN == errno || + EWOULDBLOCK == errno ) { + usleep(100); + continue; + } + return -3 + errno; + } while( rb < count ); + return rb; +} + +int bsdsock_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) +{ + char ch; + if( 1 != bsdsock_read(1, &ch, data) ) + return -1; + return ch; +} + +int bsdsock_putch(char ch, void *data) +{ + return bsdsock_write(1, &ch, data); +} + +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); + + char http_header[] = "HTTP/x.x 200 OK\r\nServer: picoweb\r\nContent-Type: text/html\r\n\r\n"; + http_header[5] = '0'+req->httpversion.major; + http_header[7] = '0'+req->httpversion.minor; + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); + char http_test[] = "handling request /\n/test\n"; + + picohttpIoWrite(req->ioops, 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; + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); + char http_test[] = "handling request /test"; + picohttpIoWrite(req->ioops, sizeof(http_test)-1, http_test); + if(req->urltail) { + picohttpIoWrite(req->ioops, 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, + .data = &confd + }; + + struct picohttpURLRoute routes[] = { + { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, + { "/|", 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/bufbsdsocket.c b/test/bufbsdsocket.c new file mode 100644 index 0000000..27ce6ef --- /dev/null +++ b/test/bufbsdsocket.c @@ -0,0 +1,229 @@ +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "../picohttp.h" + +#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_) +{ + struct bufbsdsockData *data = data_; + + ssize_t rb = 0; + ssize_t r = 0; + do { + 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(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; + } + + return -1; + } + data->recvbuf_len += r; + } + + len = data->recvbuf_len - data->recvbuf_pos; + if( len > count ) + len = count; + + rb += len; + } while( rb < count ); + return rb; +} + +int bufbsdsock_write(size_t count, char const *buf, void *data) +{ + int fd = *((int*)data); + + ssize_t wb = 0; + ssize_t w = 0; + do { + } while( wb < count ); + return wb; +} + +int16_t bufbsdsock_getch(void *data) +{ + char ch; + if( 1 != bufbsdsock_read(1, &ch, data) ) + return -1; + return ch; +} + +int bufbsdsock_putch(char ch, void *data) +{ + return bufbsdsock_write(1, &ch, data); +} + +int bufbsdsock_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); + + char http_header[] = "HTTP/x.x 200 OK\r\nServer: picoweb\r\nContent-Type: text/html\r\n\r\n"; + http_header[5] = '0'+req->httpversion.major; + http_header[7] = '0'+req->httpversion.minor; + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); + char http_test[] = "handling request /\n/test\n"; + + picohttpIoWrite(req->ioops, 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; + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); + char http_test[] = "handling request /test"; + picohttpIoWrite(req->ioops, sizeof(http_test)-1, http_test); + if(req->urltail) { + picohttpIoWrite(req->ioops, 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, + .data = &confd + }; + + struct picohttpURLRoute routes[] = { + { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, + { "/|", 0, rhRoot, 0, PICOHTTP_METHOD_GET }, + { NULL, 0, 0, 0, 0 } + }; + + picohttpProcessRequest(&ioops, routes); + + shutdown(confd, SHUT_RDWR); + close(confd); + } + + return 0; +} + -- cgit v1.2.3 From 660e0aaecd654c0ed99001cd765693dc9fb8a66d Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 23 Apr 2013 17:12:22 +0200 Subject: Tue Apr 23 17:12:22 CEST 2013 --- picohttp.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/picohttp.h b/picohttp.h index 041f124..6041935 100644 --- a/picohttp.h +++ b/picohttp.h @@ -79,6 +79,17 @@ struct picohttpURLRoute { int16_t allowed_methods; }; +#define PICOHTTP_EPOCH_YEAR 1980 + +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 */ +}; + struct picohttpRequest { struct picohttpIoOps const * ioops; struct picohttpURLRoute const * route; @@ -99,9 +110,9 @@ struct picohttpRequest { } query; struct { char const *contenttype; - char const *date; - char const *cachecontrol; char const *disposition; + struct picohttpDateTime lastmodified; + uint16_t max_age; size_t contentlength; uint8_t contentencoding; uint8_t transferencoding; -- cgit v1.2.3 From 813d72a2d1a4305811b39607828a9f49daadb2ab Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 00:53:25 +0200 Subject: Added API declarations for Chunked Tranfer transport and Multipart Encoding support. --- picohttp.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/picohttp.h b/picohttp.h index 041f124..d94172b 100644 --- a/picohttp.h +++ b/picohttp.h @@ -127,4 +127,15 @@ int picohttpResponseWrite ( size_t len, char const *buf ); +uint16_t picohttpGetch( + struct picohttpRequest * const req, + struct picohttpChunkTransfer * const ct); + +int picohttpMultipartNext( + struct picohttpRequest * const req, + struct picohttpMultiPart * const mp); + +uint16_t picohttpMultipartGetch( + struct picohttpMultiPart * const mp); + #endif/*PICOHTTP_H_HEADERGUARD*/ -- cgit v1.2.3 From fc9f3c0bde26bc882bdfe1ec0435ade8b60172f4 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 00:55:27 +0200 Subject: Epoch for datetime struct changed to 1970 --- picohttp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picohttp.h b/picohttp.h index 95ac0e7..803587d 100644 --- a/picohttp.h +++ b/picohttp.h @@ -79,7 +79,7 @@ struct picohttpURLRoute { int16_t allowed_methods; }; -#define PICOHTTP_EPOCH_YEAR 1980 +#define PICOHTTP_EPOCH_YEAR 1970 struct picohttpDateTime { unsigned int Y:7; /* EPOCH + 127 years */ -- cgit v1.2.3 From 47e13329da36b76918d44d51be78ef9e5584fcc0 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 20:10:12 +0200 Subject: Header Line separation implemented --- picohttp.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- picohttp.h | 25 ++++++++- test/bsd_socket.c | 21 ++++++- 3 files changed, 203 insertions(+), 8 deletions(-) diff --git a/picohttp.c b/picohttp.c index e3762f1..1b2550e 100644 --- a/picohttp.c +++ b/picohttp.c @@ -1,11 +1,22 @@ #include "picohttp.h" +#ifdef HOST_DEBUG +#include +#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 #include +#include 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"; @@ -17,12 +28,21 @@ 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_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"; static char const PICOHTTP_STR_DATE[] = "Date"; static char const PICOHTTP_STR_EXPECT[] = "Expect"; +static char const PICOHTTP_STR_BOUNDARY[] = " boundary="; + #if !defined(PICOHTTP_CONFIG_HAVE_LIBDJB) /* Number formating functions modified from libdjb by * Daniel J. Bernstein, packaged at http://www.fefe.de/djb/ @@ -196,7 +216,7 @@ static int16_t picohttpIoGetPercentCh( * 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, @@ -481,15 +501,75 @@ static int16_t picohttpProcessHTTPVersion ( return ch; } +static void picohttpProcessContentType( + struct picohttpRequest * const req, + char const *contenttype ) +{ + if(!strncmp(contenttype, + PICOHTTP_STR_APPLICATION_, sizeof(PICOHTTP_STR_APPLICATION_)-1)) { + } + + if(!strncmp(contenttype, + PICOHTTP_STR_TEXT_, sizeof(PICOHTTP_STR_TEXT_)-1)) { + } + + if(!strncmp(contenttype, PICOHTTP_STR_MULTIPART_, + sizeof(PICOHTTP_STR_MULTIPART_)-1)) { + req->query.contenttype.type = PICOHTTP_CONTENTTYPE_MULTIPART; + contenttype += sizeof(PICOHTTP_STR_MULTIPART_)-1; + + if(!strncmp(contenttype,PICOHTTP_STR_FORMDATA, + sizeof(PICOHTTP_STR_FORMDATA)-1)) { + req->query.contenttype.subtype = + PICOHTTP_CONTENTTYPE_MULTIPART_SUBTYPE_FORM_DATA; + contenttype += sizeof(PICOHTTP_STR_FORMDATA)-1; + } + char *boundary = strstr(contenttype, PICOHTTP_STR_BOUNDARY); + if(boundary) { + /* see RFC1521 regarding maximum length of boundary */ + strncpy(req->query.multipartboundary, + boundary + sizeof(PICOHTTP_STR_BOUNDARY)-1, + PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN); + } + } +} + +static void picohttpProcessHeaderField( + struct picohttpRequest * const req, + char const * const headername, + char const * const headervalue) +{ + debug_printf("%s: %s\n", headername, headervalue); + if(!strncmp(headername, + PICOHTTP_STR_CONTENT, + sizeof(PICOHTTP_STR_CONTENT)-1)) { + /* Content Length */ + if(!strncmp(headername + sizeof(PICOHTTP_STR_CONTENT)-1, + PICOHTTP_STR__LENGTH, sizeof(PICOHTTP_STR__LENGTH)-1)) { + req->query.contentlength = atol(headervalue); + } + + /* Content Type */ + if(!strncmp(headername + sizeof(PICOHTTP_STR_CONTENT)-1, + PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) { + picohttpProcessContentType(req, headervalue); + } + } +} + static int16_t picohttpProcessHeaders ( struct picohttpRequest * const req, int16_t ch ) { #define PICOHTTP_HEADERNAME_MAX_LEN 32 - char headername[PICOHTTP_HEADERNAME_MAX_LEN] = {0,}; - - - /* FIXME: Add Header handling here */ + 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; + +#define JUST_SKIP_HEADERS 0 +#if JUST_SKIP_HEADERS while( !picohttpIsCRLF(ch) ) { while( !picohttpIsCRLF( ch=picohttpIoSkipSpace(req->ioops, ch)) ){ if( 0 > ( ch=picohttpIoGetch(req->ioops) ) ) { @@ -505,6 +585,81 @@ static int16_t picohttpProcessHeaders ( return -PICOHTTP_STATUS_400_BAD_REQUEST; } } +#else + /* TODO: Add Header handling here */ + while( !picohttpIsCRLF(ch) ) { + /* Beginning of new header line */ + #if 0 + ch = picohttpIoGetch(req->ioops); + #endif + 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 = picohttpIoGetch(req->ioops); + } + } else { + if( *headername && *headervalue ) + picohttpProcessHeaderField( + req, + 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; + } + if( !ch ) { + return -PICOHTTP_STATUS_400_BAD_REQUEST; + } + } + if( *headername && *headervalue ) + picohttpProcessHeaderField( + req, + headername, + headervalue ); +#endif/*JUST_SKIP_HEADERS*/ + return ch; } diff --git a/picohttp.h b/picohttp.h index 041f124..831e1b0 100644 --- a/picohttp.h +++ b/picohttp.h @@ -5,6 +5,8 @@ #include #include +#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 70 + #define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) #define PICOHTTP_MINORVERSION(x) ( (x & 0x007f) ) @@ -12,6 +14,21 @@ #define PICOHTTP_METHOD_HEAD 2 #define PICOHTTP_METHOD_POST 3 +#define PICOHTTP_CONTENTTYPE_APPLICATION 0 +#define PICOHTTP_CONTENTTYPE_AUDIO 1 +#define PICOHTTP_CONTENTTYPE_IMAGE 2 +#define PICOHTTP_CONTENTTYPE_MESSAGE 3 +#define PICOHTTP_CONTENTTYPE_MODEL 4 +#define PICOHTTP_CONTENTTYPE_MULTIPART 5 +#define PICOHTTP_CONTENTTYPE_TEXT 6 +#define PICOHTTP_CONTENTTYPE_VIDEO 7 + +#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_CSV 3 +#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_HTML 4 +#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_PLAIN 6 + +#define PICOHTTP_CONTENTTYPE_MULTIPART_SUBTYPE_FORM_DATA 4 + #define PICOHTTP_CODING_IDENTITY 0 #define PICOHTTP_CODING_COMPRESS 1 #define PICOHTTP_CODING_DEFLATE 2 @@ -92,10 +109,14 @@ struct picohttpRequest { uint8_t minor; } httpversion; struct { - char const *contenttype; + struct { + uint16_t type:4; + uint16_t subtype:12; + } contenttype; size_t contentlength; - uint8_t contentcoding; + uint8_t contentencoding; uint8_t te; + char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; } query; struct { char const *contenttype; diff --git a/test/bsd_socket.c b/test/bsd_socket.c index 02ee592..f193e49 100644 --- a/test/bsd_socket.c +++ b/test/bsd_socket.c @@ -104,7 +104,14 @@ void rhRoot(struct picohttpRequest *req) req->response.contenttype = "text/html"; - char http_test[] = "handling request /\n/test\n"; + char http_test[] = +"handling request /\n" +"/test" +"
" +"" +"" +"
" +"\n"; picohttpResponseWrite(req, sizeof(http_test)-1, http_test); } @@ -120,6 +127,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, @@ -209,6 +227,7 @@ int main(int argc, char *argv[]) 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 } }; -- cgit v1.2.3 From 03292a30587f7795a88b8014fba33810c182c009 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 20:21:06 +0200 Subject: ... --- picohttp.c | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/picohttp.c b/picohttp.c index 1b2550e..6576297 100644 --- a/picohttp.c +++ b/picohttp.c @@ -568,30 +568,9 @@ static int16_t picohttpProcessHeaders ( char *hn; char *hv; -#define JUST_SKIP_HEADERS 0 -#if JUST_SKIP_HEADERS - while( !picohttpIsCRLF(ch) ) { - while( !picohttpIsCRLF( ch=picohttpIoSkipSpace(req->ioops, ch)) ){ - if( 0 > ( ch=picohttpIoGetch(req->ioops) ) ) { - return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR; - } - } - - ch = picohttpIoSkipOverCRLF(req->ioops, ch); - if( 0 > ch ) { - return -PICOHTTP_STATUS_500_INTERNAL_SERVER_ERROR; - } - if( !ch ) { - return -PICOHTTP_STATUS_400_BAD_REQUEST; - } - } -#else /* TODO: Add Header handling here */ while( !picohttpIsCRLF(ch) ) { /* Beginning of new header line */ - #if 0 - ch = picohttpIoGetch(req->ioops); - #endif if( 0 < ch && !picohttpIsCRLF(ch) ){ /* new header field OR field continuation */ if( picohttpIsLWS(ch) ) { @@ -658,7 +637,6 @@ static int16_t picohttpProcessHeaders ( req, headername, headervalue ); -#endif/*JUST_SKIP_HEADERS*/ return ch; } -- cgit v1.2.3 From c50c12e6206772273f62752c1c0091ac78698665 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 21:24:59 +0200 Subject: added header processing for content type and transfer encoding - chunked --- picohttp.c | 34 +++++++++++++++++++++++++++++----- picohttp.h | 5 ++++- test/bsdsocket.c | 35 +++++++++++++++++++++++++++++------ test/bufbsdsocket.c | 22 ++++++++++------------ 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/picohttp.c b/picohttp.c index 487a870..f7b6948 100644 --- a/picohttp.c +++ b/picohttp.c @@ -21,6 +21,8 @@ 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"; @@ -41,11 +43,12 @@ 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_CHUNKED[] = "chunked"; + #if !defined(PICOHTTP_CONFIG_HAVE_LIBDJB) /* Number formating functions modified from libdjb by * Daniel J. Bernstein, packaged at http://www.fefe.de/djb/ @@ -540,24 +543,45 @@ static void picohttpProcessContentType( static void picohttpProcessHeaderField( struct picohttpRequest * const req, - char const * const headername, - char const * const headervalue) + char const *headername, + char const *headervalue) { 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 + sizeof(PICOHTTP_STR_CONTENT)-1, + if(!strncmp(headername, PICOHTTP_STR__LENGTH, sizeof(PICOHTTP_STR__LENGTH)-1)) { req->query.contentlength = atol(headervalue); + return; } /* Content Type */ - if(!strncmp(headername + sizeof(PICOHTTP_STR_CONTENT)-1, + if(!strncmp(headername, PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) { picohttpProcessContentType(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; + } + return; } + return; } } diff --git a/picohttp.h b/picohttp.h index ced2ef5..34fc59b 100644 --- a/picohttp.h +++ b/picohttp.h @@ -126,7 +126,7 @@ struct picohttpRequest { } contenttype; size_t contentlength; uint8_t contentencoding; - uint8_t te; + uint8_t transferencoding; char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; } query; struct { @@ -138,6 +138,9 @@ struct picohttpRequest { uint8_t contentencoding; uint8_t transferencoding; } response; + struct { + size_t length; + } currentchunk; struct { size_t octets; uint8_t header; diff --git a/test/bsdsocket.c b/test/bsdsocket.c index d4bd5a2..cb6cde2 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -80,6 +80,11 @@ int bsdsock_putch(char ch, void *data) return bsdsock_write(1, &ch, data); } +int bsdsock_flush(void* data) +{ + return 0; +} + int sockfd = -1; void bye(void) @@ -97,13 +102,18 @@ void rhRoot(struct picohttpRequest *req) { fprintf(stderr, "handling request /%s\n", req->urltail); - char http_header[] = "HTTP/x.x 200 OK\r\nServer: picoweb\r\nContent-Type: text/html\r\n\r\n"; - http_header[5] = '0'+req->httpversion.major; - http_header[7] = '0'+req->httpversion.minor; - picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); - char http_test[] = "handling request /\n/test\n"; + req->response.contenttype = "text/html"; - picohttpIoWrite(req->ioops, sizeof(http_test)-1, http_test); + char http_test[] = +"handling request /\n" +"/test" +"
" +"" +"" +"
" +"\n"; + + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); } void rhTest(struct picohttpRequest *req) @@ -120,6 +130,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); + } +} + int main(int argc, char *argv[]) { sockfd = socket(AF_INET, SOCK_STREAM, 0); @@ -170,11 +191,13 @@ int main(int argc, char *argv[]) .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, 0, PICOHTTP_METHOD_GET }, { "/|", 0, rhRoot, 0, PICOHTTP_METHOD_GET }, { NULL, 0, 0, 0, 0 } }; diff --git a/test/bufbsdsocket.c b/test/bufbsdsocket.c index 596bc3d..22a4a99 100644 --- a/test/bufbsdsocket.c +++ b/test/bufbsdsocket.c @@ -6,12 +6,15 @@ #include #include #include +#include #include #include #include +#include #include +#include #include "../picohttp.h" @@ -57,7 +60,7 @@ int bufbsdsock_read(size_t count, char *buf, void *data_) assert(pfd.revents & (POLLIN | POLLPRI)); - if( -1 == ioctl(fd, FIONREAD, &avail) ) { + if( -1 == ioctl(data->fd, FIONREAD, &avail) ) { perror("ioctl(FIONREAD)"); return -1; } @@ -66,7 +69,7 @@ int bufbsdsock_read(size_t count, char *buf, void *data_) data->recvbuf = malloc( avail); int r; - while( 0 > (r = read(data->fd, data->recvbuf, avail)) ) + while( 0 > (r = read(data->fd, data->recvbuf, avail)) ) { if( EINTR == errno ) continue; @@ -119,11 +122,6 @@ int bufbsdsock_flush(void *data) return 0; } -int bsdsock_flush(void* data) -{ - return 0; -} - int sockfd = -1; void bye(void) @@ -255,11 +253,11 @@ 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 }; -- cgit v1.2.3 From e7b86baeec0ef6f946e2b60a68876eb6f3cdc32e Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 18 Jun 2013 22:14:52 +0200 Subject: ... --- picohttp.c | 7 +++++++ picohttp.h | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/picohttp.c b/picohttp.c index 43d4652..eb5decc 100644 --- a/picohttp.c +++ b/picohttp.c @@ -196,6 +196,13 @@ static int16_t picohttpIoGetPercentCh( return ch; } +uint16_t picohttpGetch(struct picohttpRequest * const req) +{ + /* read HTTP query body, skipping over Chunked Transfer Boundaries + * if Chunked Transfer Encoding is used */ + +} + /* 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. diff --git a/picohttp.h b/picohttp.h index 803587d..24212e5 100644 --- a/picohttp.h +++ b/picohttp.h @@ -138,9 +138,7 @@ int picohttpResponseWrite ( size_t len, char const *buf ); -uint16_t picohttpGetch( - struct picohttpRequest * const req, - struct picohttpChunkTransfer * const ct); +uint16_t picohttpGetch(struct picohttpRequest * const req); int picohttpMultipartNext( struct picohttpRequest * const req, -- cgit v1.2.3 From 21ac350ef7d59e88dd0ee725cdd94edbfdfcb7d6 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 20 Jun 2013 16:07:59 +0200 Subject: multipart getchar --- picohttp.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/picohttp.c b/picohttp.c index ff5a4dc..1304a2f 100644 --- a/picohttp.c +++ b/picohttp.c @@ -885,3 +885,47 @@ int picohttpResponseWrite ( return len; } +uint16_t picohttpMultipartGetch( + struct picohttpMultiPart * const mp); +{ + if( mp->replay + mp->in_boundary < 0) { + uint16_t ch = mp->replay < 1 ? '-' : + mp->req->query.multipartboundary[mp->replay - 1]; + mp->replay++; + return ch; + } else if( mp->finished) { + return -1; + } else { + uint16_t ch; + ch = picohttpIoGetch(mp->req->ioops); + if( 0 > ch ) { + return ch; + } + + if( (0 == mp->in_boundary && '\r' == ch) || + (1 == mp->in_boundary && '\n' == ch) || + (2 < mp->in_boundary && '-' == ch) || + (4 < mp->in_boundary && + mp->req->query.multipartboundary[mp->in_boundary-4] == ch) ) { + if( 5 < mp->in_boundary && + 0 == mp->req->query.multipartboundary[mp->in_boundary-3] ) { + /* matched boundary */ + mp->finished = 1; + return 0; + } + mp->in_boundary++; + ch = picohttpIoGetch(mp->req->ioops); + if( 0 >= ch ) { + return -1; + } + } else { + mp->replay = mp->in_boundary + 1; + return '-'; + } + } +} + + + + + -- cgit v1.2.3 From ef400acf33b32092c6c4c76f91a44d0a38061d0f Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 20 Jun 2013 16:55:28 +0200 Subject: ... --- picohttp.c | 16 +++++++++++++--- picohttp.h | 51 +++++++++++++++++++++++++++------------------------ test/bsdsocket.c | 9 ++++++--- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/picohttp.c b/picohttp.c index f7b6948..957bfa6 100644 --- a/picohttp.c +++ b/picohttp.c @@ -1,5 +1,11 @@ #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 #define debug_printf(...) do{fprintf(stderr, __VA_ARGS__);}while(0) @@ -446,6 +452,7 @@ static int16_t picohttpProcessQuery ( ch = picohttpIoGetch(req->ioops); } if( '=' == ch ) { + debug_printf("set variable '%s'\n", var); } else { } } @@ -522,15 +529,18 @@ static void picohttpProcessContentType( if(!strncmp(contenttype, PICOHTTP_STR_MULTIPART_, sizeof(PICOHTTP_STR_MULTIPART_)-1)) { - req->query.contenttype.type = PICOHTTP_CONTENTTYPE_MULTIPART; contenttype += sizeof(PICOHTTP_STR_MULTIPART_)-1; + req->query.contenttype = + PICOHTTP_CONTENTTYPE_MULTIPART; if(!strncmp(contenttype,PICOHTTP_STR_FORMDATA, sizeof(PICOHTTP_STR_FORMDATA)-1)) { - req->query.contenttype.subtype = - PICOHTTP_CONTENTTYPE_MULTIPART_SUBTYPE_FORM_DATA; contenttype += sizeof(PICOHTTP_STR_FORMDATA)-1; + + req->query.contenttype = + PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA; } + char *boundary = strstr(contenttype, PICOHTTP_STR_BOUNDARY); if(boundary) { /* see RFC1521 regarding maximum length of boundary */ diff --git a/picohttp.h b/picohttp.h index 34fc59b..76253b0 100644 --- a/picohttp.h +++ b/picohttp.h @@ -6,6 +6,7 @@ #include #define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 70 +#define PICOHTTP_DISPOSITION_NAME_MAX 16 #define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) #define PICOHTTP_MINORVERSION(x) ( (x & 0x007f) ) @@ -14,20 +15,21 @@ #define PICOHTTP_METHOD_HEAD 2 #define PICOHTTP_METHOD_POST 3 -#define PICOHTTP_CONTENTTYPE_APPLICATION 0 -#define PICOHTTP_CONTENTTYPE_AUDIO 1 -#define PICOHTTP_CONTENTTYPE_IMAGE 2 -#define PICOHTTP_CONTENTTYPE_MESSAGE 3 -#define PICOHTTP_CONTENTTYPE_MODEL 4 -#define PICOHTTP_CONTENTTYPE_MULTIPART 5 -#define PICOHTTP_CONTENTTYPE_TEXT 6 -#define PICOHTTP_CONTENTTYPE_VIDEO 7 +#define PICOHTTP_CONTENTTYPE_APPLICATION 0x0000 +#define PICOHTTP_CONTENTTYPE_AUDIO 0x1000 +#define PICOHTTP_CONTENTTYPE_IMAGE 0x2000 +#define PICOHTTP_CONTENTTYPE_MESSAGE 0x3000 +#define PICOHTTP_CONTENTTYPE_MODEL 0x4000 -#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_CSV 3 -#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_HTML 4 -#define PICOHTTP_CONTENTTYPE_TEXT_SUBTYPE_PLAIN 6 +#define PICOHTTP_CONTENTTYPE_MULTIPART 0x5000 +#define PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA 0x5004 -#define PICOHTTP_CONTENTTYPE_MULTIPART_SUBTYPE_FORM_DATA 4 +#define PICOHTTP_CONTENTTYPE_TEXT 0x6000 +#define PICOHTTP_CONTENTTYPE_TEXT_CSV 0x6003 +#define PICOHTTP_CONTENTTYPE_TEXT_HTML 0x6004 +#define PICOHTTP_CONTENTTYPE_TEXT_PLAIN 0x6006 + +#define PICOHTTP_CONTENTTYPE_VIDEO 0x7000 #define PICOHTTP_CODING_IDENTITY 0 #define PICOHTTP_CODING_COMPRESS 1 @@ -53,12 +55,6 @@ struct picohttpIoOps { 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, @@ -120,10 +116,7 @@ struct picohttpRequest { uint8_t minor; } httpversion; struct { - struct { - uint16_t type:4; - uint16_t subtype:12; - } contenttype; + uint16_t contenttype; size_t contentlength; uint8_t contentencoding; uint8_t transferencoding; @@ -141,12 +134,22 @@ struct picohttpRequest { struct { size_t length; } currentchunk; + size_t received_octets; struct { size_t octets; uint8_t header; } sent; }; +struct picohttpMultipart { + struct picohttpRequest *req; + uint16_t contenttype; + struct { + char name[PICOHTTP_DISPOSITION_NAME_MAX+1]; + } disposition; + uint8_t boundarymatch_counter; +}; + void picohttpProcessRequest( struct picohttpIoOps const * const ioops, struct picohttpURLRoute const * const routes ); @@ -168,9 +171,9 @@ uint16_t picohttpGetch( int picohttpMultipartNext( struct picohttpRequest * const req, - struct picohttpMultiPart * const mp); + struct picohttpMultipart * const mp); uint16_t picohttpMultipartGetch( - struct picohttpMultiPart * const mp); + struct picohttpMultipart * const mp); #endif/*PICOHTTP_H_HEADERGUARD*/ diff --git a/test/bsdsocket.c b/test/bsdsocket.c index cb6cde2..bddb5be 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -122,11 +122,11 @@ void rhTest(struct picohttpRequest *req) 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; - picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); + picohttpResponseWrite(req, sizeof(http_header)-1, http_header); char http_test[] = "handling request /test"; - picohttpIoWrite(req->ioops, sizeof(http_test)-1, http_test); + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); if(req->urltail) { - picohttpIoWrite(req->ioops, strlen(req->urltail), req->urltail); + picohttpResponseWrite(req, strlen(req->urltail), req->urltail); } } @@ -134,6 +134,9 @@ 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"; picohttpResponseWrite(req, sizeof(http_test)-1, http_test); if(req->urltail) { -- cgit v1.2.3 From 77be505fad9fbcea02b27cb89a196b1f230fe42f Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 20 Jun 2013 21:21:54 +0200 Subject: multipart separation works (almost) --- picohttp.c | 146 +++++++++++++++++++++++++++++++++++++++++-------------- picohttp.h | 14 ++++-- test/bsdsocket.c | 4 ++ 3 files changed, 123 insertions(+), 41 deletions(-) diff --git a/picohttp.c b/picohttp.c index 2cd18a6..8198563 100644 --- a/picohttp.c +++ b/picohttp.c @@ -225,11 +225,22 @@ static int16_t picohttpIoGetPercentCh( return ch; } -uint16_t picohttpGetch(struct picohttpRequest * const req) +int16_t picohttpGetch(struct picohttpRequest * const req) { /* read HTTP query body, skipping over Chunked Transfer Boundaries * if Chunked Transfer Encoding is used */ + uint16_t ch = picohttpIoGetch(req->ioops); + if( 0 <= ch ) { + /* use dirty hack? + * *((uint32_t*)prev_ch) = *((uint32_t*)prev_ch) >> 8; + */ + req->query.prev_ch[0] = req->query.prev_ch[1]; + req->query.prev_ch[1] = req->query.prev_ch[2]; + req->query.prev_ch[2] = ch; + } + + return ch; } /* TODO: @@ -766,6 +777,19 @@ void picohttpProcessRequest ( if( 0 > (ch = picohttpProcessHeaders(&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] = 0; + request.query.prev_ch[1] = '\r'; + request.query.prev_ch[2] = '\n'; + request.status = PICOHTTP_STATUS_200_OK; request.route->handler(&request); @@ -895,47 +919,95 @@ int picohttpResponseWrite ( return len; } -uint16_t picohttpMultipartGetch( - struct picohttpMultiPart * const mp); +int16_t picohttpMultipartGetch( + struct picohttpMultipart * const mp) { - if( mp->replay + mp->in_boundary < 0) { - uint16_t ch = mp->replay < 1 ? '-' : - mp->req->query.multipartboundary[mp->replay - 1]; - mp->replay++; - return ch; - } else if( mp->finished) { + uint16_t ch; + if( 0 < mp->replay ) { + if( mp->replay <= mp->in_boundary ) { + ch = mp->replay < 3 ? '-' : + mp->req->query.multipartboundary[mp->replay - 3]; + mp->replay++; + } else { + ch = mp->req->query.prev_ch[2]; + mp->replay = 0; + mp->in_boundary = 0; + } + } else if( mp->finished ) { return -1; } else { - uint16_t ch; - ch = picohttpIoGetch(mp->req->ioops); - if( 0 > ch ) { - return ch; - } - - if( (0 == mp->in_boundary && '\r' == ch) || - (1 == mp->in_boundary && '\n' == ch) || - (2 < mp->in_boundary && '-' == ch) || - (4 < mp->in_boundary && - mp->req->query.multipartboundary[mp->in_boundary-4] == ch) ) { - if( 5 < mp->in_boundary && - 0 == mp->req->query.multipartboundary[mp->in_boundary-3] ) { - /* matched boundary */ - mp->finished = 1; - return 0; - } - mp->in_boundary++; - ch = picohttpIoGetch(mp->req->ioops); - if( 0 >= ch ) { - return -1; + ch = picohttpGetch(mp->req); + while( 0 <= ch ) { + /* picohttp's query and header parsing is forgiving + * regarding line termination. or just + * are accepted. + * However multipart boundaries must be preceeded by + * a sequence, we're strict about that. + */ + if( ( 0 == mp->in_boundary && '-' == ch && + '\r' == mp->req->query.prev_ch[0] && + '\n' == mp->req->query.prev_ch[1] ) || + ( 1 == mp->in_boundary && '-' == ch ) || + ( 1 < mp->in_boundary && + mp->req->query.multipartboundary[mp->in_boundary-2] == ch) ) { + if( 1 < mp->in_boundary && + 0 == mp->req->query.multipartboundary[mp->in_boundary-1] ) { + /* matched boundary */ + char trail[2]; + for(int i=0; i<2; i++) { + trail[i] = picohttpGetch(mp->req); + if( 0 > trail[1] ) + return -1; + } + + if(trail[0] == '\r' && trail[1] == '\n') + mp->finished = 1; + + if(trail[0] == '-' && trail[1] == '-') + mp->finished = 2; + + return -1; + } else { + mp->in_boundary++; + } + } else { + if( 0 < mp->in_boundary ) { + mp->replay = 1; + return '-'; + } + return ch; } - } else { - mp->replay = mp->in_boundary + 1; - return '-'; + ch = picohttpGetch(mp->req); } } + return ch; } - - - - +int picohttpMultipartNext( + struct picohttpRequest * const req, + struct picohttpMultipart * const mp) +{ + mp->req = req; + mp->finished = 0; + mp->in_boundary = 0; + mp->replay = 0; + + int r; + for(;;) { + r = picohttpMultipartGetch(mp); + if( 2 == mp->finished ) { + debug_printf("last multipart\n"); + break; + } + + if( 1 == mp->finished ) { + mp->replay = 0; + mp->in_boundary = 0; + mp->finished = 0; + } + + debug_printf("% .3d = %c\n", r, r); + } + + return 0; +} diff --git a/picohttp.h b/picohttp.h index a60afd2..7c7997d 100644 --- a/picohttp.h +++ b/picohttp.h @@ -5,7 +5,8 @@ #include #include -#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 70 +/* max 70 for boundary + 4 chars for "--" */ +#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 74 #define PICOHTTP_DISPOSITION_NAME_MAX 16 #define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) @@ -16,6 +17,8 @@ #define PICOHTTP_METHOD_POST 3 #define PICOHTTP_CONTENTTYPE_APPLICATION 0x0000 +#define PICOHTTP_CONTENTTYPE_APPLICATION_OCTETSTREAM 0x0000 + #define PICOHTTP_CONTENTTYPE_AUDIO 0x1000 #define PICOHTTP_CONTENTTYPE_IMAGE 0x2000 #define PICOHTTP_CONTENTTYPE_MESSAGE 0x3000 @@ -121,6 +124,7 @@ struct picohttpRequest { uint8_t contentencoding; uint8_t transferencoding; char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; + char prev_ch[3]; } query; struct { char const *contenttype; @@ -143,11 +147,13 @@ struct picohttpRequest { struct picohttpMultipart { struct picohttpRequest *req; + uint8_t finished; uint16_t contenttype; struct { char name[PICOHTTP_DISPOSITION_NAME_MAX+1]; } disposition; - uint8_t boundarymatch_counter; + int in_boundary; + int replay; }; void picohttpProcessRequest( @@ -165,13 +171,13 @@ int picohttpResponseWrite ( size_t len, char const *buf ); -uint16_t picohttpGetch(struct picohttpRequest * const req); +int16_t picohttpGetch(struct picohttpRequest * const req); int picohttpMultipartNext( struct picohttpRequest * const req, struct picohttpMultipart * const mp); -uint16_t picohttpMultipartGetch( +int16_t picohttpMultipartGetch( struct picohttpMultipart * const mp); #endif/*PICOHTTP_H_HEADERGUARD*/ diff --git a/test/bsdsocket.c b/test/bsdsocket.c index bddb5be..63f3d17 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -138,6 +138,10 @@ void rhUpload(struct picohttpRequest *req) return; char http_test[] = "handling request /upload"; + + struct picohttpMultipart mp; + picohttpMultipartNext(req, &mp); + picohttpResponseWrite(req, sizeof(http_test)-1, http_test); if(req->urltail) { picohttpResponseWrite(req, strlen(req->urltail), req->urltail); -- cgit v1.2.3 From 2c6b42c55b3db99c664de543f32f2434948c5916 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 25 Jun 2013 15:01:24 +0200 Subject: still superfluous at end of multipart block --- picohttp.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++---------- picohttp.h | 6 +++ 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/picohttp.c b/picohttp.c index 8198563..7579d37 100644 --- a/picohttp.c +++ b/picohttp.c @@ -562,7 +562,10 @@ static void picohttpProcessContentType( char *boundary = strstr(contenttype, PICOHTTP_STR_BOUNDARY); if(boundary) { /* see RFC1521 regarding maximum length of boundary */ - strncpy(req->query.multipartboundary, + 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); } @@ -615,6 +618,7 @@ static void picohttpProcessHeaderField( static int16_t picohttpProcessHeaders ( struct picohttpRequest * const req, + picohttpHeaderFieldCallback headerfieldcallback, int16_t ch ) { #define PICOHTTP_HEADERNAME_MAX_LEN 32 @@ -650,7 +654,7 @@ static int16_t picohttpProcessHeaders ( } } else { if( *headername && *headervalue ) - picohttpProcessHeaderField( + headerfieldcallback( req, headername, headervalue ); @@ -689,7 +693,7 @@ static int16_t picohttpProcessHeaders ( } } if( *headername && *headervalue ) - picohttpProcessHeaderField( + headerfieldcallback( req, headername, headervalue ); @@ -774,7 +778,7 @@ void picohttpProcessRequest ( goto http_error; } - if( 0 > (ch = picohttpProcessHeaders(&request, ch)) ) + if( 0 > (ch = picohttpProcessHeaders(&request, picohttpProcessHeaderField, ch)) ) goto http_error; if( '\r' == ch ) { @@ -922,21 +926,42 @@ int picohttpResponseWrite ( int16_t picohttpMultipartGetch( struct picohttpMultipart * const mp) { - uint16_t ch; + uint16_t ch; if( 0 < mp->replay ) { - if( mp->replay <= mp->in_boundary ) { - ch = mp->replay < 3 ? '-' : - mp->req->query.multipartboundary[mp->replay - 3]; - mp->replay++; + if( mp->replayhead < mp->replay ) { + ch = mp->req->query.multipartboundary[mp->replayhead]; + mp->replayhead++; + debug_printf("replay_n: "); } else { ch = mp->req->query.prev_ch[2]; mp->replay = 0; + mp->replayhead = 0; mp->in_boundary = 0; + debug_printf("replay_p: "); } + return ch; } else if( mp->finished ) { return -1; } else { ch = picohttpGetch(mp->req); + + mp->replay = 0; + if( '\r' == ch ) { + mp->in_boundary = 0; + mp->replayhead = 0; + } else + if( '\r' == mp->req->query.prev_ch[1] && + '\n' == ch ) { + mp->in_boundary = 1; + mp->replayhead = 1; + } else + if( '\r' == mp->req->query.prev_ch[0] && + '\n' == mp->req->query.prev_ch[1] && + '-' == ch ) { + mp->in_boundary = 2; + mp->replayhead = 2; + } + while( 0 <= ch ) { /* picohttp's query and header parsing is forgiving * regarding line termination. or just @@ -944,14 +969,19 @@ int16_t picohttpMultipartGetch( * However multipart boundaries must be preceeded by * a sequence, we're strict about that. */ + /* --- problematic conditional ---*/ + #if 0 if( ( 0 == mp->in_boundary && '-' == ch && '\r' == mp->req->query.prev_ch[0] && '\n' == mp->req->query.prev_ch[1] ) || ( 1 == mp->in_boundary && '-' == ch ) || ( 1 < mp->in_boundary && mp->req->query.multipartboundary[mp->in_boundary-2] == ch) ) { - if( 1 < mp->in_boundary && - 0 == mp->req->query.multipartboundary[mp->in_boundary-1] ) { + #endif + if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { + if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { + mp->in_boundary = 0; + mp->replay = 0; /* matched boundary */ char trail[2]; for(int i=0; i<2; i++) { @@ -960,20 +990,30 @@ int16_t picohttpMultipartGetch( return -1; } - if(trail[0] == '\r' && trail[1] == '\n') + if(trail[0] == '\r' && trail[1] == '\n') { mp->finished = 1; + } if(trail[0] == '-' && trail[1] == '-') mp->finished = 2; return -1; - } else { - mp->in_boundary++; - } + } + mp->in_boundary++; } else { - if( 0 < mp->in_boundary ) { - mp->replay = 1; - return '-'; + if( mp->in_boundary ) { + if( '\r' == ch ) { + debug_printf("\\r"); + mp->replay = mp->in_boundary; + } else + if( '\n' == ch ) { + debug_printf("\\n"); + mp->replay = mp->in_boundary; + } else { + mp->replay = mp->in_boundary; + } + + ch = mp->req->query.multipartboundary[mp->replayhead++]; } return ch; } @@ -983,6 +1023,50 @@ int16_t picohttpMultipartGetch( return ch; } +static void picohttpMultipartHeaderField( + struct picohttpRequest * const req, + char const *headername, + char const *headervalue) +{ + 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; + } + + /* Content Type */ + if(!strncmp(headername, + PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) { + picohttpProcessContentType(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; + } + return; + } + return; + } +} + int picohttpMultipartNext( struct picohttpRequest * const req, struct picohttpMultipart * const mp) @@ -991,22 +1075,32 @@ int picohttpMultipartNext( mp->finished = 0; mp->in_boundary = 0; mp->replay = 0; + mp->replayhead = 0; int r; for(;;) { r = picohttpMultipartGetch(mp); if( 2 == mp->finished ) { - debug_printf("last multipart\n"); - break; + debug_printf("\n ### last multipart ###\n"); + return -1; } if( 1 == mp->finished ) { - mp->replay = 0; - mp->in_boundary = 0; mp->finished = 0; + debug_printf("\n--- boundary ---\n"); + } + + #if 1 + switch(r) { + case '\r': + debug_printf("% .2x = \n", r); break; + case '\n': + debug_printf("% .2x = \n", r, r); break; + default: + debug_printf("% .2x = %c\n", r, r); break; } + #endif - debug_printf("% .3d = %c\n", r, r); } return 0; diff --git a/picohttp.h b/picohttp.h index 7c7997d..e3aa8a3 100644 --- a/picohttp.h +++ b/picohttp.h @@ -154,8 +154,14 @@ struct picohttpMultipart { } disposition; int in_boundary; int replay; + int replayhead; }; +typedef void (*picohttpHeaderFieldCallback)( + struct picohttpRequest *req, + char const *headername, + char const *headervalue); + void picohttpProcessRequest( struct picohttpIoOps const * const ioops, struct picohttpURLRoute const * const routes ); -- cgit v1.2.3 From 1fb50bb9c2c971691a93a844a1c3e6b84cdcca31 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 25 Jun 2013 16:06:55 +0200 Subject: still superfluous at end of multipart block --- picohttp.c | 75 ++++++++++++++++++++++++++++++++++---------------------------- picohttp.h | 6 ++--- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/picohttp.c b/picohttp.c index 7579d37..3f64857 100644 --- a/picohttp.c +++ b/picohttp.c @@ -235,9 +235,9 @@ int16_t picohttpGetch(struct picohttpRequest * const req) /* use dirty hack? * *((uint32_t*)prev_ch) = *((uint32_t*)prev_ch) >> 8; */ - req->query.prev_ch[0] = req->query.prev_ch[1]; - req->query.prev_ch[1] = req->query.prev_ch[2]; - req->query.prev_ch[2] = ch; + req->query.prev_ch[0] = ch; + for(int i = 1; i < 4; i++) + req->query.prev_ch[i+1] = req->query.prev_ch[i]; } return ch; @@ -564,8 +564,8 @@ static void picohttpProcessContentType( /* 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, + memcpy(req->query.multipartboundary, "\r\n\r\n--", 6); + strncpy(req->query.multipartboundary+6, boundary + sizeof(PICOHTTP_STR_BOUNDARY)-1, PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN); } @@ -791,8 +791,10 @@ void picohttpProcessRequest ( } request.query.prev_ch[0] = 0; - request.query.prev_ch[1] = '\r'; - request.query.prev_ch[2] = '\n'; + request.query.prev_ch[1] = '\n'; + request.query.prev_ch[2] = '\r'; + request.query.prev_ch[3] = '\n'; + request.query.prev_ch[4] = '\r'; request.status = PICOHTTP_STATUS_200_OK; request.route->handler(&request); @@ -933,7 +935,7 @@ int16_t picohttpMultipartGetch( mp->replayhead++; debug_printf("replay_n: "); } else { - ch = mp->req->query.prev_ch[2]; + ch = mp->req->query.prev_ch[4]; mp->replay = 0; mp->replayhead = 0; mp->in_boundary = 0; @@ -945,39 +947,44 @@ int16_t picohttpMultipartGetch( } else { ch = picohttpGetch(mp->req); + /* picohttp's query and header parsing is forgiving + * regarding line termination. or just + * are accepted. + * However multipart boundaries are to start with + * a sequence, we're strict about that. + */ + mp->replay = 0; if( '\r' == ch ) { - mp->in_boundary = 0; - mp->replayhead = 0; + if( '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[1] ) { + mp->replayhead = + mp->in_boundary = 2; + } else { + mp->replayhead = + mp->in_boundary = 0; + } } else - if( '\r' == mp->req->query.prev_ch[1] && - '\n' == ch ) { - mp->in_boundary = 1; - mp->replayhead = 1; + if( '\n' == ch && + '\r' == mp->req->query.prev_ch[1] ) { + if( '\r' == mp->req->query.prev_ch[3] && + '\n' == mp->req->query.prev_ch[2] ) { + mp->replayhead = + mp->in_boundary = 3; + } else { + mp->replayhead = + mp->in_boundary = 1; + } } else - if( '\r' == mp->req->query.prev_ch[0] && - '\n' == mp->req->query.prev_ch[1] && - '-' == ch ) { - mp->in_boundary = 2; - mp->replayhead = 2; + if( '-' == ch ) { + if( '\r' == mp->req->query.prev_ch[0] && + '\n' == mp->req->query.prev_ch[1] ) { + mp->in_boundary = 2; + mp->replayhead = 2; + } } while( 0 <= ch ) { - /* picohttp's query and header parsing is forgiving - * regarding line termination. or just - * are accepted. - * However multipart boundaries must be preceeded by - * a sequence, we're strict about that. - */ - /* --- problematic conditional ---*/ - #if 0 - if( ( 0 == mp->in_boundary && '-' == ch && - '\r' == mp->req->query.prev_ch[0] && - '\n' == mp->req->query.prev_ch[1] ) || - ( 1 == mp->in_boundary && '-' == ch ) || - ( 1 < mp->in_boundary && - mp->req->query.multipartboundary[mp->in_boundary-2] == ch) ) { - #endif if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { mp->in_boundary = 0; diff --git a/picohttp.h b/picohttp.h index e3aa8a3..faa54de 100644 --- a/picohttp.h +++ b/picohttp.h @@ -5,8 +5,8 @@ #include #include -/* max 70 for boundary + 4 chars for "--" */ -#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 74 +/* max 70 for boundary + 6 chars for "--" */ +#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 76 #define PICOHTTP_DISPOSITION_NAME_MAX 16 #define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) @@ -124,7 +124,7 @@ struct picohttpRequest { uint8_t contentencoding; uint8_t transferencoding; char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; - char prev_ch[3]; + char prev_ch[5]; } query; struct { char const *contenttype; -- cgit v1.2.3 From 2a89ea7437ac3c7aede58d2cbceda96cdb48d4b9 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Tue, 25 Jun 2013 19:18:14 +0200 Subject: still superfluous at end of multipart block --- picohttp.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 29 deletions(-) diff --git a/picohttp.c b/picohttp.c index 3f64857..f24d36d 100644 --- a/picohttp.c +++ b/picohttp.c @@ -232,12 +232,8 @@ int16_t picohttpGetch(struct picohttpRequest * const req) uint16_t ch = picohttpIoGetch(req->ioops); if( 0 <= ch ) { - /* use dirty hack? - * *((uint32_t*)prev_ch) = *((uint32_t*)prev_ch) >> 8; - */ + memmove(req->query.prev_ch + 1, req->query.prev_ch, 4); req->query.prev_ch[0] = ch; - for(int i = 1; i < 4; i++) - req->query.prev_ch[i+1] = req->query.prev_ch[i]; } return ch; @@ -790,11 +786,11 @@ void picohttpProcessRequest ( } } - request.query.prev_ch[0] = 0; - request.query.prev_ch[1] = '\n'; - request.query.prev_ch[2] = '\r'; - request.query.prev_ch[3] = '\n'; - request.query.prev_ch[4] = '\r'; + request.query.prev_ch[0] = '\n'; + request.query.prev_ch[1] = '\r'; + request.query.prev_ch[2] = '\n'; + request.query.prev_ch[3] = '\r'; + request.query.prev_ch[4] = 0; request.status = PICOHTTP_STATUS_200_OK; request.route->handler(&request); @@ -934,14 +930,14 @@ int16_t picohttpMultipartGetch( ch = mp->req->query.multipartboundary[mp->replayhead]; mp->replayhead++; debug_printf("replay_n: "); + return ch; } else { - ch = mp->req->query.prev_ch[4]; + ch = mp->req->query.prev_ch[0]; mp->replay = 0; mp->replayhead = 0; - mp->in_boundary = 0; debug_printf("replay_p: "); + return ch; } - return ch; } else if( mp->finished ) { return -1; } else { @@ -956,37 +952,62 @@ int16_t picohttpMultipartGetch( mp->replay = 0; if( '\r' == ch ) { + debug_printf("req->query.prev_ch[2] && '\n' == mp->req->query.prev_ch[1] ) { + debug_printf("2>"); mp->replayhead = mp->in_boundary = 2; } else { + debug_printf("0>"); mp->replayhead = mp->in_boundary = 0; } } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { + debug_printf("req->query.prev_ch[3] && '\n' == mp->req->query.prev_ch[2] ) { + debug_printf("|3>"); mp->replayhead = mp->in_boundary = 3; } else { + debug_printf("|1>"); mp->replayhead = mp->in_boundary = 1; } } else - if( '-' == ch ) { - if( '\r' == mp->req->query.prev_ch[0] && - '\n' == mp->req->query.prev_ch[1] ) { - mp->in_boundary = 2; - mp->replayhead = 2; - } + if( '-' == ch && + '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[1] && + '\r' == mp->req->query.prev_ch[4] && + '\n' == mp->req->query.prev_ch[3] ) { + debug_printf("<-4>"); + mp->replayhead = + mp->in_boundary = 4; } while( 0 <= ch ) { + + switch(ch) { + case '\r': + debug_printf(""); break; + case '\n': + debug_printf(""); break; + default: + debug_putc(ch); break; + } + if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { + /* In case of a match there still might be some + * characters consumed, which we must return. + * For this we properly adjust the replay mechanism. + * + * TODO! + */ + mp->in_boundary = 0; mp->replay = 0; /* matched boundary */ @@ -1009,17 +1030,72 @@ int16_t picohttpMultipartGetch( mp->in_boundary++; } else { if( mp->in_boundary ) { + + /* In case the mismatch was due to a or + * or '-' character, it must be checked, if this may be + * preceeded by some * sequence that would + * allow to be a valid multipart boundary. This + * In that case the exact replay parameters depend + * on the exact combination. + * + * The replay code above also emits the last character + * read from IO, but in this case that character is + * far ahead of what should actually be returned. + * So we maniulate the content of the prev_ch[0] + * to be the final character of the replay and + * have the replay from the boundary buffer be one + * position short + */ + + /* dw: I really hate how deep this nests, but + * unfortunately HTTP and multipart body parsing + * is a nasty, convoluted state machine + */ if( '\r' == ch ) { - debug_printf("\\r"); - mp->replay = mp->in_boundary; + debug_printf("[CR|"); + if( '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[1] ) { + debug_printf("2]"); + mp->replay = mp->in_boundary - 3; + mp->in_boundary = 3; + } else { + debug_printf("0]"); + mp->replay = mp->in_boundary - 1; + mp->in_boundary = 1; + } + mp->req->query.prev_ch[0] = + mp->req->query.multipartboundary[mp->replay]; } else - if( '\n' == ch ) { - debug_printf("\\n"); - mp->replay = mp->in_boundary; + if( '\n' == ch && + '\r' == mp->req->query.prev_ch[1] ) { + debug_printf("[LF"); + if( '\r' == mp->req->query.prev_ch[3] && + '\n' == mp->req->query.prev_ch[2] ) { + debug_printf("|3]"); + mp->replay = mp->in_boundary - 4; + mp->in_boundary = 4; + } else { + debug_printf("|1]"); + mp->replay = mp->in_boundary - 2; + mp->in_boundary = 2; + } + mp->req->query.prev_ch[0] = + mp->req->query.multipartboundary[mp->replay]; + } else + if( '-' == ch && + '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[1] && + '\r' == mp->req->query.prev_ch[4] && + '\n' == mp->req->query.prev_ch[3] ) { + debug_printf("[-4]"); + mp->replay = mp->in_boundary - 4; + mp->in_boundary = 4; } else { mp->replay = mp->in_boundary; + mp->in_boundary = 0; } + debug_printf("\nreplayhead %d-->%d: ", mp->replayhead, mp->replay); ch = mp->req->query.multipartboundary[mp->replayhead++]; } return ch; @@ -1085,7 +1161,7 @@ int picohttpMultipartNext( mp->replayhead = 0; int r; - for(;;) { + do { r = picohttpMultipartGetch(mp); if( 2 == mp->finished ) { debug_printf("\n ### last multipart ###\n"); @@ -1097,18 +1173,21 @@ int picohttpMultipartNext( debug_printf("\n--- boundary ---\n"); } + if( -1 == r ) + continue; + #if 1 switch(r) { case '\r': - debug_printf("% .2x = \n", r); break; + debug_printf(" % .2x = \n", r); break; case '\n': - debug_printf("% .2x = \n", r, r); break; + debug_printf(" % .2x = \n", r, r); break; default: - debug_printf("% .2x = %c\n", r, r); break; + debug_printf(" % .2x = %c\n", r, r); break; } #endif - } + } while(1); return 0; } -- cgit v1.2.3 From 2a3fcf3f9bfe3f8362ed2eb6dd4cda44a51dee69 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 26 Jun 2013 16:32:49 +0200 Subject: multipart boundary slicing seems to work (finally!) --- picohttp.c | 39 ++------------------------------------- test/bsdsocket.c | 5 +++-- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/picohttp.c b/picohttp.c index f24d36d..f63ffd7 100644 --- a/picohttp.c +++ b/picohttp.c @@ -929,13 +929,11 @@ int16_t picohttpMultipartGetch( if( mp->replayhead < mp->replay ) { ch = mp->req->query.multipartboundary[mp->replayhead]; mp->replayhead++; - debug_printf("replay_n: "); return ch; } else { ch = mp->req->query.prev_ch[0]; mp->replay = 0; mp->replayhead = 0; - debug_printf("replay_p: "); return ch; } } else if( mp->finished ) { @@ -952,28 +950,22 @@ int16_t picohttpMultipartGetch( mp->replay = 0; if( '\r' == ch ) { - debug_printf("req->query.prev_ch[2] && '\n' == mp->req->query.prev_ch[1] ) { - debug_printf("2>"); mp->replayhead = mp->in_boundary = 2; } else { - debug_printf("0>"); mp->replayhead = mp->in_boundary = 0; } } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - debug_printf("req->query.prev_ch[3] && '\n' == mp->req->query.prev_ch[2] ) { - debug_printf("|3>"); mp->replayhead = mp->in_boundary = 3; } else { - debug_printf("|1>"); mp->replayhead = mp->in_boundary = 1; } @@ -983,38 +975,20 @@ int16_t picohttpMultipartGetch( '\n' == mp->req->query.prev_ch[1] && '\r' == mp->req->query.prev_ch[4] && '\n' == mp->req->query.prev_ch[3] ) { - debug_printf("<-4>"); mp->replayhead = mp->in_boundary = 4; } while( 0 <= ch ) { - - switch(ch) { - case '\r': - debug_printf(""); break; - case '\n': - debug_printf(""); break; - default: - debug_putc(ch); break; - } - if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { - /* In case of a match there still might be some - * characters consumed, which we must return. - * For this we properly adjust the replay mechanism. - * - * TODO! - */ - mp->in_boundary = 0; mp->replay = 0; /* matched boundary */ - char trail[2]; + char trail[2] = {0, 0}; for(int i=0; i<2; i++) { trail[i] = picohttpGetch(mp->req); - if( 0 > trail[1] ) + if( 0 > trail[i] ) return -1; } @@ -1052,14 +1026,11 @@ int16_t picohttpMultipartGetch( * is a nasty, convoluted state machine */ if( '\r' == ch ) { - debug_printf("[CR|"); if( '\r' == mp->req->query.prev_ch[2] && '\n' == mp->req->query.prev_ch[1] ) { - debug_printf("2]"); mp->replay = mp->in_boundary - 3; mp->in_boundary = 3; } else { - debug_printf("0]"); mp->replay = mp->in_boundary - 1; mp->in_boundary = 1; } @@ -1068,14 +1039,11 @@ int16_t picohttpMultipartGetch( } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - debug_printf("[LF"); if( '\r' == mp->req->query.prev_ch[3] && '\n' == mp->req->query.prev_ch[2] ) { - debug_printf("|3]"); mp->replay = mp->in_boundary - 4; mp->in_boundary = 4; } else { - debug_printf("|1]"); mp->replay = mp->in_boundary - 2; mp->in_boundary = 2; } @@ -1087,15 +1055,12 @@ int16_t picohttpMultipartGetch( '\n' == mp->req->query.prev_ch[1] && '\r' == mp->req->query.prev_ch[4] && '\n' == mp->req->query.prev_ch[3] ) { - debug_printf("[-4]"); mp->replay = mp->in_boundary - 4; mp->in_boundary = 4; } else { mp->replay = mp->in_boundary; mp->in_boundary = 0; } - - debug_printf("\nreplayhead %d-->%d: ", mp->replayhead, mp->replay); ch = mp->req->query.multipartboundary[mp->replayhead++]; } return ch; diff --git a/test/bsdsocket.c b/test/bsdsocket.c index 63f3d17..cda0d4d 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -70,8 +70,9 @@ int bsdsock_write(size_t count, char const *buf, void *data) int16_t bsdsock_getch(void *data) { char ch; - if( 1 != bsdsock_read(1, &ch, data) ) - return -1; + int err; + if( 1 != (err = bsdsock_read(1, &ch, data)) ) + return err; return ch; } -- cgit v1.2.3 From 6563dbb5655b409aabe9514f8844711634b0aff2 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 26 Jun 2013 18:44:49 +0200 Subject: multipart separation in getch works --- picohttp.c | 145 ++++++++++++++++++++++++++++++------------------------- test/bsdsocket.c | 3 +- 2 files changed, 80 insertions(+), 68 deletions(-) diff --git a/picohttp.c b/picohttp.c index f63ffd7..4d9376c 100644 --- a/picohttp.c +++ b/picohttp.c @@ -945,13 +945,16 @@ int16_t picohttpMultipartGetch( * regarding line termination. or just * are accepted. * However multipart boundaries are to start with - * a sequence, we're strict about that. + * a [] sequence. */ mp->replay = 0; if( '\r' == ch ) { - if( '\r' == mp->req->query.prev_ch[2] && - '\n' == mp->req->query.prev_ch[1] ) { + if( '\n' == mp->req->query.prev_ch[1] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[2] + #endif + ) { mp->replayhead = mp->in_boundary = 2; } else { @@ -961,8 +964,11 @@ int16_t picohttpMultipartGetch( } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - if( '\r' == mp->req->query.prev_ch[3] && - '\n' == mp->req->query.prev_ch[2] ) { + if( '\n' == mp->req->query.prev_ch[2] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[3] + #endif + ) { mp->replayhead = mp->in_boundary = 3; } else { @@ -971,15 +977,19 @@ int16_t picohttpMultipartGetch( } } else if( '-' == ch && - '\r' == mp->req->query.prev_ch[2] && '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[4] && - '\n' == mp->req->query.prev_ch[3] ) { + '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[3] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[4] + #endif + ) { mp->replayhead = mp->in_boundary = 4; } 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; @@ -996,6 +1006,9 @@ int16_t picohttpMultipartGetch( mp->finished = 1; } + /* TODO: Technically the last boundary is followed by + * a last sequence... we should check for this + * as well, just for completeness */ if(trail[0] == '-' && trail[1] == '-') mp->finished = 2; @@ -1026,8 +1039,11 @@ int16_t picohttpMultipartGetch( * is a nasty, convoluted state machine */ if( '\r' == ch ) { - if( '\r' == mp->req->query.prev_ch[2] && - '\n' == mp->req->query.prev_ch[1] ) { + if( '\n' == mp->req->query.prev_ch[1] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[2] + #endif + ) { mp->replay = mp->in_boundary - 3; mp->in_boundary = 3; } else { @@ -1039,8 +1055,11 @@ int16_t picohttpMultipartGetch( } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - if( '\r' == mp->req->query.prev_ch[3] && - '\n' == mp->req->query.prev_ch[2] ) { + if( '\n' == mp->req->query.prev_ch[2] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[3] + #endif + ) { mp->replay = mp->in_boundary - 4; mp->in_boundary = 4; } else { @@ -1051,16 +1070,20 @@ int16_t picohttpMultipartGetch( mp->req->query.multipartboundary[mp->replay]; } else if( '-' == ch && - '\r' == mp->req->query.prev_ch[2] && '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[4] && - '\n' == mp->req->query.prev_ch[3] ) { + '\r' == mp->req->query.prev_ch[2] && + '\n' == mp->req->query.prev_ch[3] + #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX + && '\r' == mp->req->query.prev_ch[4] + #endif + ) { mp->replay = mp->in_boundary - 4; mp->in_boundary = 4; } else { mp->replay = mp->in_boundary; mp->in_boundary = 0; } + ch = mp->req->query.multipartboundary[mp->replayhead++]; } return ch; @@ -1077,42 +1100,6 @@ static void picohttpMultipartHeaderField( char const *headervalue) { 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; - } - - /* Content Type */ - if(!strncmp(headername, - PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) { - picohttpProcessContentType(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; - } - return; - } - return; - } } int picohttpMultipartNext( @@ -1125,30 +1112,54 @@ int picohttpMultipartNext( mp->replay = 0; mp->replayhead = 0; - int r; + int16_t ch; do { - r = picohttpMultipartGetch(mp); - if( 2 == mp->finished ) { - debug_printf("\n ### last multipart ###\n"); - return -1; - } + ch = picohttpMultipartGetch(mp); + if( -1 == ch ) { + if( 2 == mp->finished ) { + debug_printf("\n### last multipart ###\n"); + return -1; + } - if( 1 == mp->finished ) { - mp->finished = 0; - debug_printf("\n--- boundary ---\n"); - } + if( 1 == mp->finished ) { + mp->finished = 0; + debug_printf("\n--- boundary ---\n"); - if( -1 == r ) - continue; + if( 0 > (ch = picohttpMultipartGetch(mp)) ) + return ch; + + if( 0 > (ch = picohttpProcessHeaders( + req, + picohttpMultipartHeaderField, + ch)) ) + return ch; + + if( '\r' == ch ) { + if( 0 > (ch = picohttpIoGetch(req->ioops)) ) + return ch; + if( '\n' != ch ) { + return -PICOHTTP_STATUS_400_BAD_REQUEST; + } + } + + req->query.prev_ch[0] = '\n'; + req->query.prev_ch[1] = '\r'; + req->query.prev_ch[2] = '\n'; + req->query.prev_ch[3] = '\r'; + req->query.prev_ch[4] = 0; + + continue; + } + } #if 1 - switch(r) { + switch(ch) { case '\r': - debug_printf(" % .2x = \n", r); break; + debug_printf(" % .2x = \n", ch); break; case '\n': - debug_printf(" % .2x = \n", r, r); break; + debug_printf(" % .2x = \n", ch, ch); break; default: - debug_printf(" % .2x = %c\n", r, r); break; + debug_printf(" % .2x = %c\n", ch, ch); break; } #endif diff --git a/test/bsdsocket.c b/test/bsdsocket.c index cda0d4d..9665e1a 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -109,7 +109,8 @@ void rhRoot(struct picohttpRequest *req) "handling request /\n" "/test" "
" -"" +"" +"" "" "
" "\n"; -- cgit v1.2.3 From ffa2fc800d92b40f4a128814ae55b121a7852e58 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 26 Jun 2013 19:34:04 +0200 Subject: *grrr* last two days completely wasted because text editors fooled me by not showing CR and LF present and things I thought to be artifacts are actual data. At least I could fix it easily. --- picohttp.c | 45 ++++++++++++++++++++++++++++++++------------- picohttp.h | 4 ++-- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/picohttp.c b/picohttp.c index 4d9376c..50a864e 100644 --- a/picohttp.c +++ b/picohttp.c @@ -560,8 +560,8 @@ static void picohttpProcessContentType( /* see RFC1521 regarding maximum length of boundary */ memset(req->query.multipartboundary, 0, PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1); - memcpy(req->query.multipartboundary, "\r\n\r\n--", 6); - strncpy(req->query.multipartboundary+6, + memcpy(req->query.multipartboundary, "\r\n--", 4); + strncpy(req->query.multipartboundary+4, boundary + sizeof(PICOHTTP_STR_BOUNDARY)-1, PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN); } @@ -945,11 +945,13 @@ int16_t picohttpMultipartGetch( * regarding line termination. or just * are accepted. * However multipart boundaries are to start with - * a [] sequence. + * a sequence. */ mp->replay = 0; if( '\r' == ch ) { + debug_printf("'CR'"); + #if PICOHTTP_CRLFCRLF if( '\n' == mp->req->query.prev_ch[1] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[2] @@ -957,13 +959,17 @@ int16_t picohttpMultipartGetch( ) { mp->replayhead = mp->in_boundary = 2; - } else { + } else + #endif + { mp->replayhead = mp->in_boundary = 0; } } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { + debug_printf("'LF'"); + #if PICOHTTP_CRLFCRLF if( '\n' == mp->req->query.prev_ch[2] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[3] @@ -971,21 +977,26 @@ int16_t picohttpMultipartGetch( ) { mp->replayhead = mp->in_boundary = 3; - } else { + } else + #endif + { mp->replayhead = mp->in_boundary = 1; } } else if( '-' == ch && '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[2] && - '\n' == mp->req->query.prev_ch[3] + '\r' == mp->req->query.prev_ch[2] +#if PICOHTTP_CRLFCRLF + && '\n' == mp->req->query.prev_ch[3] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[4] #endif +#endif ) { + debug_printf("'-'"); mp->replayhead = - mp->in_boundary = 4; + mp->in_boundary = 2; } while( 0 <= ch ) { @@ -1039,6 +1050,7 @@ int16_t picohttpMultipartGetch( * is a nasty, convoluted state machine */ if( '\r' == ch ) { + #if PICOHTTP_CRLFCRLF if( '\n' == mp->req->query.prev_ch[1] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[2] @@ -1046,7 +1058,9 @@ int16_t picohttpMultipartGetch( ) { mp->replay = mp->in_boundary - 3; mp->in_boundary = 3; - } else { + } else + #endif + { mp->replay = mp->in_boundary - 1; mp->in_boundary = 1; } @@ -1055,6 +1069,7 @@ int16_t picohttpMultipartGetch( } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { + #if PICOHTTP_CRLFCRLF if( '\n' == mp->req->query.prev_ch[2] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[3] @@ -1062,7 +1077,9 @@ int16_t picohttpMultipartGetch( ) { mp->replay = mp->in_boundary - 4; mp->in_boundary = 4; - } else { + } else + #endif + { mp->replay = mp->in_boundary - 2; mp->in_boundary = 2; } @@ -1071,14 +1088,16 @@ int16_t picohttpMultipartGetch( } else if( '-' == ch && '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[2] && - '\n' == mp->req->query.prev_ch[3] + '\r' == mp->req->query.prev_ch[2] + #if PICOHTTP_CRLFCRLF + && '\n' == mp->req->query.prev_ch[3] #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX && '\r' == mp->req->query.prev_ch[4] #endif + #endif ) { mp->replay = mp->in_boundary - 4; - mp->in_boundary = 4; + mp->in_boundary = 2; } else { mp->replay = mp->in_boundary; mp->in_boundary = 0; diff --git a/picohttp.h b/picohttp.h index faa54de..d7bccde 100644 --- a/picohttp.h +++ b/picohttp.h @@ -5,8 +5,8 @@ #include #include -/* max 70 for boundary + 6 chars for "--" */ -#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 76 +/* max 70 for boundary + 4 chars for "--" */ +#define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 74 #define PICOHTTP_DISPOSITION_NAME_MAX 16 #define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) -- cgit v1.2.3 From f9cc5128f5e9eb726b8562d33067ddce6d65385c Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 28 Jun 2013 18:14:16 +0200 Subject: multipart code works --- picohttp.c | 322 ++++++++++++++++++++++++++++++++----------------------- picohttp.h | 46 ++++---- test/bsdsocket.c | 32 ++++-- 3 files changed, 237 insertions(+), 163 deletions(-) diff --git a/picohttp.c b/picohttp.c index 50a864e..fe50424 100644 --- a/picohttp.c +++ b/picohttp.c @@ -35,7 +35,7 @@ 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/"; @@ -52,6 +52,7 @@ 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"; @@ -227,7 +228,7 @@ static int16_t picohttpIoGetPercentCh( int16_t picohttpGetch(struct picohttpRequest * const req) { - /* read HTTP query body, skipping over Chunked Transfer Boundaries + /* TODO: skipping over Chunked Transfer Boundaries * if Chunked Transfer Encoding is used */ uint16_t ch = picohttpIoGetch(req->ioops); @@ -239,6 +240,22 @@ int16_t picohttpGetch(struct picohttpRequest * const req) return ch; } +int picohttpRead(struct picohttpRequest * const req, size_t len, char * const buf) +{ + /* TODO: skipping over Chunked Transfer Boundaries + * if Chunked Transfer Encoding is used */ + 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); + } + + 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. @@ -529,32 +546,43 @@ static int16_t picohttpProcessHTTPVersion ( return ch; } -static void picohttpProcessContentType( - struct picohttpRequest * const req, - char const *contenttype ) +static uint16_t picohttpProcessContentType( + char const **contenttype) { - if(!strncmp(contenttype, + uint16_t ct = 0; + if(!strncmp(*contenttype, PICOHTTP_STR_APPLICATION_, sizeof(PICOHTTP_STR_APPLICATION_)-1)) { + ct = PICOHTTP_CONTENTTYPE_APPLICATION; } - if(!strncmp(contenttype, + if(!strncmp(*contenttype, PICOHTTP_STR_TEXT_, sizeof(PICOHTTP_STR_TEXT_)-1)) { + ct = PICOHTTP_CONTENTTYPE_TEXT; } - if(!strncmp(contenttype, PICOHTTP_STR_MULTIPART_, + if(!strncmp(*contenttype, PICOHTTP_STR_MULTIPART_, sizeof(PICOHTTP_STR_MULTIPART_)-1)) { - contenttype += sizeof(PICOHTTP_STR_MULTIPART_)-1; - req->query.contenttype = - PICOHTTP_CONTENTTYPE_MULTIPART; + ct = PICOHTTP_CONTENTTYPE_MULTIPART; + *contenttype += sizeof(PICOHTTP_STR_MULTIPART_)-1; - if(!strncmp(contenttype,PICOHTTP_STR_FORMDATA, + if(!strncmp(*contenttype,PICOHTTP_STR_FORMDATA, sizeof(PICOHTTP_STR_FORMDATA)-1)) { - contenttype += sizeof(PICOHTTP_STR_FORMDATA)-1; + *contenttype += sizeof(PICOHTTP_STR_FORMDATA)-1; - req->query.contenttype = - PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA; + ct = PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA; } + } + return ct; +} + +static void picohttpProcessHeaderContentType( + struct picohttpRequest * const req, + char const *contenttype ) +{ + 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 */ @@ -569,10 +597,11 @@ static void picohttpProcessContentType( } static void picohttpProcessHeaderField( - struct picohttpRequest * const req, + 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, @@ -588,7 +617,7 @@ static void picohttpProcessHeaderField( /* Content Type */ if(!strncmp(headername, PICOHTTP_STR__TYPE, sizeof(PICOHTTP_STR__TYPE)-1)) { - picohttpProcessContentType(req, headervalue); + picohttpProcessHeaderContentType(req, headervalue); return; } return; @@ -615,6 +644,7 @@ static void picohttpProcessHeaderField( static int16_t picohttpProcessHeaders ( struct picohttpRequest * const req, picohttpHeaderFieldCallback headerfieldcallback, + void * const data, int16_t ch ) { #define PICOHTTP_HEADERNAME_MAX_LEN 32 @@ -651,7 +681,7 @@ static int16_t picohttpProcessHeaders ( } else { if( *headername && *headervalue ) headerfieldcallback( - req, + data, headername, headervalue ); /* new header field */ @@ -690,7 +720,7 @@ static int16_t picohttpProcessHeaders ( } if( *headername && *headervalue ) headerfieldcallback( - req, + data, headername, headervalue ); @@ -752,7 +782,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; @@ -774,7 +803,11 @@ void picohttpProcessRequest ( goto http_error; } - if( 0 > (ch = picohttpProcessHeaders(&request, picohttpProcessHeaderField, ch)) ) + if( 0 > (ch = picohttpProcessHeaders( + &request, + picohttpProcessHeaderField, + &request, + ch)) ) goto http_error; if( '\r' == ch ) { @@ -788,8 +821,8 @@ void picohttpProcessRequest ( request.query.prev_ch[0] = '\n'; request.query.prev_ch[1] = '\r'; - request.query.prev_ch[2] = '\n'; - request.query.prev_ch[3] = '\r'; + request.query.prev_ch[2] = + request.query.prev_ch[3] = request.query.prev_ch[4] = 0; request.status = PICOHTTP_STATUS_200_OK; @@ -925,6 +958,9 @@ int16_t picohttpMultipartGetch( struct picohttpMultipart * const mp) { uint16_t ch; + if( mp->finished ) { + return -1; + } else if( 0 < mp->replay ) { if( mp->replayhead < mp->replay ) { ch = mp->req->query.multipartboundary[mp->replayhead]; @@ -936,8 +972,6 @@ int16_t picohttpMultipartGetch( mp->replayhead = 0; return ch; } - } else if( mp->finished ) { - return -1; } else { ch = picohttpGetch(mp->req); @@ -950,51 +984,18 @@ int16_t picohttpMultipartGetch( mp->replay = 0; if( '\r' == ch ) { - debug_printf("'CR'"); - #if PICOHTTP_CRLFCRLF - if( '\n' == mp->req->query.prev_ch[1] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[2] - #endif - ) { - mp->replayhead = - mp->in_boundary = 2; - } else - #endif - { - mp->replayhead = - mp->in_boundary = 0; - } + mp->replayhead = + mp->in_boundary = 0; } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - debug_printf("'LF'"); - #if PICOHTTP_CRLFCRLF - if( '\n' == mp->req->query.prev_ch[2] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[3] - #endif - ) { - mp->replayhead = - mp->in_boundary = 3; - } else - #endif - { - mp->replayhead = - mp->in_boundary = 1; - } + mp->replayhead = + mp->in_boundary = 1; } else if( '-' == ch && '\n' == mp->req->query.prev_ch[1] && '\r' == mp->req->query.prev_ch[2] -#if PICOHTTP_CRLFCRLF - && '\n' == mp->req->query.prev_ch[3] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[4] - #endif -#endif ) { - debug_printf("'-'"); mp->replayhead = mp->in_boundary = 2; } @@ -1032,7 +1033,7 @@ int16_t picohttpMultipartGetch( /* In case the mismatch was due to a or * or '-' character, it must be checked, if this may be * preceeded by some * sequence that would - * allow to be a valid multipart boundary. This + * allow to be a valid multipart boundary. * In that case the exact replay parameters depend * on the exact combination. * @@ -1050,51 +1051,21 @@ int16_t picohttpMultipartGetch( * is a nasty, convoluted state machine */ if( '\r' == ch ) { - #if PICOHTTP_CRLFCRLF - if( '\n' == mp->req->query.prev_ch[1] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[2] - #endif - ) { - mp->replay = mp->in_boundary - 3; - mp->in_boundary = 3; - } else - #endif - { - mp->replay = mp->in_boundary - 1; - mp->in_boundary = 1; - } + mp->replay = mp->in_boundary - 1; + mp->in_boundary = 1; mp->req->query.prev_ch[0] = mp->req->query.multipartboundary[mp->replay]; } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { - #if PICOHTTP_CRLFCRLF - if( '\n' == mp->req->query.prev_ch[2] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[3] - #endif - ) { - mp->replay = mp->in_boundary - 4; - mp->in_boundary = 4; - } else - #endif - { - mp->replay = mp->in_boundary - 2; - mp->in_boundary = 2; - } + mp->replay = mp->in_boundary - 2; + mp->in_boundary = 2; mp->req->query.prev_ch[0] = mp->req->query.multipartboundary[mp->replay]; } else if( '-' == ch && '\n' == mp->req->query.prev_ch[1] && '\r' == mp->req->query.prev_ch[2] - #if PICOHTTP_CRLFCRLF - && '\n' == mp->req->query.prev_ch[3] - #if PICOHTTP_STRICT_CRLF_MULTIPART_BOUNDARY_PREFIX - && '\r' == mp->req->query.prev_ch[4] - #endif - #endif ) { mp->replay = mp->in_boundary - 4; mp->in_boundary = 2; @@ -1113,76 +1084,161 @@ int16_t picohttpMultipartGetch( 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. + */ + uint16_t 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( - struct picohttpRequest * const req, + void * const data, char const *headername, char const *headervalue) { - debug_printf("%s: %s\n", headername, 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 = 0, + .replay = 0, + .replayhead = 0 + }; + + return mp; } int picohttpMultipartNext( - struct picohttpRequest * const req, struct picohttpMultipart * const mp) { - mp->req = req; - mp->finished = 0; - mp->in_boundary = 0; - mp->replay = 0; - mp->replayhead = 0; + if( 2 == mp->finished ) { + return -1; + } - int16_t ch; - do { - ch = picohttpMultipartGetch(mp); - if( -1 == ch ) { + for(;;) { + int16_t ch = picohttpMultipartGetch(mp); + if( 0 > ch ) { if( 2 == mp->finished ) { - debug_printf("\n### last multipart ###\n"); return -1; } if( 1 == mp->finished ) { mp->finished = 0; - debug_printf("\n--- boundary ---\n"); + mp->replay = 0; + mp->replayhead = 0; + mp->in_boundary = 0; if( 0 > (ch = picohttpMultipartGetch(mp)) ) return ch; if( 0 > (ch = picohttpProcessHeaders( - req, + mp->req, picohttpMultipartHeaderField, + mp, ch)) ) return ch; if( '\r' == ch ) { - if( 0 > (ch = picohttpIoGetch(req->ioops)) ) + if( 0 > (ch = picohttpIoGetch(mp->req->ioops)) ) return ch; if( '\n' != ch ) { - return -PICOHTTP_STATUS_400_BAD_REQUEST; + return -1; } } - req->query.prev_ch[0] = '\n'; - req->query.prev_ch[1] = '\r'; - req->query.prev_ch[2] = '\n'; - req->query.prev_ch[3] = '\r'; - req->query.prev_ch[4] = 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; - continue; + return 0; } } - #if 1 - switch(ch) { - case '\r': - debug_printf(" % .2x = \n", ch); break; - case '\n': - debug_printf(" % .2x = \n", ch, ch); break; - default: - debug_printf(" % .2x = %c\n", ch, ch); break; - } - #endif - - } while(1); + } - return 0; + return -1; } diff --git a/picohttp.h b/picohttp.h index d7bccde..063ebd5 100644 --- a/picohttp.h +++ b/picohttp.h @@ -7,32 +7,29 @@ /* max 70 for boundary + 4 chars for "--" */ #define PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN 74 -#define PICOHTTP_DISPOSITION_NAME_MAX 16 - -#define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) -#define PICOHTTP_MINORVERSION(x) ( (x & 0x007f) ) +#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 0x0000 -#define PICOHTTP_CONTENTTYPE_APPLICATION_OCTETSTREAM 0x0000 +#define PICOHTTP_CONTENTTYPE_APPLICATION 0x1000 +#define PICOHTTP_CONTENTTYPE_APPLICATION_OCTETSTREAM 0x1000 -#define PICOHTTP_CONTENTTYPE_AUDIO 0x1000 -#define PICOHTTP_CONTENTTYPE_IMAGE 0x2000 -#define PICOHTTP_CONTENTTYPE_MESSAGE 0x3000 -#define PICOHTTP_CONTENTTYPE_MODEL 0x4000 +#define PICOHTTP_CONTENTTYPE_AUDIO 0x2000 +#define PICOHTTP_CONTENTTYPE_IMAGE 0x3000 +#define PICOHTTP_CONTENTTYPE_MESSAGE 0x4000 +#define PICOHTTP_CONTENTTYPE_MODEL 0x5000 -#define PICOHTTP_CONTENTTYPE_MULTIPART 0x5000 -#define PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA 0x5004 +#define PICOHTTP_CONTENTTYPE_MULTIPART 0x6000 +#define PICOHTTP_CONTENTTYPE_MULTIPART_FORMDATA 0x6004 -#define PICOHTTP_CONTENTTYPE_TEXT 0x6000 -#define PICOHTTP_CONTENTTYPE_TEXT_CSV 0x6003 -#define PICOHTTP_CONTENTTYPE_TEXT_HTML 0x6004 -#define PICOHTTP_CONTENTTYPE_TEXT_PLAIN 0x6006 +#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 0x7000 +#define PICOHTTP_CONTENTTYPE_VIDEO 0x8000 #define PICOHTTP_CODING_IDENTITY 0 #define PICOHTTP_CODING_COMPRESS 1 @@ -52,7 +49,7 @@ 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 + int16_t (*getch)(void*); // returns negative value on error int (*putch)(char, void*); int (*flush)(void*); void *data; @@ -158,7 +155,7 @@ struct picohttpMultipart { }; typedef void (*picohttpHeaderFieldCallback)( - struct picohttpRequest *req, + void * const data, char const *headername, char const *headervalue); @@ -179,11 +176,18 @@ int picohttpResponseWrite ( int16_t picohttpGetch(struct picohttpRequest * const req); +struct picohttpMultipart picohttpMultipartStart( + struct picohttpRequest * const req); + int picohttpMultipartNext( - struct picohttpRequest * const req, struct picohttpMultipart * const mp); int16_t 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/bsdsocket.c b/test/bsdsocket.c index 9665e1a..e090054 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -107,12 +107,13 @@ void rhRoot(struct picohttpRequest *req) char http_test[] = "handling request /\n" -"/test" -"
" -"" -"" -"" -"
" +"/test\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" "\n"; picohttpResponseWrite(req, sizeof(http_test)-1, http_test); @@ -141,8 +142,21 @@ void rhUpload(struct picohttpRequest *req) char http_test[] = "handling request /upload"; - struct picohttpMultipart mp; - picohttpMultipartNext(req, &mp); + struct picohttpMultipart mp = picohttpMultipartStart(req); + + while( !picohttpMultipartNext(&mp) ) { + fprintf(stderr, "processing form field \"%s\"\n", mp.disposition.name); + for(int16_t ch = picohttpMultipartGetch(&mp); + 0 <= ch; + ch = picohttpMultipartGetch(&mp) ) { + fputc(ch, stderr); + } + if( !mp.finished ) { + break; + } + } + if( !mp.finished ) { + } picohttpResponseWrite(req, sizeof(http_test)-1, http_test); if(req->urltail) { @@ -206,7 +220,7 @@ int main(int argc, char *argv[]) struct picohttpURLRoute routes[] = { { "/test", 0, rhTest, 16, PICOHTTP_METHOD_GET }, - { "/upload|", 0, rhUpload, 0, PICOHTTP_METHOD_GET }, + { "/upload", 0, rhUpload, 16, PICOHTTP_METHOD_POST }, { "/|", 0, rhRoot, 0, PICOHTTP_METHOD_GET }, { NULL, 0, 0, 0, 0 } }; -- cgit v1.2.3 From ac9f95a91345561704c667ea387ee5efb2c67a8f Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 28 Jun 2013 20:41:42 +0200 Subject: chunked transfer getch done --- picohttp.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- picohttp.h | 4 +-- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/picohttp.c b/picohttp.c index fe50424..1a01888 100644 --- a/picohttp.c +++ b/picohttp.c @@ -56,6 +56,14 @@ static char const PICOHTTP_STR_NAME__[] = " name=\""; static char const PICOHTTP_STR_CHUNKED[] = "chunked"; +/* compilation unit local function forward declarations */ +static int16_t picohttpProcessHeaders ( + struct picohttpRequest * const req, + picohttpHeaderFieldCallback headerfieldcallback, + void * const data, + int16_t 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/ @@ -196,6 +204,23 @@ static int16_t picohttpIoB10ToU8 ( return ch; } +static int16_t picohttpIoB10ToU64 ( + uint64_t *i, + struct picohttpIoOps const * const ioops, + int16_t ch ) +{ + if( !ch ) + ch = picohttpIoGetch(ioops); + + while( ch >= '0' && ch <= '9' ) { + *i *= 10; + *i += (ch & 0x0f); + ch = picohttpIoGetch(ioops); + } + + return ch; +} + static int16_t picohttpIoGetPercentCh( struct picohttpIoOps const * const ioops ) { @@ -228,13 +253,75 @@ static int16_t picohttpIoGetPercentCh( int16_t picohttpGetch(struct picohttpRequest * const req) { - /* TODO: skipping over Chunked Transfer Boundaries + int16_t ch; + /* skipping over Chunked Transfer Boundaries * if Chunked Transfer Encoding is used */ - uint16_t ch = picohttpIoGetch(req->ioops); + if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { + if( !req->query.chunklength ) { + /* this is a new chunk; + * read the length and skip to after */ + 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 ) { + 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 , 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; @@ -634,6 +721,7 @@ static void picohttpProcessHeaderField( PICOHTTP_STR_CHUNKED, sizeof(PICOHTTP_STR_CHUNKED)-1)) { req->query.transferencoding = PICOHTTP_CODING_CHUNKED; + req->query.chunklength = 0; } return; } @@ -679,7 +767,7 @@ static int16_t picohttpProcessHeaders ( ch = picohttpIoGetch(req->ioops); } } else { - if( *headername && *headervalue ) + if( *headername && *headervalue && headerfieldcallback ) headerfieldcallback( data, headername, @@ -718,7 +806,7 @@ static int16_t picohttpProcessHeaders ( return -PICOHTTP_STATUS_400_BAD_REQUEST; } } - if( *headername && *headervalue ) + if( *headername && *headervalue && headerfieldcallback) headerfieldcallback( data, headername, @@ -763,6 +851,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 ) { @@ -1035,7 +1124,7 @@ int16_t picohttpMultipartGetch( * preceeded by some * sequence that would * allow to be a valid multipart boundary. * In that case the exact replay parameters depend - * on the exact combination. + * on the specific combination. * * The replay code above also emits the last character * read from IO, but in this case that character is diff --git a/picohttp.h b/picohttp.h index 063ebd5..8afa022 100644 --- a/picohttp.h +++ b/picohttp.h @@ -122,6 +122,7 @@ struct picohttpRequest { uint8_t transferencoding; char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; char prev_ch[5]; + size_t chunklength; } query; struct { char const *contenttype; @@ -132,9 +133,6 @@ struct picohttpRequest { uint8_t contentencoding; uint8_t transferencoding; } response; - struct { - size_t length; - } currentchunk; size_t received_octets; struct { size_t octets; -- cgit v1.2.3 From 8d66d3911d45e61956d7fe0ee4879482998d0a8e Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 28 Jun 2013 21:00:57 +0200 Subject: chunked transfer read done --- picohttp.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/picohttp.c b/picohttp.c index 1a01888..f4c45bd 100644 --- a/picohttp.c +++ b/picohttp.c @@ -329,10 +329,47 @@ int16_t picohttpGetch(struct picohttpRequest * const req) int picohttpRead(struct picohttpRequest * const req, size_t len, char * const buf) { - /* TODO: skipping over Chunked Transfer Boundaries + /* skipping over Chunked Transfer Boundaries * if Chunked Transfer Encoding is used */ - int r = picohttpIoRead(req->ioops, len, buf); + if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { + if( !req->query.chunklength ) { + int16_t ch; + /* this is a new chunk; + * read the length and skip to after */ + 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); @@ -340,6 +377,34 @@ int picohttpRead(struct picohttpRequest * const req, size_t len, char * const bu memcpy(req->query.prev_ch, buf + len - 5, 5); } + if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { + if( !req->query.chunklength <= req->received_octets ) { + int16_t ch; + /* end of chunk; + * skip over , 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; } -- cgit v1.2.3 From 08e2fb421acd1684e57f79fd74b1eced4add6f3e Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 17 Jul 2013 15:04:44 +0200 Subject: ... --- picohttp.c | 2 +- test/bsdsocket.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/picohttp.c b/picohttp.c index f4c45bd..bc2f1f1 100644 --- a/picohttp.c +++ b/picohttp.c @@ -1247,7 +1247,7 @@ int picohttpMultipartRead( * Probably a lot of code would be shared with the ...Getch variant * and could be placed into a commonly used function. */ - uint16_t ch; + int16_t ch; size_t i; for(i = 0; i < len; i++) { if( 0 > (ch = picohttpMultipartGetch(mp)) ) { diff --git a/test/bsdsocket.c b/test/bsdsocket.c index e090054..90b4702 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -145,7 +145,7 @@ void rhUpload(struct picohttpRequest *req) struct picohttpMultipart mp = picohttpMultipartStart(req); while( !picohttpMultipartNext(&mp) ) { - fprintf(stderr, "processing form field \"%s\"\n", mp.disposition.name); + fprintf(stderr, "\nprocessing form field \"%s\"\n", mp.disposition.name); for(int16_t ch = picohttpMultipartGetch(&mp); 0 <= ch; ch = picohttpMultipartGetch(&mp) ) { -- cgit v1.2.3 From 4a2cb21d686979ca9b7b527dd0935d0537a360eb Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 17 Jul 2013 15:20:46 +0200 Subject: in-band signalling in header parsing now uses negative values only. --- picohttp.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/picohttp.c b/picohttp.c index bc2f1f1..15d8647 100644 --- a/picohttp.c +++ b/picohttp.c @@ -149,10 +149,12 @@ static int16_t picohttpIoSkipSpace ( struct picohttpIoOps const * const ioops, int16_t 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; } @@ -163,12 +165,15 @@ static int16_t picohttpIoSkipOverCRLF ( struct picohttpIoOps const * const ioops, int16_t ch) { - for(;;ch = 0) { - if(!ch) + for(;;ch = -1) { + if(0 > ch) { ch = picohttpIoGetch(ioops); + } + if( ch < 0 ) { return -1; } + if( ch == '\n' ) { break; } @@ -192,7 +197,7 @@ static int16_t picohttpIoB10ToU8 ( struct picohttpIoOps const * const ioops, int16_t ch ) { - if( !ch ) + if( 0 > ch ) ch = picohttpIoGetch(ioops); while( ch >= '0' && ch <= '9' ) { @@ -209,7 +214,7 @@ static int16_t picohttpIoB10ToU64 ( struct picohttpIoOps const * const ioops, int16_t ch ) { - if( !ch ) + if( 0 > ch ) ch = picohttpIoGetch(ioops); while( ch >= '0' && ch <= '9' ) { @@ -677,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; } @@ -928,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; } @@ -1111,7 +1116,7 @@ int picohttpResponseWrite ( int16_t picohttpMultipartGetch( struct picohttpMultipart * const mp) { - uint16_t ch; + int16_t ch; if( mp->finished ) { return -1; } else -- cgit v1.2.3 From 5ab9a0131969b669f3682598ee949275c01953a8 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 17 Jul 2013 15:24:37 +0200 Subject: in-band signalling in header parsing now uses negative values only. --- test/bsdsocket.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/bsdsocket.c b/test/bsdsocket.c index 90b4702..b1ca825 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -149,7 +149,14 @@ void rhUpload(struct picohttpRequest *req) for(int16_t ch = picohttpMultipartGetch(&mp); 0 <= ch; ch = picohttpMultipartGetch(&mp) ) { - fputc(ch, stderr); + switch(ch) { + case '\r': + fputs("--- ---\n", stderr); break; + case '\n': + fputs("--- ---\n", stderr); break; + + default: + fputc(ch, stderr); } if( !mp.finished ) { break; -- cgit v1.2.3 From 9e0dabe42cb40ab5010663d092b4e7e23536b7c9 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 17 Jul 2013 15:25:17 +0200 Subject: test case highlights CR and LF. --- test/bsdsocket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bsdsocket.c b/test/bsdsocket.c index b1ca825..b2d66c1 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -157,6 +157,7 @@ void rhUpload(struct picohttpRequest *req) default: fputc(ch, stderr); + } } if( !mp.finished ) { break; -- cgit v1.2.3 From 97d36e88a0aa7d098aabb0916b59fceab6dbb2b1 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 17 Jul 2013 16:43:53 +0200 Subject: testcase Makefile --- test/Makefile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/Makefile diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..2a831d4 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,2 @@ +bsdsocket: bsdsocket.c ../picohttp.c + $(CC) -std=c99 -DHOST_DEBUG -O0 -g3 -I../ -o bsdsocket ../picohttp.c bsdsocket.c -- cgit v1.2.3 From bee3c22a9a5156fe6a2b7f4a26b4d4f20cb7d8f1 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 18 Jul 2013 13:36:39 +0200 Subject: multipart boundary handling breaks with CR CR ... CR runs --- picohttp.c | 81 +++++++++++++++++++++++++++++--------------------------- picohttp.h | 22 +++++++-------- test/bsdsocket.c | 2 +- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/picohttp.c b/picohttp.c index 15d8647..810fe1b 100644 --- a/picohttp.c +++ b/picohttp.c @@ -57,11 +57,11 @@ static char const PICOHTTP_STR_NAME__[] = " name=\""; static char const PICOHTTP_STR_CHUNKED[] = "chunked"; /* compilation unit local function forward declarations */ -static int16_t picohttpProcessHeaders ( +static int picohttpProcessHeaders ( struct picohttpRequest * const req, picohttpHeaderFieldCallback headerfieldcallback, void * const data, - int16_t ch ); + int ch ); /* compilation unit local helper functions */ #if !defined(PICOHTTP_CONFIG_HAVE_LIBDJB) @@ -99,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: @@ -121,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': @@ -138,16 +138,16 @@ 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 = -1) { if(0 > ch) { @@ -161,9 +161,9 @@ static int16_t picohttpIoSkipSpace ( return ch; } -static int16_t picohttpIoSkipOverCRLF ( +static int picohttpIoSkipOverCRLF ( struct picohttpIoOps const * const ioops, - int16_t ch) + int ch) { for(;;ch = -1) { if(0 > ch) { @@ -192,10 +192,10 @@ 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); @@ -209,10 +209,10 @@ static int16_t picohttpIoB10ToU8 ( return ch; } -static int16_t picohttpIoB10ToU64 ( +static int picohttpIoB10ToU64 ( uint64_t *i, struct picohttpIoOps const * const ioops, - int16_t ch ) + int ch ) { if( 0 > ch ) ch = picohttpIoGetch(ioops); @@ -226,11 +226,11 @@ static int16_t picohttpIoB10ToU64 ( 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; @@ -256,9 +256,9 @@ static int16_t picohttpIoGetPercentCh( return ch; } -int16_t picohttpGetch(struct picohttpRequest * const req) +int picohttpGetch(struct picohttpRequest * const req) { - int16_t ch; + int ch; /* skipping over Chunked Transfer Boundaries * if Chunked Transfer Encoding is used */ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { @@ -338,7 +338,7 @@ int picohttpRead(struct picohttpRequest * const req, size_t len, char * const bu * if Chunked Transfer Encoding is used */ if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { if( !req->query.chunklength ) { - int16_t ch; + int ch; /* this is a new chunk; * read the length and skip to after */ if( 0 > (ch = picohttpIoGetch(req->ioops)) ) { @@ -384,7 +384,7 @@ int picohttpRead(struct picohttpRequest * const req, size_t len, char * const bu if(req->query.transferencoding == PICOHTTP_CODING_CHUNKED ) { if( !req->query.chunklength <= req->received_octets ) { - int16_t ch; + int ch; /* end of chunk; * skip over , make sure the trailing '0' is * there and read the headers, err, I mean footers @@ -470,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) ) { @@ -533,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 @@ -572,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) { @@ -651,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) ) { @@ -703,10 +703,10 @@ static int16_t picohttpProcessHTTPVersion ( return ch; } -static uint16_t picohttpProcessContentType( +static int picohttpProcessContentType( char const **contenttype) { - uint16_t ct = 0; + int ct = 0; if(!strncmp(*contenttype, PICOHTTP_STR_APPLICATION_, sizeof(PICOHTTP_STR_APPLICATION_)-1)) { ct = PICOHTTP_CONTENTTYPE_APPLICATION; @@ -799,11 +799,11 @@ static void picohttpProcessHeaderField( } } -static int16_t picohttpProcessHeaders ( +static int picohttpProcessHeaders ( struct picohttpRequest * const req, picohttpHeaderFieldCallback headerfieldcallback, void * const data, - int16_t ch ) + int ch ) { #define PICOHTTP_HEADERNAME_MAX_LEN 32 char headername[PICOHTTP_HEADERNAME_MAX_LEN+1] = {0,}; @@ -890,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; @@ -1113,10 +1113,10 @@ int picohttpResponseWrite ( return len; } -int16_t picohttpMultipartGetch( +int picohttpMultipartGetch( struct picohttpMultipart * const mp) { - int16_t ch; + int ch; if( mp->finished ) { return -1; } else @@ -1132,6 +1132,8 @@ int16_t picohttpMultipartGetch( return ch; } } else { + mp->replay = 0; + ch = picohttpGetch(mp->req); /* picohttp's query and header parsing is forgiving @@ -1141,10 +1143,11 @@ int16_t picohttpMultipartGetch( * a sequence. */ - mp->replay = 0; if( '\r' == ch ) { mp->replayhead = mp->in_boundary = 0; + if( '\r' == mp->req->query.prev_ch[1] ) + return '\r'; } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { @@ -1157,7 +1160,7 @@ int16_t picohttpMultipartGetch( ) { mp->replayhead = mp->in_boundary = 2; - } + } while( 0 <= ch ) { @@ -1252,7 +1255,7 @@ int picohttpMultipartRead( * Probably a lot of code would be shared with the ...Getch variant * and could be placed into a commonly used function. */ - int16_t ch; + int ch; size_t i; for(i = 0; i < len; i++) { if( 0 > (ch = picohttpMultipartGetch(mp)) ) { @@ -1357,7 +1360,7 @@ int picohttpMultipartNext( } for(;;) { - int16_t ch = picohttpMultipartGetch(mp); + int ch = picohttpMultipartGetch(mp); if( 0 > ch ) { if( 2 == mp->finished ) { return -1; diff --git a/picohttp.h b/picohttp.h index 8afa022..380584c 100644 --- a/picohttp.h +++ b/picohttp.h @@ -49,7 +49,7 @@ struct picohttpIoOps { int (*read)(size_t /*count*/, char* /*buf*/, void*); int (*write)(size_t /*count*/, char const* /*buf*/, void*); - int16_t (*getch)(void*); // returns negative value on error + int (*getch)(void*); // returns negative value on error int (*putch)(char, void*); int (*flush)(void*); void *data; @@ -88,8 +88,8 @@ 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 @@ -109,14 +109,14 @@ 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 { - uint16_t contenttype; + int contenttype; size_t contentlength; uint8_t contentencoding; uint8_t transferencoding; @@ -128,7 +128,7 @@ struct picohttpRequest { 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; @@ -143,7 +143,7 @@ struct picohttpRequest { struct picohttpMultipart { struct picohttpRequest *req; uint8_t finished; - uint16_t contenttype; + int contenttype; struct { char name[PICOHTTP_DISPOSITION_NAME_MAX+1]; } disposition; @@ -162,7 +162,7 @@ void picohttpProcessRequest( struct picohttpURLRoute const * const routes ); void picohttpStatusResponse( - struct picohttpRequest *req, int16_t status ); + struct picohttpRequest *req, int status ); int picohttpResponseSendHeader ( struct picohttpRequest * const req ); @@ -172,7 +172,7 @@ int picohttpResponseWrite ( size_t len, char const *buf ); -int16_t picohttpGetch(struct picohttpRequest * const req); +int picohttpGetch(struct picohttpRequest * const req); struct picohttpMultipart picohttpMultipartStart( struct picohttpRequest * const req); @@ -180,7 +180,7 @@ struct picohttpMultipart picohttpMultipartStart( int picohttpMultipartNext( struct picohttpMultipart * const mp); -int16_t picohttpMultipartGetch( +int picohttpMultipartGetch( struct picohttpMultipart * const mp); int picohttpMultipartRead( diff --git a/test/bsdsocket.c b/test/bsdsocket.c index b2d66c1..cb06d58 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -67,7 +67,7 @@ int bsdsock_write(size_t count, char const *buf, void *data) return wb; } -int16_t bsdsock_getch(void *data) +int bsdsock_getch(void *data) { char ch; int err; -- cgit v1.2.3 From 9fec30ee4e98dee6411280547d861a30c76c626a Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Thu, 18 Jul 2013 18:44:37 +0200 Subject: start of branch --- picohttp.c | 21 ++++++++++++++++++++- test/bsdsocket.c | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/picohttp.c b/picohttp.c index 810fe1b..9b28b92 100644 --- a/picohttp.c +++ b/picohttp.c @@ -1124,11 +1124,13 @@ int picohttpMultipartGetch( if( mp->replayhead < mp->replay ) { ch = mp->req->query.multipartboundary[mp->replayhead]; mp->replayhead++; + debug_printf("replay head: %0.2x\n", ch); return ch; } else { ch = mp->req->query.prev_ch[0]; mp->replay = 0; mp->replayhead = 0; + debug_printf("replay prev: %0.2x\n", ch); return ch; } } else { @@ -1143,16 +1145,29 @@ int picohttpMultipartGetch( * a sequence. */ + switch(ch) { + case '\r': + debug_printf("(CR)", ch); break; + case '\n': + debug_printf("(LF)", ch); break; + default: + debug_printf("(%c)", ch); + } if( '\r' == ch ) { mp->replayhead = mp->in_boundary = 0; - if( '\r' == mp->req->query.prev_ch[1] ) + if( '\r' == mp->req->query.prev_ch[1] ) { + debug_printf(""); return '\r'; + } else { + debug_printf(""); + } } else if( '\n' == ch && '\r' == mp->req->query.prev_ch[1] ) { mp->replayhead = mp->in_boundary = 1; + debug_printf(""); } else if( '-' == ch && '\n' == mp->req->query.prev_ch[1] && @@ -1160,12 +1175,14 @@ int picohttpMultipartGetch( ) { mp->replayhead = mp->in_boundary = 2; + debug_printf("-"); } while( 0 <= ch ) { if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { + debug_printf("{|}", ch); mp->in_boundary = 0; mp->replay = 0; /* matched boundary */ @@ -1188,8 +1205,10 @@ int picohttpMultipartGetch( return -1; } + debug_printf("{#}", ch); mp->in_boundary++; } else { + debug_printf("{_}", ch); if( mp->in_boundary ) { /* In case the mismatch was due to a or diff --git a/test/bsdsocket.c b/test/bsdsocket.c index cb06d58..1d5f8d5 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -151,9 +151,9 @@ void rhUpload(struct picohttpRequest *req) ch = picohttpMultipartGetch(&mp) ) { switch(ch) { case '\r': - fputs("--- ---\n", stderr); break; + fputs("[CR]", stderr); break; case '\n': - fputs("--- ---\n", stderr); break; + fputs("[LF]", stderr); break; default: fputc(ch, stderr); -- cgit v1.2.3 From 9689e87ed54246598b154750aa2afc1735283ba7 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 19 Jul 2013 14:24:02 +0200 Subject: multipart boundary separation totally broken with runs of s; I must approach this differently. --- picohttp.c | 131 ++++++++++++++++++++----------------------------------- picohttp.h | 10 ++--- test/Makefile | 9 +++- test/bsdsocket.c | 31 +++++++++---- 4 files changed, 84 insertions(+), 97 deletions(-) diff --git a/picohttp.c b/picohttp.c index 9b28b92..7b3dfbc 100644 --- a/picohttp.c +++ b/picohttp.c @@ -453,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 ) { @@ -1113,6 +1113,25 @@ int picohttpResponseWrite ( return len; } +static void picohttpMultipartSync( + struct picohttpMultipart * const mp) +{ + if( '\r' == mp->req->query.prev_ch[0] ) { + mp->replayhead = 0; + mp->in_boundary = 1; + debug_printf("mpsync \n"); + } else + if( '\n' == mp->req->query.prev_ch[0] && + '\r' == mp->req->query.prev_ch[1] ) { + mp->replayhead = 1; + mp->in_boundary = 2; + debug_printf("mpsync \n"); + } else { + mp->replayhead = -1; + mp->in_boundary = 0; + } +} + int picohttpMultipartGetch( struct picohttpMultipart * const mp) { @@ -1124,18 +1143,17 @@ int picohttpMultipartGetch( if( mp->replayhead < mp->replay ) { ch = mp->req->query.multipartboundary[mp->replayhead]; mp->replayhead++; - debug_printf("replay head: %0.2x\n", ch); + debug_printf(" >> : %0.2x\n", ch); return ch; } else { - ch = mp->req->query.prev_ch[0]; mp->replay = 0; - mp->replayhead = 0; - debug_printf("replay prev: %0.2x\n", ch); + ch = mp->req->query.prev_ch[0]; + picohttpMultipartSync(mp); + debug_printf(" >>|: %0.2x\n", ch); return ch; } } else { - mp->replay = 0; - +end_of_replay_was_CR: ch = picohttpGetch(mp->req); /* picohttp's query and header parsing is forgiving @@ -1145,40 +1163,17 @@ int picohttpMultipartGetch( * a sequence. */ - switch(ch) { - case '\r': - debug_printf("(CR)", ch); break; - case '\n': - debug_printf("(LF)", ch); break; - default: - debug_printf("(%c)", ch); - } - if( '\r' == ch ) { - mp->replayhead = - mp->in_boundary = 0; - if( '\r' == mp->req->query.prev_ch[1] ) { - debug_printf(""); - return '\r'; - } else { - debug_printf(""); + while( 0 <= ch ) { + + switch(ch) { + case '\r': + debug_printf("(CR)", ch); break; + case '\n': + debug_printf("(LF)", ch); break; + default: + debug_printf("(%c)", ch); } - } else - if( '\n' == ch && - '\r' == mp->req->query.prev_ch[1] ) { - mp->replayhead = - mp->in_boundary = 1; - debug_printf(""); - } else - if( '-' == ch && - '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[2] - ) { - mp->replayhead = - mp->in_boundary = 2; - debug_printf("-"); - } - while( 0 <= ch ) { if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { @@ -1197,9 +1192,10 @@ int picohttpMultipartGetch( mp->finished = 1; } - /* TODO: Technically the last boundary is followed by - * a last sequence... we should check for this - * as well, just for completeness */ + /* TODO: Technically the last boundary is followed by a + * terminating sequence... we should check for + * this as well, just for completeness + */ if(trail[0] == '-' && trail[1] == '-') mp->finished = 2; @@ -1208,55 +1204,22 @@ int picohttpMultipartGetch( debug_printf("{#}", ch); mp->in_boundary++; } else { - debug_printf("{_}", ch); + debug_printf("{_|%0.2x", ch); if( mp->in_boundary ) { - - /* In case the mismatch was due to a or - * or '-' character, it must be checked, if this may be - * preceeded by some * sequence that would - * allow to be a valid multipart boundary. - * In that case the exact replay parameters depend - * on the specific combination. - * - * The replay code above also emits the last character - * read from IO, but in this case that character is - * far ahead of what should actually be returned. - * So we maniulate the content of the prev_ch[0] - * to be the final character of the replay and - * have the replay from the boundary buffer be one - * position short - */ - - /* dw: I really hate how deep this nests, but - * unfortunately HTTP and multipart body parsing - * is a nasty, convoluted state machine - */ + mp->replay = mp->in_boundary; if( '\r' == ch ) { - mp->replay = mp->in_boundary - 1; + mp->replayhead = 0; mp->in_boundary = 1; - mp->req->query.prev_ch[0] = - mp->req->query.multipartboundary[mp->replay]; - } else - if( '\n' == ch && - '\r' == mp->req->query.prev_ch[1] ) { - mp->replay = mp->in_boundary - 2; - mp->in_boundary = 2; - mp->req->query.prev_ch[0] = - mp->req->query.multipartboundary[mp->replay]; - } else - if( '-' == ch && - '\n' == mp->req->query.prev_ch[1] && - '\r' == mp->req->query.prev_ch[2] - ) { - mp->replay = mp->in_boundary - 4; - mp->in_boundary = 2; } else { - mp->replay = mp->in_boundary; + mp->replayhead = 1; mp->in_boundary = 0; } - ch = mp->req->query.multipartboundary[mp->replayhead++]; + + debug_printf("|%d..%d", mp->replayhead, mp->replay); } + debug_putc('}'); + return ch; } ch = picohttpGetch(mp->req); @@ -1368,6 +1331,8 @@ struct picohttpMultipart picohttpMultipartStart( .replayhead = 0 }; + picohttpMultipartSync(&mp); + return mp; } diff --git a/picohttp.h b/picohttp.h index 380584c..d3931ab 100644 --- a/picohttp.h +++ b/picohttp.h @@ -47,10 +47,10 @@ #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*); + 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)(char, void*); + int (*putch)(int, void*); int (*flush)(void*); void *data; }; @@ -120,8 +120,8 @@ struct picohttpRequest { size_t contentlength; uint8_t contentencoding; uint8_t transferencoding; - char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; - char prev_ch[5]; + unsigned char multipartboundary[PICOHTTP_MULTIPARTBOUNDARY_MAX_LEN+1]; + unsigned char prev_ch[5]; size_t chunklength; } query; struct { diff --git a/test/Makefile b/test/Makefile index 2a831d4..e96cb0f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,2 +1,9 @@ -bsdsocket: bsdsocket.c ../picohttp.c +.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 index 1d5f8d5..5350db9 100644 --- a/test/bsdsocket.c +++ b/test/bsdsocket.c @@ -15,14 +15,14 @@ #include "../picohttp.h" -int bsdsock_read(size_t count, char *buf, void *data) +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, buf+rb, count-rb); + r = read(fd, (unsigned char*)buf + rb, count-rb); if( 0 < r ) { rb += r; continue; @@ -41,14 +41,14 @@ int bsdsock_read(size_t count, char *buf, void *data) return rb; } -int bsdsock_write(size_t count, char const *buf, void *data) +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, buf+wb, count-wb); + w = write(fd, (unsigned char*)buf + wb, count-wb); if( 0 < w ) { wb += w; continue; @@ -69,16 +69,17 @@ int bsdsock_write(size_t count, char const *buf, void *data) int bsdsock_getch(void *data) { - char ch; + unsigned char ch; int err; if( 1 != (err = bsdsock_read(1, &ch, data)) ) return err; return ch; } -int bsdsock_putch(char ch, void *data) +int bsdsock_putch(int ch, void *data) { - return bsdsock_write(1, &ch, data); + char ch_ = ch; + return bsdsock_write(1, &ch_, data); } int bsdsock_flush(void* data) @@ -144,11 +145,21 @@ void rhUpload(struct picohttpRequest *req) struct picohttpMultipart mp = picohttpMultipartStart(req); + chdir("/tmp/uploadtest"); while( !picohttpMultipartNext(&mp) ) { fprintf(stderr, "\nprocessing form field \"%s\"\n", mp.disposition.name); - for(int16_t ch = picohttpMultipartGetch(&mp); + 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; @@ -158,7 +169,11 @@ void rhUpload(struct picohttpRequest *req) default: fputc(ch, stderr); } + fputs("\e[0m", stderr); + #endif/*HOST_DEBUG*/ } + + fclose(fil); if( !mp.finished ) { break; } -- cgit v1.2.3 From d62d604877249f1eeaf920b3f149eb906b24cd16 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 19 Jul 2013 17:32:21 +0200 Subject: multipart seems to work (finally) --- picohttp.c | 87 +++++++++++++++++++------------------------------------------- picohttp.h | 1 + 2 files changed, 28 insertions(+), 60 deletions(-) diff --git a/picohttp.c b/picohttp.c index 7b3dfbc..476630e 100644 --- a/picohttp.c +++ b/picohttp.c @@ -1113,25 +1113,6 @@ int picohttpResponseWrite ( return len; } -static void picohttpMultipartSync( - struct picohttpMultipart * const mp) -{ - if( '\r' == mp->req->query.prev_ch[0] ) { - mp->replayhead = 0; - mp->in_boundary = 1; - debug_printf("mpsync \n"); - } else - if( '\n' == mp->req->query.prev_ch[0] && - '\r' == mp->req->query.prev_ch[1] ) { - mp->replayhead = 1; - mp->in_boundary = 2; - debug_printf("mpsync \n"); - } else { - mp->replayhead = -1; - mp->in_boundary = 0; - } -} - int picohttpMultipartGetch( struct picohttpMultipart * const mp) { @@ -1139,21 +1120,19 @@ int picohttpMultipartGetch( if( mp->finished ) { return -1; } else - if( 0 < mp->replay ) { + if( 0 <= mp->mismatch ) { +replay: if( mp->replayhead < mp->replay ) { ch = mp->req->query.multipartboundary[mp->replayhead]; mp->replayhead++; - debug_printf(" >> : %0.2x\n", ch); return ch; } else { - mp->replay = 0; - ch = mp->req->query.prev_ch[0]; - picohttpMultipartSync(mp); - debug_printf(" >>|: %0.2x\n", ch); + ch = mp->mismatch; + mp->mismatch = -1; + mp->replayhead = 0; return ch; } } else { -end_of_replay_was_CR: ch = picohttpGetch(mp->req); /* picohttp's query and header parsing is forgiving @@ -1165,21 +1144,9 @@ end_of_replay_was_CR: while( 0 <= ch ) { - switch(ch) { - case '\r': - debug_printf("(CR)", ch); break; - case '\n': - debug_printf("(LF)", ch); break; - default: - debug_printf("(%c)", ch); - } - - if( mp->req->query.multipartboundary[mp->in_boundary] == ch ) { if( 0 == mp->req->query.multipartboundary[mp->in_boundary+1] ) { - debug_printf("{|}", ch); mp->in_boundary = 0; - mp->replay = 0; /* matched boundary */ char trail[2] = {0, 0}; for(int i=0; i<2; i++) { @@ -1196,30 +1163,32 @@ end_of_replay_was_CR: * terminating sequence... we should check for * this as well, just for completeness */ - if(trail[0] == '-' && trail[1] == '-') + if(trail[0] == '-' && trail[1] == '-') { mp->finished = 2; + } return -1; } - debug_printf("{#}", ch); mp->in_boundary++; } else { - debug_printf("{_|%0.2x", ch); - if( mp->in_boundary ) { - mp->replay = mp->in_boundary; + if( mp->in_boundary ) + { +#if 1 if( '\r' == ch ) { - mp->replayhead = 0; + mp->replay = mp->in_boundary-1; + mp->mismatch = mp->req->query.multipartboundary[mp->replay]; mp->in_boundary = 1; - } else { - mp->replayhead = 1; + } else +#endif + { + mp->mismatch = ch; + mp->replay = mp->in_boundary; mp->in_boundary = 0; - } - ch = mp->req->query.multipartboundary[mp->replayhead++]; + } - debug_printf("|%d..%d", mp->replayhead, mp->replay); - } - debug_putc('}'); + goto replay; + } return ch; } ch = picohttpGetch(mp->req); @@ -1326,13 +1295,11 @@ struct picohttpMultipart picohttpMultipartStart( .finished = 0, .contenttype = 0, .disposition = { .name = {0,} }, - .in_boundary = 0, - .replay = 0, - .replayhead = 0 + .in_boundary = 2, + .replayhead = 2, + .mismatch = -1, }; - picohttpMultipartSync(&mp); - return mp; } @@ -1352,11 +1319,8 @@ int picohttpMultipartNext( if( 1 == mp->finished ) { mp->finished = 0; - mp->replay = 0; - mp->replayhead = 0; - mp->in_boundary = 0; - if( 0 > (ch = picohttpMultipartGetch(mp)) ) + if( 0 > (ch = picohttpGetch(mp->req)) ) return ch; if( 0 > (ch = picohttpProcessHeaders( @@ -1373,6 +1337,9 @@ int picohttpMultipartNext( 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'; diff --git a/picohttp.h b/picohttp.h index d3931ab..59e792e 100644 --- a/picohttp.h +++ b/picohttp.h @@ -150,6 +150,7 @@ struct picohttpMultipart { int in_boundary; int replay; int replayhead; + int mismatch; }; typedef void (*picohttpHeaderFieldCallback)( -- cgit v1.2.3