aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfgang Draxinger <Wolfgang.Draxinger@physik.uni-muenchen.de>2013-03-06 21:58:47 +0100
committerWolfgang Draxinger <Wolfgang.Draxinger@physik.uni-muenchen.de>2013-03-06 21:58:47 +0100
commitdfb3c1ca13a828405503d6ec2ee3979ae825326b (patch)
tree7f6197650df3f86401eb3e12d23b9266745c5ae9
downloadlitheweb-dfb3c1ca13a828405503d6ec2ee3979ae825326b.tar.gz
litheweb-dfb3c1ca13a828405503d6ec2ee3979ae825326b.tar.bz2
Wed Mar 6 21:58:47 CET 2013
-rw-r--r--README0
-rw-r--r--picohttp.c265
-rw-r--r--picohttp.h79
-rw-r--r--test/bsd_socket.c151
4 files changed, 495 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README
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 <alloca.h>
+#include <string.h>
+
+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 <stdlib.h>
+#include <stdint.h>
+
+#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 <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+
+#include "../picohttp.h"
+
+int bsdsock_read(size_t count, 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;
+}