diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | fhp.c | 591 | ||||
-rw-r--r-- | include/fhp/fhp.h | 97 | ||||
-rw-r--r-- | test.c | 59 |
5 files changed, 762 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9dab3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/*.o +/fhp-test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9541695 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CC ?= cc +CFLAGS=-std=c99 -W -Wall -pedantic -O2 -Iinclude +OBJS=fhp.o test.o +APP=fhp-test + +all: $(OBJS) + $(CC) -o $(APP) $(OBJS) + +%.o: %.c include/fhp/fhp.h + $(CC) -c $(CFLAGS) $< + +clean: + rm -f $(APP) $(OBJS) @@ -0,0 +1,591 @@ +#include <string.h> +#include "fhp/fhp.h" + +#define UNUSED(a) ((void) (a)) + +// +// rfc7230, Appendix B +// https://tools.ietf.org/html/rfc7230 +// +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +// token = 1*tchar +// +#define CASE_TOKEN_CHARS \ + case '!': \ + case '#': \ + case '$': \ + case '%': \ + case '&': \ + case '\'': \ + case '*': \ + case '+': \ + case '-': \ + case '.': \ + case '^': \ + case '_': \ + case '|': \ + case '~': \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9': \ + case 'a': \ + case 'b': \ + case 'c': \ + case 'd': \ + case 'e': \ + case 'f': \ + case 'g': \ + case 'h': \ + case 'i': \ + case 'j': \ + case 'k': \ + case 'l': \ + case 'm': \ + case 'n': \ + case 'o': \ + case 'p': \ + case 'q': \ + case 'r': \ + case 's': \ + case 't': \ + case 'u': \ + case 'v': \ + case 'w': \ + case 'x': \ + case 'y': \ + case 'z': \ + case 'A': \ + case 'B': \ + case 'C': \ + case 'D': \ + case 'E': \ + case 'F': \ + case 'G': \ + case 'H': \ + case 'I': \ + case 'J': \ + case 'K': \ + case 'L': \ + case 'M': \ + case 'N': \ + case 'O': \ + case 'P': \ + case 'Q': \ + case 'R': \ + case 'S': \ + case 'T': \ + case 'U': \ + case 'V': \ + case 'W': \ + case 'X': \ + case 'Y': \ + case 'Z': + +// +// rfc7230, Appendix B +// https://tools.ietf.org/html/rfc7230 +// +// OWS = *( SP / HTAB ) +// +#define CASE_OWS_CHARS \ + case ' ': \ + case '\t': + +static const char * +fhp_errors[] = { + "OK", + "callback error", + "bad state", + "invalid character", + "invalid error code", + "buffer too small", +}; + +fhp_err_t +fhp_strerror( + fhp_err_t err, + char * const buf, + size_t len +) { + // check error code + if (err >= FHP_ERR_LAST) + return FHP_ERR_INVALID_ERROR; + + // check buffer size + size_t err_len = strlen(fhp_errors[err]) + 1; + if (len < err_len) + return FHP_ERR_BUFFER_TOO_SMALL; + + // copy string + memcpy(buf, fhp_errors[err], err_len); + + // return success + return FHP_OK; +} + +static fhp_t DEFAULT_CONTEXT = { + .state = FHP_STATE_INIT, + .user_data = NULL, + .cb = NULL, + .err = FHP_OK, + .ofs = 0, + .buf_len = 0, +}; + +fhp_err_t +fhp_init( + fhp_t * const fhp, + fhp_cb_t cb, + void * const user_data +) { + *fhp = DEFAULT_CONTEXT; + fhp->user_data = user_data; + fhp->cb = cb; + + /* return success */ + return FHP_OK; +} + +static void +fhp_buf_clear(fhp_t * const fhp) { + // clear buffer + fhp->buf_len = 0; +} + +static bool +fhp_buf_flush( + fhp_t * const fhp, + fhp_token_t token +) { + if (fhp->buf_len > 0) { + // push data + if (!fhp->cb(fhp, token, fhp->buf, fhp->buf_len)) + return false; + + // clear buffer + fhp_buf_clear(fhp); + } + + // return success + return true; +} + +static bool +fhp_buf_push( + fhp_t * const fhp, + fhp_token_t token, + uint8_t byte +) { + // flush buffer + if (fhp->buf_len + 1 >= FHP_BUF_SIZE) { + if (!fhp_buf_flush(fhp, token)) + return false; + } + + // append to buffer + fhp->buf[fhp->buf_len] = byte; + fhp->buf_len++; + + // return success + return true; +} + +static fhp_err_t +fhp_push_byte( + fhp_t * const fhp, + uint8_t byte +) { +retry: + switch (fhp->state) { + case FHP_STATE_INIT: + switch (byte) { + CASE_TOKEN_CHARS + // send start token + if (!fhp->cb(fhp, FHP_TOKEN_METHOD_START, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_METHOD; + goto retry; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_METHOD: + switch (byte) { + CASE_TOKEN_CHARS + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_METHOD_FRAGMENT, byte)) + return FHP_ERR_CB; + + break; + case ' ': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_METHOD_FRAGMENT)) + return FHP_ERR_CB; + + // send end token + if (!fhp->cb(fhp, FHP_TOKEN_METHOD_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_METHOD_END; + goto retry; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_METHOD_END: + switch (byte) { + case ' ': + // FIXME: do we want to allow more than one whitespace? + // ignore + break; + default: + if (!fhp->cb(fhp, FHP_TOKEN_URL_START, 0, 0)) + return FHP_ERR_CB; + + fhp->state = FHP_STATE_URL; + goto retry; + } + + break; + case FHP_STATE_URL: + switch (byte) { + case ' ': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_URL_FRAGMENT)) + return FHP_ERR_CB; + + // send end token + if (!fhp->cb(fhp, FHP_TOKEN_URL_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_URL_END; + goto retry; + + break; + default: + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_URL_FRAGMENT, byte)) + return FHP_ERR_CB; + } + + break; + case FHP_STATE_URL_END: + switch (byte) { + case ' ': + // ignore + break; + default: + if (!fhp->cb(fhp, FHP_TOKEN_VERSION_START, 0, 0)) + return FHP_ERR_CB; + + fhp->state = FHP_STATE_VERSION; + goto retry; + } + + break; + case FHP_STATE_VERSION: + switch (byte) { + case '\r': + case '\n': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_VERSION_FRAGMENT)) + return FHP_ERR_CB; + + // send end token + if (!fhp->cb(fhp, FHP_TOKEN_VERSION_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_VERSION_END; + goto retry; + + break; + case ' ': + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + + break; + default: + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_VERSION_FRAGMENT, byte)) + return FHP_ERR_CB; + } + + break; + case FHP_STATE_VERSION_END: + switch (byte) { + case '\r': + fhp->state = FHP_STATE_VERSION_END_CR; + + break; + case '\n': + fhp->state = FHP_STATE_STATUS_END; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + }; + + break; + case FHP_STATE_VERSION_END_CR: + switch (byte) { + case '\n': + fhp->state = FHP_STATE_STATUS_END; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + }; + + break; + case FHP_STATE_STATUS_END: + // TODO + switch (byte) { + CASE_TOKEN_CHARS + // send start token + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_HEADER_NAME; + goto retry; + + break; + case '\r': + // set state + fhp->state = FHP_STATE_STATUS_END_CR; + + break; + case '\n': + // set state + fhp->state = FHP_STATE_HEADERS_END; + + break; + } + + break; + case FHP_STATE_STATUS_END_CR: + switch (byte) { + case '\n': + // set state + fhp->state = FHP_STATE_HEADERS_END; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_HEADER_NAME: + switch (byte) { + CASE_TOKEN_CHARS + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) + return FHP_ERR_CB; + + break; + case ':': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT)) + return FHP_ERR_CB; + + // send end token + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_HEADER_NAME_END; + + break; + default: + // FIXME: invalid character + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_HEADER_NAME_END: + switch (byte) { + CASE_OWS_CHARS + // ignore leading spaces + + break; + default: + // send start token + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_START, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_HEADER_VALUE; + goto retry; + + break; + } + + break; + case FHP_STATE_HEADER_VALUE: + switch (byte) { + case '\r': + fhp->state = FHP_STATE_HEADER_VALUE_END_CR; + + break; + case '\n': + fhp->state = FHP_STATE_HEADER_VALUE_END; + break; + default: + // FIXME: need more limits on valid octets + + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, byte)) + return FHP_ERR_CB; + + break; + } + + break; + case FHP_STATE_HEADER_VALUE_END_CR: + switch (byte) { + case '\n': + fhp->state = FHP_STATE_HEADER_VALUE_END; + break; + default: + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_HEADER_VALUE_END: + switch (byte) { + CASE_OWS_CHARS + // ows-fold + + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, ' ')) + return FHP_ERR_CB; + + fhp->state = FHP_STATE_HEADER_VALUE; + break; + CASE_TOKEN_CHARS + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) + return FHP_ERR_CB; + + // end header value + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) + return FHP_ERR_CB; + + // send start token + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) + return FHP_ERR_CB; + + // add to buffer + if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) + return FHP_ERR_CB; + + break; + case '\r': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) + return FHP_ERR_CB; + + // end header value + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_HEADERS_END_CR; + + break; + case '\n': + // flush buffer + if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) + return FHP_ERR_CB; + + // end header value + if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) + return FHP_ERR_CB; + + // set state + fhp->state = FHP_STATE_HEADERS_END; + + break; + default: + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_HEADERS_END_CR: + switch (byte) { + case '\n': + fhp->state = FHP_STATE_HEADERS_END; + + break; + default: + return FHP_ERR_INVALID_CHAR; + } + + break; + case FHP_STATE_HEADERS_END: + // TODO + break; + default: + // FIXME: invalid state + return FHP_ERR_BAD_STATE; + } + + // increment byte offset + fhp->ofs++; + + /* return success */ + return FHP_OK; +} + +fhp_err_t +fhp_push( + fhp_t * const fhp, + uint8_t * const buf, + size_t len +) { + switch (fhp->state) { + case FHP_STATE_ERROR: + return fhp->err; + + break; + default: + for (size_t i = 0; i < len; i++) { + // push byte + fhp_err_t err = fhp_push_byte(fhp, buf[i]); + + // check result + if (err != FHP_OK) { + fhp->state = FHP_STATE_ERROR; + fhp->err = err; + return err; + } + } + } + + // return success + return FHP_OK; +} diff --git a/include/fhp/fhp.h b/include/fhp/fhp.h new file mode 100644 index 0000000..4a54e3c --- /dev/null +++ b/include/fhp/fhp.h @@ -0,0 +1,97 @@ +#ifndef FHP_H_ +#define FHP_H + +#include <stdint.h> // for uint8_t +#include <stddef.h> // for size_t +#include <stdbool.h> // for size_t + +typedef enum { + FHP_TOKEN_METHOD_START, + FHP_TOKEN_METHOD_FRAGMENT, + FHP_TOKEN_METHOD_END, + + FHP_TOKEN_URL_START, + FHP_TOKEN_URL_FRAGMENT, + FHP_TOKEN_URL_END, + + FHP_TOKEN_VERSION_START, + FHP_TOKEN_VERSION_FRAGMENT, + FHP_TOKEN_VERSION_END, + + FHP_TOKEN_HEADER_NAME_START, + FHP_TOKEN_HEADER_NAME_FRAGMENT, + FHP_TOKEN_HEADER_NAME_END, + + FHP_TOKEN_HEADER_VALUE_START, + FHP_TOKEN_HEADER_VALUE_FRAGMENT, + FHP_TOKEN_HEADER_VALUE_END, + + FHP_TOKEN_LAST +} fhp_token_t; + +typedef struct fhp_t_ fhp_t; + +typedef bool (*fhp_cb_t)( + fhp_t * const, + fhp_token_t, + uint8_t * const, + size_t +); + +typedef enum { + FHP_STATE_INIT, + FHP_STATE_ERROR, + FHP_STATE_METHOD, + FHP_STATE_METHOD_END, + FHP_STATE_URL, + FHP_STATE_URL_END, + FHP_STATE_VERSION, + FHP_STATE_VERSION_END, + FHP_STATE_VERSION_END_CR, + FHP_STATE_STATUS_END, + FHP_STATE_STATUS_END_CR, + FHP_STATE_HEADER_NAME, + FHP_STATE_HEADER_NAME_END, + FHP_STATE_HEADER_NAME_END_CR, + FHP_STATE_HEADER_VALUE, + FHP_STATE_HEADER_VALUE_END_CR, + FHP_STATE_HEADER_VALUE_END, + FHP_STATE_HEADERS_END, + FHP_STATE_HEADERS_END_CR, + FHP_STATE_LAST +} fhp_state_t; + +typedef enum { + FHP_OK, + FHP_ERR_CB, + FHP_ERR_BAD_STATE, + FHP_ERR_INVALID_CHAR, + FHP_ERR_INVALID_ERROR, + FHP_ERR_BUFFER_TOO_SMALL, + FHP_ERR_LAST +} fhp_err_t; + +fhp_err_t +fhp_strerror(fhp_err_t, char * const, size_t); + +#define FHP_BUF_SIZE 1024 + +struct fhp_t_ { + fhp_state_t state; + fhp_cb_t cb; + uint64_t ofs; + fhp_err_t err; + + void *user_data; + + uint8_t buf[FHP_BUF_SIZE]; + size_t buf_len; +}; + +fhp_err_t +fhp_init(fhp_t * const, fhp_cb_t, void * const); + +fhp_err_t +fhp_push(fhp_t * const, uint8_t * const, size_t); + +#endif /* FHP_H */ @@ -0,0 +1,59 @@ +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fhp/fhp.h> + +#define UNUSED(a) ((void) (a)) + +static const char * +str_test_basic = + "GET / HTTP/1.1\r\n" + "Host: pablotron.org\r\n" + "Connection: close\r\n" + "\r\n"; + +static bool +test_basic_cb( + fhp_t *fhp, + fhp_token_t token, + uint8_t * const buf, + size_t len +) { + UNUSED(fhp); + UNUSED(buf); + + fprintf(stderr, "test_basic_cb(): token = %u, len = %lu\n", token, len); + + // return success + return true; +} + +static void +test_basic(void) { + char buf[1024]; + fhp_err_t err; + fhp_t fhp; + + if ((err = fhp_init(&fhp, test_basic_cb, NULL)) != FHP_OK) { + fhp_strerror(err, buf, sizeof(buf)); + fprintf(stderr, "ERROR: fhp_init(): %s\n", buf); + exit(EXIT_FAILURE); + } + + size_t len = strlen(str_test_basic); + if ((err = fhp_push(&fhp, (uint8_t*) str_test_basic, len)) != FHP_OK) { + fhp_strerror(err, buf, sizeof(buf)); + fprintf(stderr, "ERROR: fhp_push(): %s\n", buf); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char *argv[]) { + UNUSED(argc); + UNUSED(argv); + + test_basic(); + + return EXIT_SUCCESS; +} |