#include "internal.h" // // context buffer functions // 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 = fhp_header_value_parser_push(ctx, ctx->buf, ctx->buf_len); if (err != 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; } // // method functions // static fhp_err_t fhp_ctx_handle_method(fhp_ctx_t * const ctx) { uint32_t hash = ctx->buf_hash; fhp_token_t method = FHP_TOKEN_METHOD_OTHER; // get method token if (hash == ctx->env->hashes[FHP_STR_GET]) { method = FHP_TOKEN_METHOD_GET; } else if (hash == ctx->env->hashes[FHP_STR_POST]) { method = FHP_TOKEN_METHOD_POST; } else if (hash == ctx->env->hashes[FHP_STR_HEAD]) { method = FHP_TOKEN_METHOD_HEAD; } else if (hash == ctx->env->hashes[FHP_STR_PUT]) { method = FHP_TOKEN_METHOD_PUT; } else if (hash == ctx->env->hashes[FHP_STR_DELETE]) { method = FHP_TOKEN_METHOD_DELETE; } else if (hash == ctx->env->hashes[FHP_STR_OPTIONS]) { method = FHP_TOKEN_METHOD_OPTIONS; } else { method = FHP_TOKEN_METHOD_OTHER; } // set method token ctx->http_method = method; // send method token if (!ctx->cb(ctx, method, 0, 0)) return FHP_ERR_CB; // return success return FHP_OK; } // // context functions // static const fhp_ctx_t FHP_CTX_DEFAULT = { .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, .content_type = FHP_CONTENT_TYPE_NONE, }; 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_CTX_DEFAULT; ctx->env = env ? env : fhp_get_default_env(); ctx->user_data = user_data; ctx->cb = 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; goto retry; break; } break; case FHP_STATE_STATUS_END_CR: switch (byte) { case '\n': // set state ctx->state = FHP_STATE_HEADERS_END; goto retry; 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; goto retry; break; default: return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADERS_END_CR: switch (byte) { case '\n': ctx->state = FHP_STATE_HEADERS_END; goto retry; break; default: return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADERS_END: // send end headers token if (!ctx->cb(ctx, FHP_TOKEN_HEADERS_END, 0, 0)) return FHP_ERR_CB; switch (ctx->body_type) { case FHP_BODY_TYPE_NONE: // no body // send request end token if (!ctx->cb(ctx, FHP_TOKEN_REQUEST_END, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_REQUEST_END; break; case FHP_BODY_TYPE_CONTENT_LENGTH: // send body start token if (!ctx->cb(ctx, FHP_TOKEN_BODY_START, 0, 0)) return FHP_ERR_CB; if (ctx->content_length > 0) { // grab content length and clear buffer ctx->bytes_left = ctx->content_length; ctx->buf_len = 0; // set state ctx->state = FHP_STATE_CL_BODY; } else { // empty body // send body end token if (!ctx->cb(ctx, FHP_TOKEN_BODY_END, 0, 0)) return FHP_ERR_CB; // send request end token if (!ctx->cb(ctx, FHP_TOKEN_REQUEST_END, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_REQUEST_END; } break; case FHP_BODY_TYPE_TRANSFER_ENCODING: // send body start token if (!ctx->cb(ctx, FHP_TOKEN_BODY_START, 0, 0)) return FHP_ERR_CB; // clear chunk size ctx->chunk_len = 0; // set state ctx->state = FHP_STATE_CHUNK_LEN; break; default: // invalid body type (bug?) return FHP_ERR_INVALID_BODY_TYPE; } break; case FHP_STATE_CL_BODY: // add to buffer if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_BODY_FRAGMENT, byte)) != FHP_OK) return err; // decriment remaining bytes ctx->bytes_left--; if (!ctx->bytes_left) { // flush buffer if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_BODY_FRAGMENT)) != FHP_OK) return err; // send body end token if (!ctx->cb(ctx, FHP_TOKEN_BODY_END, 0, 0)) return FHP_ERR_CB; // send request end token if (!ctx->cb(ctx, FHP_TOKEN_REQUEST_END, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_REQUEST_END; } break; case FHP_STATE_CHUNK_LEN: { uint64_t old_chunk_len = ctx->chunk_len; switch (byte) { case '\r': // set state ctx->state = FHP_STATE_CHUNK_LEN_CR; break; case '\n': // set state ctx->state = FHP_STATE_CHUNK_LEN_CR; goto retry; break; CASE_DIGIT_CHARS // update chunk length ctx->chunk_len = (ctx->chunk_len << 4) + (byte - '0'); break; CASE_HEX_LC_ALPHA_CHARS // update chunk length ctx->chunk_len = (ctx->chunk_len << 4) + (byte - 'a'); break; CASE_HEX_UC_ALPHA_CHARS // update chunk length ctx->chunk_len = (ctx->chunk_len << 4) + (byte - 'A'); break; default: // invalid character return FHP_ERR_INVALID_CHAR_IN_CHUNK_LEN; } // check for overflow if (ctx->chunk_len < old_chunk_len) { // overflow in chunk length return FHP_ERR_CHUNK_LEN_OVERFLOW; } } break; case FHP_STATE_CHUNK_LEN_CR: switch (byte) { case '\n': // TODO: check chunk len if (ctx->chunk_len > 0) { ctx->bytes_left = ctx->chunk_len; ctx->buf_len = 0; // send chunk start token if (!ctx->cb(ctx, FHP_TOKEN_CHUNK_START, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_CHUNK_BODY; } else { // last chunk // send chunk start token if (!ctx->cb(ctx, FHP_TOKEN_CHUNK_LAST, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_TE_FOOTER; } break; default: return FHP_ERR_INVALID_CHAR_AFTER_CHUNK_LEN; } break; case FHP_STATE_CHUNK_BODY: // add to buffer if ((err = fhp_ctx_buf_push(ctx, FHP_TOKEN_BODY_FRAGMENT, byte)) != FHP_OK) return err; // decriment remaining bytes ctx->bytes_left--; if (!ctx->bytes_left) { // flush buffer if ((err = fhp_ctx_buf_flush(ctx, FHP_TOKEN_BODY_FRAGMENT)) != FHP_OK) return err; // send body end token if (!ctx->cb(ctx, FHP_TOKEN_CHUNK_END, 0, 0)) return FHP_ERR_CB; // set state ctx->state = FHP_STATE_CHUNK_BODY_END; } break; case FHP_STATE_CHUNK_BODY_END: switch (byte) { case '\r': // set state ctx->state = FHP_STATE_CHUNK_BODY_END_CR; break; case '\n': // set state ctx->state = FHP_STATE_CHUNK_BODY_END_CR; goto retry; break; default: return FHP_ERR_INVALID_CHAR_AFTER_CHUNK_BODY; } break; case FHP_STATE_CHUNK_BODY_END_CR: switch (byte) { case '\n': // clear chunk len, set state ctx->chunk_len = 0; ctx->state = FHP_STATE_CHUNK_LEN; break; default: return FHP_ERR_INVALID_CHAR_AFTER_CHUNK_BODY; } break; case FHP_STATE_TE_FOOTER: // TODO break; case FHP_STATE_REQUEST_END: switch (byte) { case '\r': case '\n': // eat newlines break; default: // set state ctx->state = FHP_STATE_INIT; goto retry; } 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; } fhp_content_type_t fhp_ctx_get_content_type(fhp_ctx_t * const ctx) { return ctx->content_type; }