From 720a430b3d9cc87512f1625e6d78391480dfb062 Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Fri, 8 Mar 2013 20:08:35 +0100 Subject: Fri Mar 8 20:08:35 CET 2013 --- picohttp.c | 529 +++++++++++++++++++++++++++++++++++++++++------------- picohttp.h | 29 ++- test/bsd_socket.c | 45 ++++- 3 files changed, 473 insertions(+), 130 deletions(-) diff --git a/picohttp.c b/picohttp.c index 10b7d3c..feef174 100644 --- a/picohttp.c +++ b/picohttp.c @@ -2,47 +2,131 @@ #include #include +#include static void picohttpStatus400BadRequest( struct picohttpRequest *req ) { + fputs("400\n", stderr); } static void picohttpStatus404NotFound( struct picohttpRequest *req ) { + char http_header[] = "HTTP/x.x 404 Not Found\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); + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); } static void picohttpStatus405MethodNotAllowed( struct picohttpRequest *req ) { + fputs("405\n", stderr); } static void picohttpStatus414RequestURITooLong( struct picohttpRequest *req ) { + char http_header[] = "HTTP/x.x 414 URI Too Long\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); + picohttpIoWrite(req->ioops, sizeof(http_header)-1, http_header); } static void picohttpStatus500InternalServerError( struct picohttpRequest *req ) { + fputs("500\n", stderr); } static void picohttpStatus501NotImplemented( struct picohttpRequest *req ) { + fputs("501\n", stderr); } static void picohttpStatus505HTTPVersionNotSupported( struct picohttpRequest *req ) { + fputs("505\n", stderr); } -static int16_t picohttpIoSkipSpace( - struct picohttpIoOps const * const ioops ) +static uint8_t picohttpIsCRLF(int16_t ch) { - int16_t ch; - while(' ' == (char)(ch = picohttpIoGetch(ioops))); + switch(ch) { + case '\r': + case '\n': + return 1; + } + return 0; +} + +static uint8_t picohttpIsLWS(int16_t ch) +{ + return + picohttpIsCRLF(ch) || + ' ' == ch || '\t' == ch; +} + +static int16_t picohttpIoSkipSpace ( + struct picohttpIoOps const * const ioops, + int16_t ch) +{ + for(;;ch = 0) { + if(!ch) + ch = picohttpIoGetch(ioops); + if( 0 >= ch || + ( ' ' != ch && '\t' != ch ) ) + break; + } + return ch; +} + +static int16_t picohttpIoSkipOverCRLF ( + struct picohttpIoOps const * const ioops, + int16_t ch) +{ + for(;;ch = 0) { + if(!ch) + ch = picohttpIoGetch(ioops); + if( ch < 0 ) { + return -1; + } + if( ch == '\n' ) { + break; + } + if( ch == '\r' ) { + ch = picohttpIoGetch(ioops); + if( ch < 0 ) { + return -1; + } + if( ch != '\n' ) { + return 0; + } + break; + } + } + ch = picohttpIoGetch(ioops); + return ch; +} + +static int16_t picohttpIoB10ToU8 ( + uint8_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; } @@ -56,10 +140,10 @@ static int16_t picohttpIoGetPercentCh( chr |= 0x20; if( chr >= '0' && chr <= '9' ) { - ch = ((chr)&0x0f); + ch = ((chr)&0x0f)<<4; } else if( chr >= 'a' && chr <= 'f' ) { - ch = (((chr)&0x0f) + 9); + ch = (((chr)&0x0f) + 9)<<4; } if( 0 > (chr = picohttpIoGetch(ioops))) @@ -67,199 +151,402 @@ static int16_t picohttpIoGetPercentCh( chr |= 0x20; if( chr >= '0' && chr <= '9' ) { - ch |= ((chr)&0x0f) << 4; + ch |= ((chr)&0x0f); } else if( chr >= 'a' && chr <= 'f' ) { - ch |= (((chr)&0x0f) + 9) << 4; + ch |= (((chr)&0x0f) + 9); } return ch; } -int picohttpMatchRoute( - struct picohttpRequest * const req, - struct picohttpURLRoute const * const routes ) +static size_t picohttpMatchURL( + char const * const urlhead, + char const * const url ) { -} - -void picohttpProcessRequest( - struct picohttpIoOps const * const ioops, - struct picohttpURLRoute const * const routes ) -{ - char *url, *var; - struct picohttpRequest request = {0,}; - size_t url_max_length = 0; - size_t var_max_length = 0; - - for(size_t i = 0; routes[i].urlhead; i++) { - size_t url_length = - strlen(routes[i].urlhead) + - routes[i].max_urltail_len; + size_t len_urlhead = strlen(urlhead); + size_t j; + for(j = 0; j < len_urlhead; j++) { + if( '|' == urlhead[j] ) { + /* hard URL termination */ + if( url[j] ) { + return 0; + } + break; + } - if(url_length > url_max_length) - url_max_length = url_length; + if( '\\' == urlhead[j] ) { + /* soft URL termination, i.e. URL may be terminated + * by an optional '/' character */ + if( url[j] && !( url[j] == '/' && !url[j+1] ) ) { + return 0; + } + break; + } + if( urlhead[j] != url[j] ) { + return 0; + } } - url = alloca(url_max_length+1); - memset(url, 0, url_max_length+1); + if( url[j] && url[j] != '/' ) { + return 0; + } + return j; +} - request.url = url; - request.urltail = 0; - request.ioops = ioops; - request.method = 0; +static int8_t picohttpMatchRoute( + struct picohttpRequest * const req, + struct picohttpURLRoute const * const routes ) +{ + struct picohttpURLRoute const *r; + for(size_t i = 0; (r = routes + i)->urlhead; i++) { + size_t l; + if( (l = picohttpMatchURL(r->urlhead, req->url)) && + req->method & r->allowed_methods ) { + req->route = r; + req->urltail = req->url[l] ? req->url+l : 0; + return 1; + } + } + return 0; +} - /* Poor mans string matching tree; trade RAM for code */ +static int16_t picohttpProcessRequestMethod ( + struct picohttpIoOps const * const ioops ) +{ + int16_t method = 0; + /* Poor man's string matching tree; trade RAM for code */ switch( picohttpIoGetch(ioops) ) { case 'H': switch( picohttpIoGetch(ioops) ) { case 'E': switch( picohttpIoGetch(ioops) ) { case 'A': switch( picohttpIoGetch(ioops) ) { case 'D': - request.method = PICOHTTP_METHOD_HEAD; + method = PICOHTTP_METHOD_HEAD; break; - } + case -1: + method = -1; + break; + } break; + case -1: + method = -1; + break; } break; + case -1: + method = -1; + break; } break; case 'G': switch( picohttpIoGetch(ioops) ) { case 'E': switch( picohttpIoGetch(ioops) ) { case 'T': - request.method = PICOHTTP_METHOD_GET; + method = PICOHTTP_METHOD_GET; + break; + case -1: + method = -1; + break; } break; + case -1: + method = -1; + break; } break; case 'P': switch( picohttpIoGetch(ioops) ) { case 'O': switch( picohttpIoGetch(ioops) ) { case 'S': switch( picohttpIoGetch(ioops) ) { case 'T': - request.method = PICOHTTP_METHOD_POST; + method = PICOHTTP_METHOD_POST; break; - } + case -1: + method = -1; + break; + } break; + case -1: + method = -1; + break; } break; + case -1: + method = -1; + break; } break; + case -1: + method = -1; + break; } - if( !request.method ) { - picohttpStatus501NotImplemented(&request); - return; - } - - int16_t ch; + return method; +} - ch = picohttpIoSkipSpace(ioops); - if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; - } - - url[0] = (char)ch; - /* copy url up to the first query variable; note that this is not - * fully compliant to RFC 6874, which permits query components in each - * path component (i.e. between '/'-es). */ - for(char *urliter = url+1 ;; urliter++) { - if( urliter - url >= url_max_length ) { - picohttpStatus414RequestURITooLong(&request); - return; - } - ch = picohttpIoGetch(ioops); +static int16_t picohttpProcessURL ( + struct picohttpRequest * const req, + size_t const url_max_length, + int16_t 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 + * path component (i.e. between '/'-es). + * picohttp terminates the path once it encounters the first query + * component. + */ + /* Deliberately discarding const qualifier! */ + for(char *urliter = (char*)req->url ;; urliter++) { if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; + return -500; } - if( '?' == (char)ch || ' ' == (char)ch ) { + if( '?' == ch || + picohttpIsLWS(ch) ) { break; } - if( '%' == (char)ch ) { - ch = picohttpIoGetPercentCh(ioops); + if( '%' == ch ) { + ch = picohttpIoGetPercentCh(req->ioops); + if( ch < 0 ) { + return -500; + } } - if(ch < 0) { - picohttpStatus500InternalServerError(&request); - return; + if( !ch ) { + return -400; } - *urliter = (char)ch; - } + if( urliter - req->url >= url_max_length ) { + return -414; + } + *urliter = ch; - if( !picohttpMatchRoute(&request, routes) ) { - picohttpStatus404NotFound(&request); - return; - } - if( !(request.route->allowed_methods & request.method) ) { - picohttpStatus405MethodNotAllowed(&request); - return; + ch = picohttpIoGetch(req->ioops); } + return ch; +} - if(request.route->get_vars) { - for(size_t j = 0; request.route->get_vars[j].name; j++) { +static int16_t picohttpProcessQuery ( + struct picohttpRequest * const req, + int16_t ch ) +{ + size_t var_max_length = 0; + if(req->route->get_vars) { + for(size_t j = 0; req->route->get_vars[j].name; j++) { size_t var_length = strlen( - request.route->get_vars[j].name ); + req->route->get_vars[j].name ); if( var_length > var_max_length ) { var_max_length = var_length; } } } - var = alloca(var_max_length+1); + char *var = alloca(var_max_length+1); - while('?' == (char)ch || '&' ==(char)ch) { + while('?' == ch || '&' == ch) { memset(var, 0, var_max_length+1); - ch = picohttpIoGetch(ioops); + ch = picohttpIoGetch(req->ioops); if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; + return -500; } - - if( '&' == (char)ch ) + if( '&' == ch ) continue; - if( '%' == (char)ch ) { - ch = picohttpIoGetPercentCh(ioops); - } - if(ch < 0) { - picohttpStatus500InternalServerError(&request); - return; - } - var[0] = ch; - for(char *variter = var+1 ;; variter++) { + for(char *variter = var ;; variter++) { + if( ch < 0 ) { + return -500; + } + if( '=' == ch || + '#' == ch || + '&' == ch || + picohttpIsLWS(ch) ) { + break; + } + if( '%' == ch ) { + ch = picohttpIoGetPercentCh(req->ioops); + if( ch < 0 ) { + return -500; + } + } + if( !ch ) { + return -400; + } + if( variter - var >= var_max_length ) { - /* variable name longer than longest accepted - * variable name --> skip to next variable */ +/* variable name in request longer than longest + * variable name accepted by route --> skip to next variable */ do { - ch = picohttpIoGetch(ioops); + ch = picohttpIoGetch(req->ioops); if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; + return -500; } - } while ( '&' != ch ); + } while(!( '&' == ch || + picohttpIsLWS(ch) )); continue; } + *variter = ch; - ch = picohttpIoGetch(ioops); - if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; - } + ch = picohttpIoGetch(req->ioops); } + if( '=' == ch ) { + } else { + } + } + if( 0 > (ch = picohttpIoSkipSpace(req->ioops, ch)) ) { + return -500; } - ch = picohttpIoSkipSpace(ioops); + return ch; +} + +static int16_t picohttpProcessHTTPVersion ( + struct picohttpRequest * const req, + int16_t ch ) +{ + if( !picohttpIsCRLF(ch) ) { + for(uint8_t i = 0; i < 5; i++) { + if("HTTP/"[i] != (char)ch ) { + if( ch < 0 ) { + return -500; + } + return -400; + } + ch = picohttpIoGetch(req->ioops); + } + + req->httpversion.major = 0; + req->httpversion.minor = 0; + ch = picohttpIoB10ToU8( + &req->httpversion.major, + req->ioops, + ch ); + if( ch < 0 ) { + return -500; + } + if( ch != '.' ) { + return -400; + } + ch = picohttpIoB10ToU8( + &req->httpversion.minor, + req->ioops, + 0 ); + if( ch < 0 ) { + return -500; + } + + ch = picohttpIoSkipSpace(req->ioops, ch); + if( ch < 0 ) { + return -500; + } + } + ch = picohttpIoSkipOverCRLF(req->ioops, ch); if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - return; + return -500; } - for(uint8_t i = 0; i < 4; i++) { - if("HTTP"[i] != (char)(ch = picohttpIoGetch(ioops)) ) { - if( ch < 0 ) { - picohttpStatus500InternalServerError(&request); - } else { - picohttpStatus400BadRequest(&request); + if( !ch ) { + return -400; + } + + return ch; +} + +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 */ + while( !picohttpIsCRLF(ch) ) { + fprintf(stderr, "\n>>> 0x%02x ", (int)ch, stderr); + + while( !picohttpIsCRLF( ch=picohttpIoSkipSpace(req->ioops, ch)) ){ + fputc(ch, stderr); + if( 0 > ( ch=picohttpIoGetch(req->ioops) ) ) { + return -500; } - return; } + + ch = picohttpIoSkipOverCRLF(req->ioops, ch); + if( 0 > ch ) { + return -500; + } + if( !ch ) { + return -400; + } + } + fputc('\n', stderr); + return ch; +} + +void picohttpProcessRequest ( + struct picohttpIoOps const * const ioops, + struct picohttpURLRoute const * const routes ) +{ + char *url; + struct picohttpRequest request = {0,}; + size_t url_max_length = 0; + + for(size_t i = 0; routes[i].urlhead; i++) { + size_t url_length = + strlen(routes[i].urlhead) + + routes[i].max_urltail_len; + + if(url_length > url_max_length) + url_max_length = url_length; + + } + url = alloca(url_max_length+1); + memset(url, 0, url_max_length+1); + + request.url = url; + request.urltail = 0; + request.ioops = ioops; + request.method = 0; + request.httpversion.major = 1; + request.httpversion.minor = 0; + + request.method = picohttpProcessRequestMethod(ioops); + if( !request.method ) { + picohttpStatus501NotImplemented(&request); + return; + } + if( 0 > request.method ) { + picohttpStatus500InternalServerError(&request); + return; } - if( PICOHTTP_MAJORVERSION(request.httpversion) > 1 || - PICOHTTP_MINORVERSION(request.httpversion) > 1 ) { + int16_t ch; + if( 0 > (ch = picohttpIoSkipSpace(ioops, 0)) ) { + picohttpStatus500InternalServerError(&request); + return; + } + + if( 0 > (ch = picohttpProcessURL(&request, url_max_length, ch)) ) + goto http_error; + + if( !picohttpMatchRoute(&request, routes) || !request.route ) { + picohttpStatus404NotFound(&request); + return; + } + if( !(request.route->allowed_methods & request.method) ) { + picohttpStatus405MethodNotAllowed(&request); + return; + } + + if( 0 > (ch = picohttpProcessQuery(&request, ch)) ) + goto http_error; + + if( 0 > (ch = picohttpProcessHTTPVersion (&request, ch)) ) + goto http_error; + + if( request.httpversion.major > 1 || + request.httpversion.minor > 1 ) { picohttpStatus505HTTPVersionNotSupported(&request); return; } + if( 0 > (ch = picohttpProcessHeaders(&request, ch)) ) + goto http_error; + request.route->handler(&request); + return; + +http_error: + switch(-ch) { + case 400: picohttpStatus400BadRequest(&request); break; + case 404: picohttpStatus404NotFound(&request); break; + case 405: picohttpStatus405MethodNotAllowed(&request); break; + case 500: picohttpStatus500InternalServerError(&request); break; + } } + diff --git a/picohttp.h b/picohttp.h index cceee44..9df3596 100644 --- a/picohttp.h +++ b/picohttp.h @@ -14,7 +14,7 @@ struct picohttpIoOps { int (*read)(size_t /*count*/, char* /*buf*/, void*); - int (*write)(size_t /*count*/, char* /*buf*/, void*); + int (*write)(size_t /*count*/, char const* /*buf*/, void*); int16_t (*getch)(void*); // returns -1 on error int (*putch)(char, void*); void *data; @@ -52,24 +52,41 @@ struct picohttpVar { struct picohttpRequest; -typedef void (*picohttpHandler) (struct picohttpRequest *ctx); +typedef void (*picohttpHandler)(struct picohttpRequest*); struct picohttpURLRoute { char const * urlhead; struct picohttpVarSpec const * get_vars; picohttpHandler handler; uint16_t max_urltail_len; - uint8_t allowed_methods; + int16_t allowed_methods; }; struct picohttpRequest { struct picohttpIoOps const * ioops; - struct picohttpURLRoute *route; + struct picohttpURLRoute const * route; struct picohttpVar *get_vars; char const *url; char const *urltail; - int16_t httpversion; - uint8_t method; + int16_t status; + int16_t method; + struct { + uint8_t major; + uint8_t minor; + } httpversion; + struct { + uint8_t encoding; + char const *contenttype; + size_t contentlength; + } queryheader; + struct { + uint8_t encoding; + char const *contenttype; + char const *date; + char const *cachecontrol; + char const *disposition; + size_t contentlength; + } responseheader; }; void picohttpProcessRequest( diff --git a/test/bsd_socket.c b/test/bsd_socket.c index e03f882..d4bd5a2 100644 --- a/test/bsd_socket.c +++ b/test/bsd_socket.c @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -40,7 +41,7 @@ int bsdsock_read(size_t count, char *buf, void *data) return rb; } -int bsdsock_write(size_t count, char *buf, void *data) +int bsdsock_write(size_t count, char const *buf, void *data) { int fd = *((int*)data); @@ -92,6 +93,33 @@ void bye(void) 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); @@ -99,9 +127,11 @@ int main(int argc, char *argv[]) perror("socket"); return -1; } +#if 0 if( atexit(bye) ) { return -1; } +#endif struct sockaddr_in addr = { .sin_family = AF_INET, @@ -109,6 +139,8 @@ int main(int argc, char *argv[]) .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; @@ -141,11 +173,18 @@ int main(int argc, char *argv[]) .data = &confd }; - char const hellostr[] = "Hello World!\n"; - write(confd, hellostr, sizeof(hellostr)); + 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