#include "internal.h" // // context functions // static const fhp_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_init( fhp_t * const fhp, fhp_env_t * const env, fhp_cb_t cb, void * const user_data ) { *fhp = FHP_DEFAULT_CONTEXT; fhp->env = env ? env : fhp_get_default_env(); 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 fhp_err_t 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 FHP_ERR_CB; // update buffer hash if (fhp->is_hashing) fhp->buf_hash = fhp_lc_hash_push(fhp->buf_hash, fhp->buf, fhp->buf_len); // push to header value parser fhp_err_t err; if ((err = fhp_header_value_parser_push(fhp, fhp->buf, fhp->buf_len)) != FHP_OK) return err; // clear buffer fhp_buf_clear(fhp); } // return success return FHP_OK; } static fhp_err_t fhp_buf_push( fhp_t * const fhp, fhp_token_t token, uint8_t byte ) { // flush buffer if (fhp->buf_len + 1 >= FHP_MAX_BUF_SIZE) { fhp_err_t err; if ((err = fhp_buf_flush(fhp, token)) != FHP_OK) return err; } // append to buffer fhp->buf[fhp->buf_len] = byte; fhp->buf_len++; // return success return FHP_OK; } static fhp_err_t fhp_handle_method(fhp_t * const fhp) { // get method token fhp->http_method = FHP_TOKEN_METHOD_OTHER; if (fhp->buf_hash == fhp->env->hashes[FHP_STR_GET]) { fhp->http_method = FHP_TOKEN_METHOD_GET; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_POST]) { fhp->http_method = FHP_TOKEN_METHOD_POST; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_HEAD]) { fhp->http_method = FHP_TOKEN_METHOD_HEAD; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_PUT]) { fhp->http_method = FHP_TOKEN_METHOD_PUT; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_DELETE]) { fhp->http_method = FHP_TOKEN_METHOD_DELETE; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_OPTIONS]) { fhp->http_method = FHP_TOKEN_METHOD_OPTIONS; } // send method token if (!fhp->cb(fhp, fhp->http_method, 0, 0)) return FHP_ERR_CB; // return success return FHP_OK; } static fhp_err_t fhp_push_byte( fhp_t * const fhp, uint8_t byte ) { fhp_err_t err; 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; // enable buffer hashing fhp->is_hashing = true; fhp->buf_hash = fhp_hash_init(); // set state fhp->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_buf_push(fhp, FHP_TOKEN_METHOD_FRAGMENT, byte)) != FHP_OK) return err; break; case ' ': // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_METHOD_FRAGMENT)) != FHP_OK) return err; // disable buffer hashing fhp->is_hashing = false; // send end token if (!fhp->cb(fhp, FHP_TOKEN_METHOD_END, 0, 0)) return FHP_ERR_CB; // handle method fhp_err_t err = fhp_handle_method(fhp); if (err != FHP_OK) return err; // set state fhp->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 (!fhp->cb(fhp, FHP_TOKEN_URL_START, 0, 0)) return FHP_ERR_CB; fhp->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_buf_flush(fhp, FHP_TOKEN_URL_FRAGMENT)) != FHP_OK) return err; // 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; case '%': // set state fhp->state = FHP_STATE_URL_PERCENT; break; CASE_URL_CHARS // add to buffer if ((err = fhp_buf_push(fhp, FHP_TOKEN_URL_FRAGMENT, byte)) != FHP_OK) return err; } break; case FHP_STATE_URL_PERCENT: switch (byte) { CASE_DIGIT_CHARS fhp->hex = byte - '0'; fhp->state = FHP_STATE_URL_PERCENT_LAST; break; CASE_HEX_ALPHA_CHARS fhp->hex = 10 + byte - ((byte >= 'a') ? 'a' : 'A'); fhp->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 fhp->hex = (fhp->hex << 4) + (byte - '0'); break; CASE_HEX_ALPHA_CHARS fhp->hex = (fhp->hex << 4) + (10 + byte - ((byte >= 'a') ? 'a' : 'A')); break; default: return FHP_ERR_INVALID_CHAR_IN_URL_PERCENT; } // add to buffer if ((err = fhp_buf_push(fhp, FHP_TOKEN_URL_FRAGMENT, fhp->hex)) != FHP_OK) return err; // set state fhp->state = FHP_STATE_URL; 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; // enable buffer hashing fhp->is_hashing = true; fhp->buf_hash = fhp_hash_init(); // set state fhp->state = FHP_STATE_VERSION; goto retry; } break; case FHP_STATE_VERSION: switch (byte) { case '\r': case '\n': // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_VERSION_FRAGMENT)) != FHP_OK) return err; // send end token if (!fhp->cb(fhp, FHP_TOKEN_VERSION_END, 0, 0)) return FHP_ERR_CB; // disable buffer hashing fhp->is_hashing = false; // get version token fhp->http_version = FHP_TOKEN_VERSION_OTHER; if (fhp->buf_hash == fhp->env->hashes[FHP_STR_HTTP_10]) { fhp->http_version = FHP_TOKEN_VERSION_HTTP_10; } else if (fhp->buf_hash == fhp->env->hashes[FHP_STR_HTTP_11]) { fhp->http_version = FHP_TOKEN_VERSION_HTTP_11; } // send version token if (!fhp->cb(fhp, fhp->http_version, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_VERSION_END; goto retry; break; CASE_VERSION_CHARS // add to buffer if ((err = fhp_buf_push(fhp, 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': fhp->state = FHP_STATE_VERSION_END_CR; break; case '\n': fhp->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': fhp->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 (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) return FHP_ERR_CB; // enable buffer hashing fhp->is_hashing = true; fhp->buf_hash = fhp_hash_init(); // 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: return FHP_ERR_INVALID_CHAR_AFTER_CR; } break; case FHP_STATE_HEADER_NAME: switch (byte) { CASE_TOKEN_CHARS // add to buffer if ((err = fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) != FHP_OK) return err; break; case ':': // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT)) != FHP_OK) return err; // disable buffer hashing and cache header name hash fhp->is_hashing = false; fhp->header_name_hash = fhp->buf_hash; // 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: 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 (!fhp->cb(fhp, 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(fhp)) != FHP_OK) return err; // 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 ((err = fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, byte)) != FHP_OK) return err; 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 space to buffer // folding to ' ', as per RFC7230 3.2.4 // https://tools.ietf.org/html/rfc7230#section-3.2.4 if ((err = fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, ' ')) != FHP_OK) return err; // set state fhp->state = FHP_STATE_HEADER_VALUE; break; CASE_TOKEN_CHARS // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK) return err; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // end header value parser if ((err = fhp_header_value_parser_done(fhp)) != FHP_OK) return err; // send start token if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) return FHP_ERR_CB; // enable buffer hashing fhp->is_hashing = true; fhp->buf_hash = fhp_hash_init(); // add to buffer if ((err = fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) != FHP_OK) return err; // set state fhp->state = FHP_STATE_HEADER_NAME; break; case '\r': // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK) return err; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // end header value parser if ((err = fhp_header_value_parser_done(fhp)) != FHP_OK) return err; // set state fhp->state = FHP_STATE_HEADERS_END_CR; break; case '\n': // flush buffer if ((err = fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) != FHP_OK) return err; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // end header value parser if ((err = fhp_header_value_parser_done(fhp)) != FHP_OK) return err; // 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: // invalid state // (should never be reached) 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; } fhp_env_t * fhp_get_env(fhp_t * const fhp) { return fhp->env; } void * fhp_get_user_data(fhp_t * const fhp) { return fhp->user_data; } size_t fhp_get_num_tes(fhp_t * const fhp) { return fhp->num_tes; } uint64_t fhp_get_content_length(fhp_t * const fhp) { return fhp->content_length; }