From dfb3c1ca13a828405503d6ec2ee3979ae825326b Mon Sep 17 00:00:00 2001 From: Wolfgang Draxinger Date: Wed, 6 Mar 2013 21:58:47 +0100 Subject: Wed Mar 6 21:58:47 CET 2013 --- README | 0 picohttp.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ picohttp.h | 79 ++++++++++++++++ test/bsd_socket.c | 151 +++++++++++++++++++++++++++++++ 4 files changed, 495 insertions(+) create mode 100644 README create mode 100644 picohttp.c create mode 100644 picohttp.h create mode 100644 test/bsd_socket.c diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/picohttp.c b/picohttp.c new file mode 100644 index 0000000..10b7d3c --- /dev/null +++ b/picohttp.c @@ -0,0 +1,265 @@ +#include "picohttp.h" + +#include +#include + +static void picohttpStatus400BadRequest( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus404NotFound( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus405MethodNotAllowed( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus414RequestURITooLong( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus500InternalServerError( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus501NotImplemented( + struct picohttpRequest *req ) +{ +} + +static void picohttpStatus505HTTPVersionNotSupported( + struct picohttpRequest *req ) +{ +} + +static int16_t picohttpIoSkipSpace( + struct picohttpIoOps const * const ioops ) +{ + int16_t ch; + while(' ' == (char)(ch = picohttpIoGetch(ioops))); + return ch; +} + +static int16_t picohttpIoGetPercentCh( + struct picohttpIoOps const * const ioops ) +{ + char ch; + int16_t chr; + if( 0 > (chr = picohttpIoGetch(ioops))) + return chr; + + chr |= 0x20; + if( chr >= '0' && chr <= '9' ) { + ch = ((chr)&0x0f); + } else if( + chr >= 'a' && chr <= 'f' ) { + ch = (((chr)&0x0f) + 9); + } + + if( 0 > (chr = picohttpIoGetch(ioops))) + return chr; + + chr |= 0x20; + if( chr >= '0' && chr <= '9' ) { + ch |= ((chr)&0x0f) << 4; + } else if( + chr >= 'a' && chr <= 'f' ) { + ch |= (((chr)&0x0f) + 9) << 4; + } + + return ch; +} + +int picohttpMatchRoute( + struct picohttpRequest * const req, + struct picohttpURLRoute const * const routes ) +{ +} + +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; + + 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; + + /* Poor mans 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; + break; + } + } break; + } break; + case 'G': switch( picohttpIoGetch(ioops) ) { + case 'E': switch( picohttpIoGetch(ioops) ) { + case 'T': + request.method = PICOHTTP_METHOD_GET; + } break; + } break; + case 'P': switch( picohttpIoGetch(ioops) ) { + case 'O': switch( picohttpIoGetch(ioops) ) { + case 'S': switch( picohttpIoGetch(ioops) ) { + case 'T': + request.method = PICOHTTP_METHOD_POST; + break; + } + } break; + } break; + } + if( !request.method ) { + picohttpStatus501NotImplemented(&request); + return; + } + + int16_t ch; + + 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); + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + return; + } + if( '?' == (char)ch || ' ' == (char)ch ) { + break; + } + if( '%' == (char)ch ) { + ch = picohttpIoGetPercentCh(ioops); + } + if(ch < 0) { + picohttpStatus500InternalServerError(&request); + return; + } + + *urliter = (char)ch; + } + + if( !picohttpMatchRoute(&request, routes) ) { + picohttpStatus404NotFound(&request); + return; + } + if( !(request.route->allowed_methods & request.method) ) { + picohttpStatus405MethodNotAllowed(&request); + return; + } + + if(request.route->get_vars) { + for(size_t j = 0; request.route->get_vars[j].name; j++) { + size_t var_length = strlen( + request.route->get_vars[j].name ); + if( var_length > var_max_length ) { + var_max_length = var_length; + } + } + } + var = alloca(var_max_length+1); + + while('?' == (char)ch || '&' ==(char)ch) { + memset(var, 0, var_max_length+1); + ch = picohttpIoGetch(ioops); + + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + return; + } + + if( '&' == (char)ch ) + continue; + + if( '%' == (char)ch ) { + ch = picohttpIoGetPercentCh(ioops); + } + if(ch < 0) { + picohttpStatus500InternalServerError(&request); + return; + } + + var[0] = ch; + for(char *variter = var+1 ;; variter++) { + if( variter - var >= var_max_length ) { + /* variable name longer than longest accepted + * variable name --> skip to next variable */ + do { + ch = picohttpIoGetch(ioops); + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + return; + } + } while ( '&' != ch ); + continue; + } + + ch = picohttpIoGetch(ioops); + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + return; + } + } + } + + ch = picohttpIoSkipSpace(ioops); + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + return; + } + for(uint8_t i = 0; i < 4; i++) { + if("HTTP"[i] != (char)(ch = picohttpIoGetch(ioops)) ) { + if( ch < 0 ) { + picohttpStatus500InternalServerError(&request); + } else { + picohttpStatus400BadRequest(&request); + } + return; + } + } + + if( PICOHTTP_MAJORVERSION(request.httpversion) > 1 || + PICOHTTP_MINORVERSION(request.httpversion) > 1 ) { + picohttpStatus505HTTPVersionNotSupported(&request); + return; + } + + request.route->handler(&request); +} diff --git a/picohttp.h b/picohttp.h new file mode 100644 index 0000000..cceee44 --- /dev/null +++ b/picohttp.h @@ -0,0 +1,79 @@ +#pragma once +#ifndef PICOHTTP_H_HEADERGUARD +#define PICOHTTP_H_HEADERGUARD + +#include +#include + +#define PICOHTTP_MAJORVERSION(x) ( (x & 0x7f00) >> 8 ) +#define PICOHTTP_MINORVERSION(x) ( (x & 0x007f) ) + +#define PICOHTTP_METHOD_GET 1 +#define PICOHTTP_METHOD_HEAD 2 +#define PICOHTTP_METHOD_POST 3 + +struct picohttpIoOps { + int (*read)(size_t /*count*/, char* /*buf*/, void*); + int (*write)(size_t /*count*/, char* /*buf*/, void*); + int16_t (*getch)(void*); // returns -1 on error + int (*putch)(char, void*); + void *data; +}; + +#define picohttpIoWrite(ioops,size,buf) (ioops->write(size, buf, ioops->data)) +#define picohttpIoRead(ioops,size,buf) (ioops->read(size, buf, ioops->data)) +#define picohttpIoGetch(ioops) (ioops->getch(ioops->data)) +#define picohttpIoPutch(ioops,c) (ioops->putch(c, ioops->data)) + +enum picohttpVarType { + PICOHTTP_TYPE_UNDEFINED = 0, + PICOHTTP_TYPE_INTEGER = 1, + PICOHTTP_TYPE_REAL = 2, + PICOHTTP_TYPE_BOOLEAN = 3, + PICOHTTP_TYPE_TEXT = 4 +}; + +struct picohttpVarSpec { + char const * const name; + enum picohttpVarType type; + size_t max_len; +}; + +struct picohttpVar { + struct picohttpVarSpec const *spec; + union { + char *text; + float real; + int integer; + uint8_t boolean; + } value; + struct picohttpVar *next; +}; + +struct picohttpRequest; + +typedef void (*picohttpHandler) (struct picohttpRequest *ctx); + +struct picohttpURLRoute { + char const * urlhead; + struct picohttpVarSpec const * get_vars; + picohttpHandler handler; + uint16_t max_urltail_len; + uint8_t allowed_methods; +}; + +struct picohttpRequest { + struct picohttpIoOps const * ioops; + struct picohttpURLRoute *route; + struct picohttpVar *get_vars; + char const *url; + char const *urltail; + int16_t httpversion; + uint8_t method; +}; + +void picohttpProcessRequest( + struct picohttpIoOps const * const ioops, + struct picohttpURLRoute const * const routes ); + +#endif/*PICOHTTP_H_HEADERGUARD*/ diff --git a/test/bsd_socket.c b/test/bsd_socket.c new file mode 100644 index 0000000..e03f882 --- /dev/null +++ b/test/bsd_socket.c @@ -0,0 +1,151 @@ +#define _BSD_SOURCE + +#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 *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); +} + +int main(int argc, char *argv[]) +{ + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if( -1 == sockfd ) { + perror("socket"); + return -1; + } + if( atexit(bye) ) { + return -1; + } + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(8000), + .sin_addr = 0 + }; + + 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 + }; + + char const hellostr[] = "Hello World!\n"; + write(confd, hellostr, sizeof(hellostr)); + shutdown(confd, SHUT_RDWR); + close(confd); + } + + return 0; +} -- cgit v1.2.3