summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile13
-rw-r--r--fhp.c591
-rw-r--r--include/fhp/fhp.h97
-rw-r--r--test.c59
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)
diff --git a/fhp.c b/fhp.c
new file mode 100644
index 0000000..da48f0a
--- /dev/null
+++ b/fhp.c
@@ -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 */
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..3d16363
--- /dev/null
+++ b/test.c
@@ -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;
+}