summaryrefslogtreecommitdiff
path: root/fhp.c
diff options
context:
space:
mode:
Diffstat (limited to 'fhp.c')
-rw-r--r--fhp.c591
1 files changed, 591 insertions, 0 deletions
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;
+}