summaryrefslogtreecommitdiff
path: root/ctx.c
diff options
context:
space:
mode:
Diffstat (limited to 'ctx.c')
-rw-r--r--ctx.c638
1 files changed, 638 insertions, 0 deletions
diff --git a/ctx.c b/ctx.c
new file mode 100644
index 0000000..e73fba8
--- /dev/null
+++ b/ctx.c
@@ -0,0 +1,638 @@
+#include "internal.h"
+
+//
+// context functions
+//
+
+static const fhp_ctx_t FHP_DEFAULT_CONTEXT = {
+ .state = FHP_STATE_INIT,
+ .user_data = NULL,
+ .cb = NULL,
+ .err = FHP_OK,
+ .ofs = 0,
+ .buf_len = 0,
+ .is_hashing = false,
+ .header_name_hash = 0,
+ .header_value_parser = FHP_HEADER_VALUE_PARSER_NONE,
+ .body_type = FHP_BODY_TYPE_NONE,
+ .content_length = 0,
+ .num_tes = 0,
+};
+
+fhp_err_t
+fhp_ctx_init(
+ fhp_ctx_t * const ctx,
+ fhp_env_t * const env,
+ fhp_cb_t cb,
+ void * const user_data
+) {
+ *ctx = FHP_DEFAULT_CONTEXT;
+ ctx->env = env ? env : fhp_get_default_env();
+ ctx->user_data = user_data;
+ ctx->cb = cb;
+
+ /* return success */
+ return FHP_OK;
+}
+
+static void
+fhp_ctx_buf_clear(fhp_ctx_t * const ctx) {
+ // clear buffer
+ ctx->buf_len = 0;
+}
+
+static fhp_err_t
+fhp_ctx_buf_flush(
+ fhp_ctx_t * const ctx,
+ fhp_token_t token
+) {
+ if (ctx->buf_len > 0) {
+ // push data
+ if (!ctx->cb(ctx, token, ctx->buf, ctx->buf_len))
+ return FHP_ERR_CB;
+
+ // update buffer hash
+ if (ctx->is_hashing)
+ ctx->buf_hash = fhp_lc_hash_push(ctx->buf_hash, ctx->buf, ctx->buf_len);
+
+ // push to header value parser
+ fhp_err_t err;
+ if ((err = fhp_header_value_parser_push(ctx, ctx->buf, ctx->buf_len)) != FHP_OK)
+ return err;
+
+ // clear buffer
+ fhp_ctx_buf_clear(ctx);
+ }
+
+ // return success
+ return FHP_OK;
+}
+
+static fhp_err_t
+fhp_ctx_buf_push(
+ fhp_ctx_t * const ctx,
+ fhp_token_t token,
+ uint8_t byte
+) {
+ // flush buffer
+ if (ctx->buf_len + 1 >= FHP_CTX_MAX_BUF_SIZE) {
+ fhp_err_t err;
+ if ((err = fhp_ctx_buf_flush(ctx, token)) != FHP_OK)
+ return err;
+ }
+
+ // append to buffer
+ ctx->buf[ctx->buf_len] = byte;
+ ctx->buf_len++;
+
+ // return success
+ return FHP_OK;
+}
+
+static fhp_err_t
+fhp_ctx_handle_method(fhp_ctx_t * const ctx) {
+ // get method token
+ ctx->http_method = FHP_TOKEN_METHOD_OTHER;
+ if (ctx->buf_hash == ctx->env->hashes[FHP_STR_GET]) {
+ ctx->http_method = FHP_TOKEN_METHOD_GET;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_POST]) {
+ ctx->http_method = FHP_TOKEN_METHOD_POST;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_HEAD]) {
+ ctx->http_method = FHP_TOKEN_METHOD_HEAD;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_PUT]) {
+ ctx->http_method = FHP_TOKEN_METHOD_PUT;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_DELETE]) {
+ ctx->http_method = FHP_TOKEN_METHOD_DELETE;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_OPTIONS]) {
+ ctx->http_method = FHP_TOKEN_METHOD_OPTIONS;
+ }
+
+ // send method token
+ if (!ctx->cb(ctx, ctx->http_method, 0, 0))
+ return FHP_ERR_CB;
+
+ // return success
+ return FHP_OK;
+}
+
+static fhp_err_t
+fhp_ctx_push_byte(
+ fhp_ctx_t * const ctx,
+ uint8_t byte
+) {
+ fhp_err_t err;
+
+retry:
+ switch (ctx->state) {
+ case FHP_STATE_INIT:
+ switch (byte) {
+ CASE_TOKEN_CHARS
+ // send start token
+ if (!ctx->cb(ctx, FHP_TOKEN_METHOD_START, 0, 0))
+ return FHP_ERR_CB;
+
+ // enable buffer hashing
+ ctx->is_hashing = true;
+ ctx->buf_hash = fhp_hash_init();
+
+ // set state
+ ctx->state = FHP_STATE_METHOD;
+ goto retry;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_METHOD;
+ }
+
+ break;
+ case FHP_STATE_METHOD:
+ switch (byte) {
+ CASE_TOKEN_CHARS
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_METHOD_FRAGMENT, byte)) != FHP_OK)
+ return err;
+
+ break;
+ case ' ':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_METHOD_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // disable buffer hashing
+ ctx->is_hashing = false;
+
+ // send end token
+ if (!ctx->cb(ctx, FHP_TOKEN_METHOD_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // handle method
+ fhp_err_t err = fhp_ctx_handle_method(ctx);
+ if (err != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_METHOD_END;
+ goto retry;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_METHOD;
+ }
+
+ break;
+ case FHP_STATE_METHOD_END:
+ switch (byte) {
+ case ' ':
+ // FIXME: do we want to allow more than one whitespace?
+ // ignore
+ break;
+ CASE_URL_CHARS
+ if (!ctx->cb(ctx, FHP_TOKEN_URL_START, 0, 0))
+ return FHP_ERR_CB;
+
+ ctx->state = FHP_STATE_URL;
+ goto retry;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_URL;
+ }
+
+ break;
+ case FHP_STATE_URL:
+ switch (byte) {
+ case ' ':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_URL_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // send end token
+ if (!ctx->cb(ctx, FHP_TOKEN_URL_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // set state
+ ctx->state = FHP_STATE_URL_END;
+ goto retry;
+
+ break;
+ case '%':
+ // set state
+ ctx->state = FHP_STATE_URL_PERCENT;
+
+ break;
+ CASE_URL_CHARS
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_URL_FRAGMENT, byte)) != FHP_OK)
+ return err;
+ }
+
+ break;
+ case FHP_STATE_URL_PERCENT:
+ switch (byte) {
+ CASE_DIGIT_CHARS
+ ctx->hex = byte - '0';
+ ctx->state = FHP_STATE_URL_PERCENT_LAST;
+
+ break;
+ CASE_HEX_ALPHA_CHARS
+ ctx->hex = 10 + byte - ((byte >= 'a') ? 'a' : 'A');
+ ctx->state = FHP_STATE_URL_PERCENT_LAST;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_URL_PERCENT;
+ }
+
+ break;
+ case FHP_STATE_URL_PERCENT_LAST:
+ switch (byte) {
+ CASE_DIGIT_CHARS
+ ctx->hex = (ctx->hex << 4) + (byte - '0');
+ break;
+ CASE_HEX_ALPHA_CHARS
+ ctx->hex = (ctx->hex << 4) + (10 + byte - ((byte >= 'a') ? 'a' : 'A'));
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_URL_PERCENT;
+ }
+
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_URL_FRAGMENT, ctx->hex)) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_URL;
+
+ break;
+ case FHP_STATE_URL_END:
+ switch (byte) {
+ case ' ':
+ // ignore
+ break;
+ default:
+ if (!ctx->cb(ctx, FHP_TOKEN_VERSION_START, 0, 0))
+ return FHP_ERR_CB;
+
+ // enable buffer hashing
+ ctx->is_hashing = true;
+ ctx->buf_hash = fhp_hash_init();
+
+ // set state
+ ctx->state = FHP_STATE_VERSION;
+ goto retry;
+ }
+
+ break;
+ case FHP_STATE_VERSION:
+ switch (byte) {
+ case '\r':
+ case '\n':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_VERSION_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // send end token
+ if (!ctx->cb(ctx, FHP_TOKEN_VERSION_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // disable buffer hashing
+ ctx->is_hashing = false;
+
+ // get version token
+ ctx->http_version = FHP_TOKEN_VERSION_OTHER;
+ if (ctx->buf_hash == ctx->env->hashes[FHP_STR_HTTP_10]) {
+ ctx->http_version = FHP_TOKEN_VERSION_HTTP_10;
+ } else if (ctx->buf_hash == ctx->env->hashes[FHP_STR_HTTP_11]) {
+ ctx->http_version = FHP_TOKEN_VERSION_HTTP_11;
+ }
+
+ // send version token
+ if (!ctx->cb(ctx, ctx->http_version, 0, 0))
+ return FHP_ERR_CB;
+
+ // set state
+ ctx->state = FHP_STATE_VERSION_END;
+ goto retry;
+
+ break;
+ CASE_VERSION_CHARS
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_VERSION_FRAGMENT, byte)) != FHP_OK)
+ return err;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_VERSION;
+ }
+
+ break;
+ case FHP_STATE_VERSION_END:
+ switch (byte) {
+ case '\r':
+ ctx->state = FHP_STATE_VERSION_END_CR;
+
+ break;
+ case '\n':
+ ctx->state = FHP_STATE_STATUS_END;
+
+ break;
+ default:
+ // invalid character
+ // (should never be reached)
+ return FHP_ERR_INVALID_CHAR;
+ };
+
+ break;
+ case FHP_STATE_VERSION_END_CR:
+ switch (byte) {
+ case '\n':
+ ctx->state = FHP_STATE_STATUS_END;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_AFTER_CR;
+ };
+
+ break;
+ case FHP_STATE_STATUS_END:
+ switch (byte) {
+ CASE_TOKEN_CHARS
+ // send start token
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_NAME_START, 0, 0))
+ return FHP_ERR_CB;
+
+ // enable buffer hashing
+ ctx->is_hashing = true;
+ ctx->buf_hash = fhp_hash_init();
+
+ // set state
+ ctx->state = FHP_STATE_HEADER_NAME;
+ goto retry;
+
+ break;
+ case '\r':
+ // set state
+ ctx->state = FHP_STATE_STATUS_END_CR;
+
+ break;
+ case '\n':
+ // set state
+ ctx->state = FHP_STATE_HEADERS_END;
+
+ break;
+ }
+
+ break;
+ case FHP_STATE_STATUS_END_CR:
+ switch (byte) {
+ case '\n':
+ // set state
+ ctx->state = FHP_STATE_HEADERS_END;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_AFTER_CR;
+ }
+
+ break;
+ case FHP_STATE_HEADER_NAME:
+ switch (byte) {
+ CASE_TOKEN_CHARS
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) != FHP_OK)
+ return err;
+
+ break;
+ case ':':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_HEADER_NAME_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // disable buffer hashing and cache header name hash
+ ctx->is_hashing = false;
+ ctx->header_name_hash = ctx->buf_hash;
+
+ // send end token
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_NAME_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // set state
+ ctx->state = FHP_STATE_HEADER_NAME_END;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR_IN_HEADER_NAME;
+ }
+
+ break;
+ case FHP_STATE_HEADER_NAME_END:
+ switch (byte) {
+ CASE_OWS_CHARS
+ // ignore leading spaces
+
+ break;
+ default:
+ // send start token
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_VALUE_START, 0, 0))
+ return FHP_ERR_CB;
+
+ // init header value parser
+ fhp_err_t err;
+ if ((err = fhp_header_value_parser_init(ctx)) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_HEADER_VALUE;
+ goto retry;
+
+ break;
+ }
+
+ break;
+ case FHP_STATE_HEADER_VALUE:
+ switch (byte) {
+ case '\r':
+ ctx->state = FHP_STATE_HEADER_VALUE_END_CR;
+
+ break;
+ case '\n':
+ ctx->state = FHP_STATE_HEADER_VALUE_END;
+ break;
+ default:
+ // FIXME: need more limits on valid octets
+
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_HEADER_VALUE_FRAGMENT, byte)) != FHP_OK)
+ return err;
+
+ break;
+ }
+
+ break;
+ case FHP_STATE_HEADER_VALUE_END_CR:
+ switch (byte) {
+ case '\n':
+ ctx->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 space to buffer
+ // folding to ' ', as per RFC7230 3.2.4
+ // https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_HEADER_VALUE_FRAGMENT, ' ')) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_HEADER_VALUE;
+
+ break;
+ CASE_TOKEN_CHARS
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // end header value
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_VALUE_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // end header value parser
+ if ((err = fhp_header_value_parser_done(ctx)) != FHP_OK)
+ return err;
+
+ // send start token
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_NAME_START, 0, 0))
+ return FHP_ERR_CB;
+
+ // enable buffer hashing
+ ctx->is_hashing = true;
+ ctx->buf_hash = fhp_hash_init();
+
+ // add to buffer
+ if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_HEADER_NAME;
+
+ break;
+ case '\r':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // end header value
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_VALUE_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // end header value parser
+ if ((err = fhp_header_value_parser_done(ctx)) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_HEADERS_END_CR;
+
+ break;
+ case '\n':
+ // flush buffer
+ if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK)
+ return err;
+
+ // end header value
+ if (!ctx->cb(ctx, FHP_TOKEN_HEADER_VALUE_END, 0, 0))
+ return FHP_ERR_CB;
+
+ // end header value parser
+ if ((err = fhp_header_value_parser_done(ctx)) != FHP_OK)
+ return err;
+
+ // set state
+ ctx->state = FHP_STATE_HEADERS_END;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR;
+ }
+
+ break;
+ case FHP_STATE_HEADERS_END_CR:
+ switch (byte) {
+ case '\n':
+ ctx->state = FHP_STATE_HEADERS_END;
+
+ break;
+ default:
+ return FHP_ERR_INVALID_CHAR;
+ }
+
+ break;
+ case FHP_STATE_HEADERS_END:
+ // TODO
+
+ break;
+ default:
+ // invalid state
+ // (should never be reached)
+ return FHP_ERR_BAD_STATE;
+ }
+
+ // increment byte offset
+ ctx->ofs++;
+
+ /* return success */
+ return FHP_OK;
+}
+
+fhp_err_t
+fhp_ctx_push(
+ fhp_ctx_t * const ctx,
+ uint8_t * const buf,
+ size_t len
+) {
+ switch (ctx->state) {
+ case FHP_STATE_ERROR:
+ return ctx->err;
+
+ break;
+ default:
+ for (size_t i = 0; i < len; i++) {
+ // push byte
+ fhp_err_t err = fhp_ctx_push_byte(ctx, buf[i]);
+
+ // check result
+ if (err != FHP_OK) {
+ ctx->state = FHP_STATE_ERROR;
+ ctx->err = err;
+ return err;
+ }
+ }
+ }
+
+ // return success
+ return FHP_OK;
+}
+
+fhp_env_t *
+fhp_ctx_get_env(fhp_ctx_t * const ctx) {
+ return ctx->env;
+}
+
+void *
+fhp_ctx_get_user_data(fhp_ctx_t * const ctx) {
+ return ctx->user_data;
+}
+
+size_t
+fhp_ctx_get_num_tes(fhp_ctx_t * const ctx) {
+ return ctx->num_tes;
+}
+
+uint64_t
+fhp_ctx_get_content_length(fhp_ctx_t * const ctx) {
+ return ctx->content_length;
+}