From 9cfa120ae79cfd429f1b77eb8ccb6036651e7233 Mon Sep 17 00:00:00 2001 From: luwenpeng Date: Wed, 17 Aug 2022 18:08:33 +0800 Subject: [PATCH] feature: http decoder * support parser uncompress request/response * define public API interface --- sdk/include/http.h | 176 ++++- src/main.cpp | 2 +- src/plugin_manager/plugin_manager.cpp | 8 +- src/protocol_decoder/http/CMakeLists.txt | 15 +- src/protocol_decoder/http/http.cpp | 21 +- src/protocol_decoder/http/http_decoder.cpp | 506 +++++++++++++++ src/protocol_decoder/http/http_decoder.h | 81 +++ .../http/http_decoder_rstring.cpp | 179 +++++ .../http/http_decoder_rstring.h | 90 +++ .../http/http_decoder_table.cpp | 344 ++++++++++ .../http/http_decoder_table.h | 45 ++ .../http/http_decoder_util.cpp | 16 + src/protocol_decoder/http/http_decoder_util.h | 61 ++ src/protocol_decoder/http/test/CMakeLists.txt | 38 ++ .../http/test/http_decoder_rstring_test.cpp | 328 ++++++++++ .../http/test/http_decoder_table_test.cpp | 431 ++++++++++++ .../http/test/http_decoder_test.cpp | 614 ++++++++++++++++++ vendor/CMakeLists.txt | 16 +- vendor/llhttp-release-v6.0.9.tar.gz | Bin 0 -> 41388 bytes 19 files changed, 2958 insertions(+), 13 deletions(-) create mode 100644 src/protocol_decoder/http/http_decoder.cpp create mode 100644 src/protocol_decoder/http/http_decoder.h create mode 100644 src/protocol_decoder/http/http_decoder_rstring.cpp create mode 100644 src/protocol_decoder/http/http_decoder_rstring.h create mode 100644 src/protocol_decoder/http/http_decoder_table.cpp create mode 100644 src/protocol_decoder/http/http_decoder_table.h create mode 100644 src/protocol_decoder/http/http_decoder_util.cpp create mode 100644 src/protocol_decoder/http/http_decoder_util.h create mode 100644 src/protocol_decoder/http/test/CMakeLists.txt create mode 100644 src/protocol_decoder/http/test/http_decoder_rstring_test.cpp create mode 100644 src/protocol_decoder/http/test/http_decoder_table_test.cpp create mode 100644 src/protocol_decoder/http/test/http_decoder_test.cpp create mode 100644 vendor/llhttp-release-v6.0.9.tar.gz diff --git a/sdk/include/http.h b/sdk/include/http.h index e364ca0..b98a244 100644 --- a/sdk/include/http.h +++ b/sdk/include/http.h @@ -1,14 +1,184 @@ -#pragma once +#ifndef _HTTP_H +#define _HTTP_H #ifdef __cpluscplus extern "C" { #endif +#include + #include "session.h" -void http_decoder(const struct stellar_session *session, enum session_event_type event, const char *payload, size_t len, void **ctx); +/****************************************************************************** + * Public API: For build in + ******************************************************************************/ + +void http_entry(const struct stellar_session *session, enum session_event_type event, const char *payload, size_t len, void **ctx); + +/****************************************************************************** + * Public API: For http consumer + ******************************************************************************/ + +enum http_event +{ + HTTP_EVENT_NONE = 0x0, + + HTTP_EVENT_REQ_LINE = 0x01, + HTTP_EVENT_REQ_HDR = 0x02, + HTTP_EVENT_REQ_BODY = 0x04, + + HTTP_EVENT_RESP_LINE = 0x08, + HTTP_EVENT_RESP_HDR = 0x10, + HTTP_EVENT_RESP_BODY = 0x20, +}; + +struct http_request_line +{ + char *method; + size_t method_len; + + char *uri; + size_t uri_len; + + int major_version; + int minor_version; +}; + +struct http_status_line +{ + char *status; + size_t status_len; + + int status_code; + + int major_version; + int minor_version; +}; + +struct http_decoder *http_session_get_decoder(const struct stellar_session *http_session); +enum http_event http_decoder_get_event(struct http_decoder *decoder); + +/* + * The data fetched by the http_decoder_fetch_xxx() API + * is only available during the lifetime of the current packet. + */ +void http_decoder_fetch_request_line(struct http_decoder *decoder, struct http_request_line *request_line); +void http_decoder_fetch_status_line(struct http_decoder *decoder, struct http_status_line *status_line); + +void http_decoder_fetch_body(struct http_decoder *decoder, char **ptr, size_t *len); +void http_decoder_fetch_next_header(struct http_decoder *decoder, int *iter_index, char **filed_ptr, size_t *filed_len, char **value_ptr, size_t *value_len); + +/****************************************************************************** + * Example: How to implement http consumer + ******************************************************************************/ +/* +void http_consumer_entry_example(const struct stellar_session *http_session, enum session_event_type event, const char *payload, size_t len, void **ctx) +{ + if (event & SESSION_EVENT_OPENING) + { + // malloc ctx + } + + if (event & SESSION_EVENT_META) + { + struct http_decoder *decoder = http_decoder_get(http_session); + enum http_event ready_event = http_decoder_ready_event(decoder); + + if (ready_event & HTTP_EVENT_REQ_LINE) + { + struct http_request_line request_line; + http_decoder_fetch_request_line(decoder, &request_line); + } + + if (ready_event & HTTP_EVENT_RESP_LINE) + { + struct http_status_line status_line; + http_decoder_fetch_status_line(decoder, &status_line); + } + + if (ready_event & HTTP_EVENT_REQ_HDR) + { + int iter_index = 0; + char *filed_ptr = NULL; + char *value_ptr = NULL; + + size_t filed_len = 0; + size_t value_len = 0; + + do + { + http_decoder_fetch_next_header(decoder, iter_index, &filed_ptr, &filed_len, &value_ptr, &value_len); + if (filed_ptr) + { + if (strcmp("Host", filed_ptr, filed_len) == 0) + { + // get Host value : value_ptr + } + + // .... + + if (strcmp("User-Agent", filed_ptr, filed_len) == 0) + { + // get User-Agen value : value_ptr + } + } + } while (filed_ptr); + } + + if (ready_event & HTTP_EVENT_RESP_HDR) + { + int iter_index = 0; + char *filed_ptr = NULL; + char *value_ptr = NULL; + + size_t filed_len = 0; + size_t value_len = 0; + + do + { + http_decoder_fetch_next_header(decoder, iter_index, &filed_ptr, &filed_len, &value_ptr, &value_len); + if (filed_ptr) + { + if (strcmp("Server", filed_ptr, filed_len) == 0) + { + // get Server value : value_ptr + } + + // .... + + if (strcmp("content-length", filed_ptr, filed_len) == 0) + { + // get content-length value : value_ptr + } + } + } while (filed_ptr); + } + + if (ready_event & HTTP_EVENT_REQ_BODY) + { + char *req_body_ptr = NULL; + size_t req_body_len = 0; + http_decoder_fetch_body(decoder, req_body_ptr, req_body_len); + } + + if (ready_event & HTTP_EVENT_RESP_BODY) + { + char *resp_body_ptr = NULL; + size_t resp_body_len = 0; + http_decoder_fetch_body(decoder, resp_body_ptr, resp_body_len); + } + } + + if (event & SESSION_EVENT_CLOSING) + { + // free ctx + } +} +*/ #ifdef __cpluscplus } -#endif \ No newline at end of file +#endif + +#endif diff --git a/src/main.cpp b/src/main.cpp index 19c54d9..8d94824 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,7 +84,7 @@ int main(int argc, char ** argv) struct plugin_manager *plug_mgr = plugin_manager_create(); // register build-in plugin - plugin_manager_register(plug_mgr, "HTTP", SESSION_EVENT_ALL, http_decoder); + plugin_manager_register(plug_mgr, "HTTP", SESSION_EVENT_ALL, http_entry); // load external plugins char file_path[] = "./plugs/plugins.inf"; diff --git a/src/plugin_manager/plugin_manager.cpp b/src/plugin_manager/plugin_manager.cpp index a35ee82..b12cf28 100644 --- a/src/plugin_manager/plugin_manager.cpp +++ b/src/plugin_manager/plugin_manager.cpp @@ -396,14 +396,14 @@ int plugin_manager_register(struct plugin_manager *plug_mgr, const char *session void plugin_manager_dispatch(struct plugin_manager *plug_mgr, struct stellar_event *event) { - const struct stellar_session *seesion = stellar_event_get_session(event); + const struct stellar_session *session = stellar_event_get_session(event); struct session_plugin_ctx *plug_ctx = stellar_event_get_plugin_ctx(event); enum session_event_type event_type = stellar_event_get_type(event); const char *session_name = stellar_event_get_session_name(event); uint16_t payload_len = stellar_event_get_payload_length(event); const char *payload = stellar_event_get_payload(event); - assert(seesion); + assert(session); assert(session_name); char event_str_buffer[1024] = {0}; @@ -441,7 +441,7 @@ void plugin_manager_dispatch(struct plugin_manager *plug_mgr, struct stellar_eve { plug_ctx->callback_index = i; plugin_manager_log(DEBUG, "dispatch, run event_cb: %p, plugin status: 'taken over', session: %s, event: (%d, %s)", runtime->event_cb, session_name, event_type, event_str_buffer); - runtime->event_cb(seesion, SESSION_EVENT_CLOSING, payload, payload_len, &runtime->cb_args); + runtime->event_cb(session, SESSION_EVENT_CLOSING, payload, payload_len, &runtime->cb_args); continue; } else @@ -455,7 +455,7 @@ void plugin_manager_dispatch(struct plugin_manager *plug_mgr, struct stellar_eve { plug_ctx->callback_index = i; plugin_manager_log(DEBUG, "dispatch, run event_cb: %p, plugin status: 'normal', session: %s, event: (%d, %s)", runtime->event_cb, session_name, event_type, event_str_buffer); - runtime->event_cb(seesion, event_type, payload, payload_len, &runtime->cb_args); + runtime->event_cb(session, event_type, payload, payload_len, &runtime->cb_args); runtime->is_be_called = 1; } else diff --git a/src/protocol_decoder/http/CMakeLists.txt b/src/protocol_decoder/http/CMakeLists.txt index dd8482f..b8c5d66 100644 --- a/src/protocol_decoder/http/CMakeLists.txt +++ b/src/protocol_decoder/http/CMakeLists.txt @@ -1,6 +1,15 @@ - - add_library(http http.cpp + http_decoder_util.cpp + http_decoder_rstring.cpp + http_decoder_table.cpp + http_decoder.cpp ) -target_include_directories(http PUBLIC ${CMAKE_SOURCE_DIR}) \ No newline at end of file + +target_link_libraries(http + llhttp-static +) + +target_include_directories(http PUBLIC ${CMAKE_SOURCE_DIR}) + +add_subdirectory(test) \ No newline at end of file diff --git a/src/protocol_decoder/http/http.cpp b/src/protocol_decoder/http/http.cpp index 14e431c..dc74ab0 100644 --- a/src/protocol_decoder/http/http.cpp +++ b/src/protocol_decoder/http/http.cpp @@ -1,12 +1,31 @@ #include #include "sdk/include/session.h" +#include "sdk/include/http.h" -void http_decoder(const struct stellar_session *session, enum session_event_type event, const char *payload, size_t len, void **ctx) +void http_entry(const struct stellar_session *session, enum session_event_type event, const char *payload, size_t len, void **ctx) { struct stellar_session_event_extras *info = NULL; struct stellar_session *new_session = session_manager_session_derive(session, "HTTP"); session_manager_trigger_event(new_session, SESSION_EVENT_OPENING, info); session_manager_trigger_event(new_session, SESSION_EVENT_META, info); +} + +struct http_decoder *http_session_get_decoder(const struct stellar_session *http_session) +{ + return NULL; +} + +enum http_event http_decoder_get_event(struct http_decoder *decoder) +{ + return HTTP_EVENT_NONE; +} + +void http_decoder_fetch_request_line(struct http_decoder *decoder, struct http_request_line *request_line) +{ +} + +void http_decoder_fetch_status_line(struct http_decoder *decoder, struct http_status_line *status_line) +{ } \ No newline at end of file diff --git a/src/protocol_decoder/http/http_decoder.cpp b/src/protocol_decoder/http/http_decoder.cpp new file mode 100644 index 0000000..8f7707f --- /dev/null +++ b/src/protocol_decoder/http/http_decoder.cpp @@ -0,0 +1,506 @@ +#include +#include +#include + +#include "llhttp.h" +#include "http_decoder_util.h" +#include "http_decoder_table.h" +#include "http_decoder.h" + +struct http_decoder +{ + llhttp_t parser; + llhttp_settings_t settings; + enum http_decoder_status status; + struct http_decoder_table *table; + int commit_count; +}; + +/****************************************************************************** + * Private API + ******************************************************************************/ + +static void printf_debug_info(const char *desc, const char *at, size_t length) +{ + if (at) + { + char *temp = http_decoder_safe_dup(at, length); + printf("%s: %s\n", desc, temp); + http_decoder_safe_free(temp); + } + else + { + printf("%s\n", desc); + } +} + +/* Possible return values 0, -1, `HPE_PAUSED` */ +static int on_message_begin(llhttp_t *http) +{ + printf_debug_info("on_message_begin", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + if (decoder->table == NULL) + { + decoder->table = http_decoder_table_create(); + } + else + { + http_decoder_table_reset(decoder->table); + } + decoder->commit_count = 0; + decoder->status = ON_MESSAGE_BEGIN; + + return 0; +} + +/* Possible return values 0, -1, `HPE_PAUSED` */ +static int on_message_complete(llhttp_t *http) +{ + printf_debug_info("on_message_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + enum rstring_status status = http_decoder_table_status(decoder->table, HTTP_ITERM_BODY); + if (status == RSTRING_STATUS_REFER || status == RSTRING_STATUS_CACHE) + { + http_decoder_table_commit(decoder->table, HTTP_ITERM_BODY); + decoder->commit_count++; + } + decoder->status = ON_MESSAGE_COMPLETE; + + return 0; +} + +/* Possible return values 0, -1, HPE_USER */ +static int on_uri(llhttp_t *http, const char *at, size_t length) +{ + printf_debug_info("on_uri", at, length); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_add(decoder->table, HTTP_ITERM_URI, at, length); + decoder->status = ON_URI; + + return 0; +} + +/* Information-only callbacks, return value is ignored */ +static int on_uri_complete(llhttp_t *http) +{ + printf_debug_info("on_uri_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_commit(decoder->table, HTTP_ITERM_URI); + decoder->commit_count++; + decoder->status = ON_URI_COMPLETE; + + return 0; +} + +/* Possible return values 0, -1, HPE_USER */ +static int on_status(llhttp_t *http, const char *at, size_t length) +{ + printf_debug_info("on_status", at, length); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_add(decoder->table, HTTP_ITERM_STATUS, at, length); + decoder->status = ON_STATUS; + + return 0; +} + +/* Information-only callbacks, return value is ignored */ +static int on_status_complete(llhttp_t *http) +{ + printf_debug_info("on_status_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_commit(decoder->table, HTTP_ITERM_STATUS); + decoder->commit_count++; + decoder->status = ON_STATUS_COMPLETE; + + return 0; +} + +/* Possible return values 0, -1, HPE_USER */ +static int on_header_field(llhttp_t *http, const char *at, size_t length) +{ + printf_debug_info("on_header_field", at, length); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_add(decoder->table, HTTP_ITERM_HEADER_FILED, at, length); + decoder->status = ON_HEADER_FIELD; + + return 0; +} + +/* Information-only callbacks, return value is ignored */ +static int on_header_field_complete(llhttp_t *http) +{ + printf_debug_info("on_header_field_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_commit(decoder->table, HTTP_ITERM_HEADER_FILED); + decoder->commit_count++; + decoder->status = ON_HEADER_FIELD_COMPLETE; + + return 0; +} + +/* Possible return values 0, -1, HPE_USER */ +static int on_header_value(llhttp_t *http, const char *at, size_t length) +{ + printf_debug_info("on_header_value", at, length); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_add(decoder->table, HTTP_ITERM_HEADER_VALUE, at, length); + decoder->status = ON_HEADER_VALUE; + + return 0; +} + +/* Information-only callbacks, return value is ignored */ +static int on_header_value_complete(llhttp_t *http) +{ + printf_debug_info("on_header_value_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_commit(decoder->table, HTTP_ITERM_HEADER_VALUE); + decoder->commit_count++; + decoder->status = ON_HEADER_VALUE_COMPLETE; + + return 0; +} + +/* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + * Possible return values 0, -1, `HPE_PAUSED` + */ +static int on_chunk_header(llhttp_t *http) +{ + printf_debug_info("on_chunk_header", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + decoder->status = ON_CHUNK_HEADER; + + return 0; +} + +/* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + * Possible return values 0, -1, `HPE_PAUSED` + */ +static int on_chunk_header_complete(llhttp_t *http) +{ + printf_debug_info("on_chunk_header_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + decoder->status = ON_CHUNK_HEADER_COMPLETE; + + return 0; +} + +/* Possible return values: + * 0 - Proceed normally + * 1 - Assume that request/response has no body, and proceed to parsing the next message + * 2 - Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE` + * -1 - Error `HPE_PAUSED` + */ +static int on_headers_complete(llhttp_t *http) +{ + printf_debug_info("on_headers_complete", NULL, 0); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + decoder->status = ON_HEADERS_COMPLETE; + + return 0; +} + +/* Possible return values 0, -1, HPE_USER */ +static int on_body(llhttp_t *http, const char *at, size_t length) +{ + printf_debug_info("on_body", at, length); + + struct http_decoder *decoder = container_of(http, struct http_decoder, parser); + http_decoder_table_add(decoder->table, HTTP_ITERM_BODY, at, length); + decoder->status = ON_BODY; + + return 0; +} + +/****************************************************************************** + * Manipulate http decoder + ******************************************************************************/ + +struct http_decoder *http_decoder_create() +{ + struct http_decoder *decoder = http_decoder_safe_alloc(struct http_decoder, 1); + + return decoder; +} + +void http_decoder_destory(struct http_decoder *decoder) +{ + if (decoder) + { + http_decoder_table_destory(decoder->table); + http_decoder_safe_free(decoder); + } +} + +void http_decoder_init(struct http_decoder *decoder) +{ + assert(decoder); + + enum llhttp_type type = HTTP_BOTH; // HTTP_BOTH | HTTP_REQUEST | HTTP_RESPONSE + + llhttp_settings_init(&decoder->settings); + llhttp_init(&decoder->parser, type, &decoder->settings); + + decoder->settings.on_message_begin = on_message_begin; + decoder->settings.on_message_complete = on_message_complete; + + decoder->settings.on_url = on_uri; + decoder->settings.on_url_complete = on_uri_complete; + + decoder->settings.on_status = on_status; + decoder->settings.on_status_complete = on_status_complete; + + decoder->settings.on_header_field = on_header_field; + decoder->settings.on_header_field_complete = on_header_field_complete; + + decoder->settings.on_header_value = on_header_value; + decoder->settings.on_header_value_complete = on_header_value_complete; + + decoder->settings.on_chunk_header = on_chunk_header; + decoder->settings.on_chunk_complete = on_chunk_header_complete; + + decoder->settings.on_headers_complete = on_headers_complete; + + decoder->settings.on_body = on_body; + + decoder->status = ON_INIT; +} + +void http_decoder_reset(struct http_decoder *decoder) +{ + if (decoder) + { + http_decoder_init(decoder); + http_decoder_table_reset(decoder->table); + decoder->commit_count = 0; + } +} + +/* + * return 0 : new data that needs to be consumed by upper layers has been parsed + * return -1 : no new data + * return -2 : error or not http protocol + */ +int http_decoder_dispatch(struct http_decoder *decoder, const char *data, size_t len) +{ + assert(decoder); + + enum llhttp_errno err = llhttp_execute(&decoder->parser, data, len); + if (err != HPE_OK) + { + fprintf(stderr, "llhttp_execute parse error: %s %s\n", llhttp_errno_name(err), decoder->parser.reason); + return -2; + } + + if (decoder->table == NULL) + { + return -1; + } + + if (http_decoder_table_status(decoder->table, HTTP_ITERM_URI) == RSTRING_STATUS_REFER) + { + http_decoder_table_cache(decoder->table, HTTP_ITERM_URI); + } + + if (http_decoder_table_status(decoder->table, HTTP_ITERM_STATUS) == RSTRING_STATUS_REFER) + { + http_decoder_table_cache(decoder->table, HTTP_ITERM_STATUS); + } + + if (http_decoder_table_status(decoder->table, HTTP_ITERM_HEADER_FILED) == RSTRING_STATUS_REFER) + { + http_decoder_table_cache(decoder->table, HTTP_ITERM_HEADER_FILED); + } + + if (http_decoder_table_status(decoder->table, HTTP_ITERM_HEADER_VALUE) == RSTRING_STATUS_REFER) + { + http_decoder_table_cache(decoder->table, HTTP_ITERM_HEADER_VALUE); + } + + // do not cache incomplete http body, submit immediately every time + if (http_decoder_table_status(decoder->table, HTTP_ITERM_BODY) == RSTRING_STATUS_REFER || + http_decoder_table_status(decoder->table, HTTP_ITERM_BODY) == RSTRING_STATUS_CACHE) + + { + http_decoder_table_commit(decoder->table, HTTP_ITERM_BODY); + } + + if (decoder->commit_count) + { + return 0; + } + else + { + return -1; + } +} + +// remove the data that has been consumed by the upper layer +void http_decoder_remove(struct http_decoder *decoder) +{ + assert(decoder); + + if (decoder->table) + { + http_decoder_table_remove(decoder->table); + } + decoder->commit_count = 0; +} + +// for debug +void http_decoder_dump(struct http_decoder *decoder) +{ + uint64_t content_length = decoder->parser.content_length; + uint8_t type = decoder->parser.type; + uint8_t method = decoder->parser.method; + uint8_t http_major = decoder->parser.http_major; + uint8_t http_minor = decoder->parser.http_minor; + uint8_t header_state = decoder->parser.header_state; + uint8_t lenient_flags = decoder->parser.lenient_flags; + uint8_t upgrade = decoder->parser.upgrade; + uint8_t finish = decoder->parser.finish; + uint16_t flags = decoder->parser.flags; + uint16_t status_code = decoder->parser.status_code; + + char *method_str = (char *)llhttp_method_name((llhttp_method_t)method); + + printf("\n=====================================================\n"); + printf("content_length: %lu, type: %d, header_state: %d, lenient_flags: %d, upgrade: %d, finish: %d, flags: %d\n", + content_length, type, header_state, lenient_flags, upgrade, finish, flags); + printf("method: %d %s, http_major: %d, http_minor: %d, status_code: %d\n", + method, method_str, http_major, http_minor, status_code); + + if (decoder->table) + { + http_decoder_table_dump(decoder->table); + } + printf("=====================================================\n"); +} + +// for gtest +enum http_decoder_status http_decoder_status(struct http_decoder *decoder) +{ + return decoder->status; +} + +/****************************************************************************** + * Consume decoded table + ******************************************************************************/ + +// HTTP_DIR_UNKNOWN: Not Find +enum http_dir http_decoder_fetch_dir(struct http_decoder *decoder) +{ + if (decoder->status >= ON_URI) + { + switch (decoder->parser.type) + { + case 1: + return HTTP_DIR_REQUEST; + break; + case 2: + return HTTP_DIR_RESPONSE; + default: + return HTTP_DIR_UNKNOWN; + break; + } + } + else + { + return HTTP_DIR_UNKNOWN; + } +} + +// -1 : Not Find +int http_decoder_fetch_status_code(struct http_decoder *decoder) +{ + if (decoder->status >= ON_STATUS_COMPLETE && http_decoder_fetch_dir(decoder) == HTTP_DIR_RESPONSE) + { + return decoder->parser.status_code; + } + else + { + return -1; + } +} + +// -1 : Not Find +int http_decoder_fetch_major_version(struct http_decoder *decoder) +{ + if (decoder->status >= ON_HEADER_FIELD) + { + return decoder->parser.http_major; + } + else + { + return -1; + } +} + +// -1 : Not Find +int http_decoder_fetch_minor_version(struct http_decoder *decoder) +{ + if (decoder->status >= ON_HEADER_FIELD) + { + return decoder->parser.http_minor; + } + else + { + return -1; + } +} + +// ptr == NULL : Not Find +void http_decoder_fetch_method(struct http_decoder *decoder, char **ptr, size_t *len) +{ + if (decoder->status >= ON_URI && http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST) + { + const char *method_str = llhttp_method_name((llhttp_method_t)decoder->parser.method); + *ptr = (char *)method_str; + *len = strlen(method_str); + } + else + { + *ptr = NULL; + *len = 0; + } +} + +// ptr == NULL : Not Find +void http_decoder_fetch_uri(struct http_decoder *decoder, char **ptr, size_t *len) +{ + http_decoder_table_get_uri(decoder->table, ptr, len); +} + +// ptr == NULL : Not Find +void http_decoder_fetch_status(struct http_decoder *decoder, char **ptr, size_t *len) +{ + http_decoder_table_get_status(decoder->table, ptr, len); +} + +// ptr == NULL : Not Find +void http_decoder_fetch_body(struct http_decoder *decoder, char **ptr, size_t *len) +{ + http_decoder_table_get_body(decoder->table, ptr, len); +} + +// filed_ptr == NULL : Not Find +void http_decoder_fetch_next_header(struct http_decoder *decoder, int *iter_index, + char **filed_ptr, size_t *filed_len, char **value_ptr, size_t *value_len) +{ + http_decoder_table_next_header(decoder->table, iter_index, filed_ptr, filed_len, value_ptr, value_len); +} diff --git a/src/protocol_decoder/http/http_decoder.h b/src/protocol_decoder/http/http_decoder.h new file mode 100644 index 0000000..5ca43a5 --- /dev/null +++ b/src/protocol_decoder/http/http_decoder.h @@ -0,0 +1,81 @@ +#ifndef _HTTP_DECODER_H +#define _HTTP_DECODER_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +/****************************************************************************** + * Manipulate http decoder(Per HTTP Session) + ******************************************************************************/ + +enum http_decoder_status +{ + ON_INIT = 0x1, + ON_MESSAGE_BEGIN = 0x2, + ON_URI = 0x3, + ON_URI_COMPLETE = 0x4, + ON_STATUS = 0x5, + ON_STATUS_COMPLETE = 0x6, + ON_HEADER_FIELD = 0x7, + ON_HEADER_FIELD_COMPLETE = 0x8, + ON_HEADER_VALUE = 0x9, + ON_HEADER_VALUE_COMPLETE = 0xa, + ON_CHUNK_HEADER = 0xb, + ON_CHUNK_HEADER_COMPLETE = 0xc, + ON_HEADERS_COMPLETE = 0xd, + ON_BODY = 0xe, + ON_MESSAGE_COMPLETE = 0xf, +}; + +struct http_decoder; + +struct http_decoder *http_decoder_create(); +void http_decoder_destory(struct http_decoder *decoder); + +void http_decoder_init(struct http_decoder *decoder); +void http_decoder_reset(struct http_decoder *decoder); + +/* + * return 0 : new data that needs to be consumed by upper layers has been parsed + * return -1 : no new data + * return -2 : error or not http protocol + */ +int http_decoder_dispatch(struct http_decoder *decoder, const char *data, size_t len); +// remove the data that has been consumed by the upper layer +void http_decoder_remove(struct http_decoder *decoder); + +void http_decoder_dump(struct http_decoder *decoder); // for debug +enum http_decoder_status http_decoder_status(struct http_decoder *decoder); // for gtest + +/****************************************************************************** + * Consume decoded data + ******************************************************************************/ + +enum http_dir +{ + HTTP_DIR_UNKNOWN = 0x0, + HTTP_DIR_REQUEST = 0x1, + HTTP_DIR_RESPONSE = 0x2, +}; + +enum http_dir http_decoder_fetch_dir(struct http_decoder *decoder); // HTTP_DIR_UNKNOWN: Not Find +int http_decoder_fetch_status_code(struct http_decoder *decoder); // -1 : Not Find +int http_decoder_fetch_major_version(struct http_decoder *decoder); // -1 : Not Find +int http_decoder_fetch_minor_version(struct http_decoder *decoder); // -1 : Not Find + +void http_decoder_fetch_method(struct http_decoder *decoder, char **ptr, size_t *len); // ptr == NULL : Not Find +void http_decoder_fetch_uri(struct http_decoder *decoder, char **ptr, size_t *len); // ptr == NULL : Not Find +void http_decoder_fetch_status(struct http_decoder *decoder, char **ptr, size_t *len); // ptr == NULL : Not Find +void http_decoder_fetch_body(struct http_decoder *decoder, char **ptr, size_t *len); // ptr == NULL : Not Find +void http_decoder_fetch_next_header(struct http_decoder *decoder, int *iter_index, + char **filed_ptr, size_t *filed_len, char **value_ptr, size_t *value_len); // filed_ptr == NULL : Not Find + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/src/protocol_decoder/http/http_decoder_rstring.cpp b/src/protocol_decoder/http/http_decoder_rstring.cpp new file mode 100644 index 0000000..981d107 --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_rstring.cpp @@ -0,0 +1,179 @@ +#include +#include +#include + +#include "http_decoder_util.h" +#include "http_decoder_rstring.h" + +static const char *rstring_status_to_desc(enum rstring_status status) +{ + switch (status) + { + case RSTRING_STATUS_INIT: + return "INIT"; + break; + case RSTRING_STATUS_REFER: + return "REFER"; + break; + case RSTRING_STATUS_CACHE: + return "CACHE"; + break; + case RSTRING_STATUS_COMMIT: + return "COMMIT"; + break; + default: + return "UNKNOWN"; + break; + } +} + +// RSTRING_STATUS_INIT -> RSTRING_STATUS_REFER +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_REFER +void http_decoder_rstring_refer(struct http_decoder_rstring *rstr, const char *at, size_t length) +{ + assert(rstr); + + switch (rstr->status) + { + case RSTRING_STATUS_INIT: + case RSTRING_STATUS_CACHE: + rstr->refer.str = (char *)at; + rstr->refer.len = length; + break; + default: + abort(); + break; + } + + rstr->status = RSTRING_STATUS_REFER; +} + +// RSTRING_STATUS_REFER -> RSTRING_STATUS_CACHE +void http_decoder_rstring_cache(struct http_decoder_rstring *rstr) +{ + assert(rstr); + + switch (rstr->status) + { + case RSTRING_STATUS_REFER: + if (rstr->refer.len > 0) + { + rstr->cache.str = http_decoder_safe_realloc(char, rstr->cache.str, rstr->cache.len + rstr->refer.len); + memcpy(rstr->cache.str + rstr->cache.len, rstr->refer.str, rstr->refer.len); + rstr->cache.len += rstr->refer.len; + + rstr->refer.str = NULL; + rstr->refer.len = 0; + } + break; + default: + abort(); + break; + } + + rstr->status = RSTRING_STATUS_CACHE; +} + +// RSTRING_STATUS_REFER -> RSTRING_STATUS_COMMIT +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_COMMIT +void http_decoder_rstring_commit(struct http_decoder_rstring *rstr) +{ + assert(rstr); + + switch (rstr->status) + { + case RSTRING_STATUS_REFER: + if (rstr->cache.len) + { + http_decoder_rstring_cache(rstr); + + rstr->commit.str = rstr->cache.str; + rstr->commit.len = rstr->cache.len; + // not overwrite rstr->cache.str + } + else + { + rstr->commit.str = rstr->refer.str; + rstr->commit.len = rstr->refer.len; + + rstr->refer.str = NULL; + rstr->refer.len = 0; + } + break; + case RSTRING_STATUS_CACHE: + rstr->commit.str = rstr->cache.str; + rstr->commit.len = rstr->cache.len; + // not overwrite rstr->cache.str + break; + default: + abort(); + break; + } + + rstr->status = RSTRING_STATUS_COMMIT; +} + +// RSTRING_STATUS_INIT -> RSTRING_STATUS_INIT +// RSTRING_STATUS_REFER -> RSTRING_STATUS_INIT +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_INIT +// RSTRING_STATUS_COMMIT -> RSTRING_STATUS_INIT +void http_decoder_rstring_reset(struct http_decoder_rstring *rstr) +{ + assert(rstr); + + switch (rstr->status) + { + case RSTRING_STATUS_INIT: + case RSTRING_STATUS_REFER: + case RSTRING_STATUS_CACHE: + case RSTRING_STATUS_COMMIT: + http_decoder_safe_free(rstr->cache.str); + memset(rstr, 0, sizeof(struct http_decoder_rstring)); + break; + default: + abort(); + break; + } + + rstr->status = RSTRING_STATUS_INIT; +} + +enum rstring_status http_decoder_rstring_status(struct http_decoder_rstring *rstr) +{ + return rstr->status; +} + +void http_decoder_rstring_read(struct http_decoder_rstring *rstr, char **ptr, size_t *len) +{ + assert(rstr); + + if (http_decoder_rstring_status(rstr) == RSTRING_STATUS_COMMIT) + { + *ptr = rstr->commit.str; + *len = rstr->commit.len; + } + else + { + *ptr = NULL; + *len = 0; + } +} + +void http_decoder_rstring_dump(struct http_decoder_rstring *rstr, const char *desc) +{ + assert(rstr); + + char *refer_str = http_decoder_safe_dup(rstr->refer.str, rstr->refer.len); + char *cache_str = http_decoder_safe_dup(rstr->cache.str, rstr->cache.len); + char *commit_str = http_decoder_safe_dup(rstr->commit.str, rstr->commit.len); + + printf("%s: status: %s, refer: {len: %02zu, str: %s}, cache: {len: %02zu, str: %s}, commit: {len: %02zu, str: %s}\n", + desc, rstring_status_to_desc(rstr->status), + rstr->refer.len, refer_str, + rstr->cache.len, cache_str, + rstr->commit.len, commit_str); + + http_decoder_safe_free(refer_str); + http_decoder_safe_free(cache_str); + http_decoder_safe_free(commit_str); +} \ No newline at end of file diff --git a/src/protocol_decoder/http/http_decoder_rstring.h b/src/protocol_decoder/http/http_decoder_rstring.h new file mode 100644 index 0000000..0af2c89 --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_rstring.h @@ -0,0 +1,90 @@ +#ifndef _HTTP_DECODER_RSTRING_H +#define _HTTP_DECODER_RSTRING_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +enum rstring_status +{ + RSTRING_STATUS_INIT, + RSTRING_STATUS_REFER, + RSTRING_STATUS_CACHE, + RSTRING_STATUS_COMMIT, +}; + +struct rstring +{ + char *str; + size_t len; +}; + +struct http_decoder_rstring +{ + struct rstring refer; // shallow copy + struct rstring cache; // deep copy + struct rstring commit; + + enum rstring_status status; +}; + +/* state transition diagram + * +----------+ + * | | + * \|/ | + * +------+ | + * | init | | + * +------+ | + * | | + * +---->| | + * | \|/ | + * | +-------+ | + * | | refer |--+ | + * | +-------+ | | + * | | | | + * | \|/ | | + * | +-------+ | | + * +--| cache | | | + * +-------+ | | + * | | | + * |<------+ | + * \|/ | + * +--------+ | + * | commit | | + * +--------+ | + * | | + * \|/ | + * +--------+ | + * | reset |----+ + * +--------+ + */ + +// RSTRING_STATUS_INIT -> RSTRING_STATUS_REFER +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_REFER +void http_decoder_rstring_refer(struct http_decoder_rstring *rstr, const char *at, size_t length); + +// RSTRING_STATUS_REFER -> RSTRING_STATUS_CACHE +void http_decoder_rstring_cache(struct http_decoder_rstring *rstr); + +// RSTRING_STATUS_REFER -> RSTRING_STATUS_COMMIT +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_COMMIT +void http_decoder_rstring_commit(struct http_decoder_rstring *rstr); + +// RSTRING_STATUS_INIT -> RSTRING_STATUS_INIT +// RSTRING_STATUS_REFER -> RSTRING_STATUS_INIT +// RSTRING_STATUS_CACHE -> RSTRING_STATUS_INIT +// RSTRING_STATUS_COMMIT -> RSTRING_STATUS_INIT +void http_decoder_rstring_reset(struct http_decoder_rstring *rstr); + +enum rstring_status http_decoder_rstring_status(struct http_decoder_rstring *rstr); +void http_decoder_rstring_read(struct http_decoder_rstring *rstr, char **ptr, size_t *len); +void http_decoder_rstring_dump(struct http_decoder_rstring *rstr, const char *desc); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/src/protocol_decoder/http/http_decoder_table.cpp b/src/protocol_decoder/http/http_decoder_table.cpp new file mode 100644 index 0000000..b98ddeb --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_table.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include + +#include "http_decoder_util.h" +#include "http_decoder_rstring.h" +#include "http_decoder_table.h" + +struct http_decoder_header +{ + struct http_decoder_rstring filed; + struct http_decoder_rstring value; +}; + +struct http_decoder_table +{ + struct http_decoder_rstring uri; + struct http_decoder_rstring status; + struct http_decoder_rstring body; + + int header_size; + int header_index; + struct http_decoder_header *headers; +}; + +struct http_decoder_table *http_decoder_table_create() +{ + struct http_decoder_table *table = http_decoder_safe_alloc(struct http_decoder_table, 1); + assert(table); + + table->header_size = 16; + table->headers = http_decoder_safe_alloc(struct http_decoder_header, table->header_size); + assert(table->headers); + + return table; +} + +void http_decoder_table_destory(struct http_decoder_table *table) +{ + if (table) + { + http_decoder_table_reset(table); + http_decoder_safe_free(table->headers); + http_decoder_safe_free(table); + } +} + +void http_decoder_table_reset(struct http_decoder_table *table) +{ + assert(table); + + http_decoder_rstring_reset(&table->uri); + http_decoder_rstring_reset(&table->status); + http_decoder_rstring_reset(&table->body); + + for (int i = 0; i < table->header_size; i++) + { + struct http_decoder_header *header = &table->headers[i]; + http_decoder_rstring_reset(&header->filed); + http_decoder_rstring_reset(&header->value); + } + + int header_size = table->header_size; + struct http_decoder_header *headers = table->headers; + + memset(table->headers, 0, sizeof(struct http_decoder_header) * table->header_size); + memset(table, 0, sizeof(struct http_decoder_table)); + + table->header_size = header_size; + table->headers = headers; +} + +void http_decoder_table_add(struct http_decoder_table *table, enum http_iterm type, const char *str, size_t len) +{ + struct http_decoder_header *header = NULL; + assert(table); + + switch (type) + { + case HTTP_ITERM_URI: + http_decoder_rstring_refer(&table->uri, str, len); + break; + case HTTP_ITERM_STATUS: + http_decoder_rstring_refer(&table->status, str, len); + break; + case HTTP_ITERM_HEADER_FILED: + if (table->header_index >= table->header_size) + { + table->headers = http_decoder_safe_realloc(struct http_decoder_header, table->headers, table->header_size * 2 * sizeof(struct http_decoder_header)); + assert(table->headers); + table->header_size *= 2; + for (int i = table->header_index; i < table->header_size; i++) + { + header = &table->headers[i]; + memset(header, 0, sizeof(struct http_decoder_header)); + } + } + header = &table->headers[table->header_index]; + http_decoder_rstring_refer(&header->filed, str, len); + break; + case HTTP_ITERM_HEADER_VALUE: + header = &table->headers[table->header_index]; + http_decoder_rstring_refer(&header->value, str, len); + break; + case HTTP_ITERM_BODY: + http_decoder_rstring_refer(&table->body, str, len); + break; + default: + abort(); + break; + } +} + +void http_decoder_table_cache(struct http_decoder_table *table, enum http_iterm type) +{ + struct http_decoder_header *header = NULL; + assert(table); + + switch (type) + { + case HTTP_ITERM_URI: + http_decoder_rstring_cache(&table->uri); + break; + case HTTP_ITERM_STATUS: + http_decoder_rstring_cache(&table->status); + break; + case HTTP_ITERM_HEADER_FILED: + header = &table->headers[table->header_index]; + http_decoder_rstring_cache(&header->filed); + break; + case HTTP_ITERM_HEADER_VALUE: + header = &table->headers[table->header_index]; + http_decoder_rstring_cache(&header->value); + break; + case HTTP_ITERM_BODY: + http_decoder_rstring_cache(&table->body); + break; + default: + abort(); + break; + } +} + +void http_decoder_table_commit(struct http_decoder_table *table, enum http_iterm type) +{ + struct http_decoder_header *header = NULL; + assert(table); + + switch (type) + { + case HTTP_ITERM_URI: + http_decoder_rstring_commit(&table->uri); + break; + case HTTP_ITERM_STATUS: + http_decoder_rstring_commit(&table->status); + break; + case HTTP_ITERM_HEADER_FILED: + header = &table->headers[table->header_index]; + http_decoder_rstring_commit(&header->filed); + break; + case HTTP_ITERM_HEADER_VALUE: + header = &table->headers[table->header_index]; + http_decoder_rstring_commit(&header->value); + + // inc index + table->header_index++; + break; + case HTTP_ITERM_BODY: + http_decoder_rstring_commit(&table->body); + break; + default: + abort(); + break; + } +} + +void http_decoder_table_remove(struct http_decoder_table *table) +{ + assert(table); + + if (http_decoder_rstring_status(&table->uri) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_reset(&table->uri); + } + + if (http_decoder_rstring_status(&table->status) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_reset(&table->status); + } + + if (http_decoder_rstring_status(&table->body) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_reset(&table->body); + } + + for (int i = 0; i <= table->header_index; i++) + { + struct http_decoder_header *header = &table->headers[i]; + if (http_decoder_rstring_status(&header->filed) == RSTRING_STATUS_COMMIT && http_decoder_rstring_status(&header->value) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_reset(&header->filed); + http_decoder_rstring_reset(&header->value); + } + } + + if (table->header_index != 0) + { + struct http_decoder_header *last_header = &table->headers[table->header_index]; + if (http_decoder_rstring_status(&last_header->filed) == RSTRING_STATUS_CACHE || http_decoder_rstring_status(&last_header->value) == RSTRING_STATUS_CACHE) + { + memmove(&table->headers[0], last_header, sizeof(struct http_decoder_header)); + memset(last_header, 0, sizeof(struct http_decoder_header)); + } + + table->header_index = 0; + } +} + +void http_decoder_table_dump(struct http_decoder_table *table) +{ + assert(table); + + http_decoder_rstring_dump(&table->uri, "uri"); + http_decoder_rstring_dump(&table->status, "status"); + http_decoder_rstring_dump(&table->body, "body"); + + for (int i = 0; i <= table->header_index; i++) + { + struct http_decoder_header *header = &table->headers[i]; + http_decoder_rstring_dump(&header->filed, "filed"); + http_decoder_rstring_dump(&header->value, "value"); + } +} + +enum rstring_status http_decoder_table_status(struct http_decoder_table *table, enum http_iterm type) +{ + struct http_decoder_header *header = NULL; + enum rstring_status status = RSTRING_STATUS_INIT; + assert(table); + + switch (type) + { + case HTTP_ITERM_URI: + status = http_decoder_rstring_status(&table->uri); + break; + case HTTP_ITERM_STATUS: + status = http_decoder_rstring_status(&table->status); + break; + case HTTP_ITERM_HEADER_FILED: + header = &table->headers[table->header_index]; + status = http_decoder_rstring_status(&header->filed); + break; + case HTTP_ITERM_HEADER_VALUE: + header = &table->headers[table->header_index]; + status = http_decoder_rstring_status(&header->value); + break; + case HTTP_ITERM_BODY: + status = http_decoder_rstring_status(&table->body); + break; + default: + abort(); + break; + } + + return status; +} + +void http_decoder_table_get_uri(struct http_decoder_table *table, char **ptr, size_t *len) +{ + assert(table); + + if (http_decoder_rstring_status(&table->uri) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_read(&table->uri, ptr, len); + } + else + { + *ptr = NULL; + *len = 0; + } +} + +void http_decoder_table_get_status(struct http_decoder_table *table, char **ptr, size_t *len) +{ + assert(table); + + if (http_decoder_rstring_status(&table->status) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_read(&table->status, ptr, len); + } + else + { + *ptr = NULL; + *len = 0; + } +} + +void http_decoder_table_get_body(struct http_decoder_table *table, char **ptr, size_t *len) +{ + assert(table); + + if (http_decoder_rstring_status(&table->body) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_read(&table->body, ptr, len); + } + else + { + *ptr = NULL; + *len = 0; + } +} + +void http_decoder_table_next_header(struct http_decoder_table *table, int *iter_index, char **filed_ptr, size_t *filed_len, char **value_ptr, size_t *value_len) +{ + assert(table); + + if (*iter_index <= table->header_index) + { + struct http_decoder_header *header = &table->headers[*iter_index]; + if (http_decoder_rstring_status(&header->filed) == RSTRING_STATUS_COMMIT && http_decoder_rstring_status(&header->value) == RSTRING_STATUS_COMMIT) + { + http_decoder_rstring_read(&header->filed, filed_ptr, filed_len); + http_decoder_rstring_read(&header->value, value_ptr, value_len); + } + else + { + *filed_ptr = NULL; + *filed_len = 0; + + *value_ptr = NULL; + *value_len = 0; + } + *iter_index = *iter_index + 1; + } + else + { + *filed_ptr = NULL; + *filed_len = 0; + + *value_ptr = NULL; + *value_len = 0; + } +} \ No newline at end of file diff --git a/src/protocol_decoder/http/http_decoder_table.h b/src/protocol_decoder/http/http_decoder_table.h new file mode 100644 index 0000000..48cdd89 --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_table.h @@ -0,0 +1,45 @@ +#ifndef _HTTP_DECODER_TABLE_H +#define _HTTP_DECODER_TABLE_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +#include "http_decoder_rstring.h" + +enum http_iterm +{ + HTTP_ITERM_URI = 0x01, + HTTP_ITERM_STATUS = 0x02, + HTTP_ITERM_HEADER_FILED = 0x03, + HTTP_ITERM_HEADER_VALUE = 0x04, + HTTP_ITERM_BODY = 0x05, +}; + +struct http_decoder_table; + +struct http_decoder_table *http_decoder_table_create(); +void http_decoder_table_destory(struct http_decoder_table *table); +void http_decoder_table_reset(struct http_decoder_table *table); + +void http_decoder_table_add(struct http_decoder_table *table, enum http_iterm type, const char *str, size_t len); +void http_decoder_table_cache(struct http_decoder_table *table, enum http_iterm type); +void http_decoder_table_commit(struct http_decoder_table *table, enum http_iterm type); +void http_decoder_table_remove(struct http_decoder_table *table); +void http_decoder_table_dump(struct http_decoder_table *table); +enum rstring_status http_decoder_table_status(struct http_decoder_table *table, enum http_iterm type); + +void http_decoder_table_get_uri(struct http_decoder_table *table, char **ptr, size_t *len); +void http_decoder_table_get_status(struct http_decoder_table *table, char **ptr, size_t *len); +void http_decoder_table_get_body(struct http_decoder_table *table, char **ptr, size_t *len); + +void http_decoder_table_next_header(struct http_decoder_table *table, int *iter_index, char **filed_ptr, size_t *filed_len, char **value_ptr, size_t *value_len); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/src/protocol_decoder/http/http_decoder_util.cpp b/src/protocol_decoder/http/http_decoder_util.cpp new file mode 100644 index 0000000..63a154d --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_util.cpp @@ -0,0 +1,16 @@ +#include + +#include "http_decoder_util.h" + +char *http_decoder_safe_dup(const char *str, size_t len) +{ + if (str == NULL || len == 0) + { + return NULL; + } + + char *dup = http_decoder_safe_alloc(char, len + 1); + memcpy(dup, str, len); + + return dup; +} \ No newline at end of file diff --git a/src/protocol_decoder/http/http_decoder_util.h b/src/protocol_decoder/http/http_decoder_util.h new file mode 100644 index 0000000..de2bbb3 --- /dev/null +++ b/src/protocol_decoder/http/http_decoder_util.h @@ -0,0 +1,61 @@ +#ifndef _HTTP_DECODER_UTIL_H +#define _HTTP_DECODER_UTIL_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +/****************************************************************************** + * Malloc + ******************************************************************************/ + +#define http_decoder_safe_realloc(type, ptr, number) ((type *)realloc(ptr, number)) +#define http_decoder_safe_alloc(type, number) ((type *)calloc(number, sizeof(type))) +#define http_decoder_safe_free(ptr) \ + { \ + if (ptr) \ + { \ + free(ptr); \ + ptr = NULL; \ + } \ + } +char *http_decoder_safe_dup(const char *str, size_t len); + +/****************************************************************************** + * Offset + ******************************************************************************/ + +#ifndef offsetof +#define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER) +#endif + +/* + * Return pointer to the wrapping struct instance. + * + * Example: + * + * struct wrapper { + * ... + * struct child c; + * ... + * }; + * + * struct child *x = obtain(...); + * struct wrapper *w = container_of(x, struct wrapper, c); + */ +#ifndef container_of +#define container_of(ptr, type, member) __extension__({ \ + const typeof(((type *)0)->member) *_ptr = (ptr); \ + __attribute__((unused)) type *_target_ptr = (type *)(ptr); \ + (type *)(((uintptr_t)_ptr) - offsetof(type, member)); \ +}) +#endif + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/src/protocol_decoder/http/test/CMakeLists.txt b/src/protocol_decoder/http/test/CMakeLists.txt new file mode 100644 index 0000000..54d8d44 --- /dev/null +++ b/src/protocol_decoder/http/test/CMakeLists.txt @@ -0,0 +1,38 @@ +add_executable(http_decoder_rstring_test + http_decoder_rstring_test.cpp +) + +add_executable(http_decoder_table_test + http_decoder_table_test.cpp +) + +add_executable(http_decoder_test + http_decoder_test.cpp +) + +target_link_libraries( + http_decoder_rstring_test + gtest + http + llhttp-static +) + +target_link_libraries( + http_decoder_table_test + gtest + http +) + +target_link_libraries( + http_decoder_test + gtest + http +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,--export-dynamic") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--export-dynamic") + +include(GoogleTest) +gtest_discover_tests(http_decoder_rstring_test) +gtest_discover_tests(http_decoder_table_test) +gtest_discover_tests(http_decoder_test) \ No newline at end of file diff --git a/src/protocol_decoder/http/test/http_decoder_rstring_test.cpp b/src/protocol_decoder/http/test/http_decoder_rstring_test.cpp new file mode 100644 index 0000000..046fb86 --- /dev/null +++ b/src/protocol_decoder/http/test/http_decoder_rstring_test.cpp @@ -0,0 +1,328 @@ +#include + +#include "../http_decoder_util.h" +#include "../http_decoder_rstring.h" + +TEST(HTTP_DECODER_RSTRING_TEST, refer_commit_reset) +{ + char *ptr = NULL; + size_t len = 0; + struct http_decoder_rstring rstr = {0}; + char *hello_str = http_decoder_safe_dup("Hello111", 8); + + // refer + http_decoder_rstring_refer(&rstr, hello_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // commit + http_decoder_rstring_commit(&rstr); + http_decoder_rstring_dump(&rstr, "After commit"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_COMMIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 5); + EXPECT_TRUE(strncmp(rstr.commit.str, "Hello", 5) == 0); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(strncmp("Hello", ptr, len) == 0); + + // reset + http_decoder_rstring_reset(&rstr); + http_decoder_rstring_dump(&rstr, "After reset"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_INIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + http_decoder_safe_free(hello_str); +} + +TEST(HTTP_DECODER_RSTRING_TEST, refer_cache_commit_reset) +{ + char *ptr = NULL; + size_t len = 0; + struct http_decoder_rstring rstr = {0}; + char *hello_str = http_decoder_safe_dup("Hello111", 8); + + // refer + http_decoder_rstring_refer(&rstr, hello_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // cache + http_decoder_rstring_cache(&rstr); + http_decoder_rstring_dump(&rstr, "After cache"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_CACHE); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + http_decoder_safe_free(hello_str); + + // commit + http_decoder_rstring_commit(&rstr); + http_decoder_rstring_dump(&rstr, "After commit"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_COMMIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 5); + EXPECT_TRUE(strncmp(rstr.commit.str, "Hello", 5) == 0); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(strncmp("Hello", ptr, len) == 0); + + // reset + http_decoder_rstring_reset(&rstr); + http_decoder_rstring_dump(&rstr, "After reset"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_INIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); +} + +TEST(HTTP_DECODER_RSTRING_TEST, refer_cache_refer_commit_reset) +{ + char *ptr = NULL; + size_t len = 0; + struct http_decoder_rstring rstr = {0}; + char *hello_str = http_decoder_safe_dup("Hello111", 8); + char *world_str = http_decoder_safe_dup("World222", 8); + + // refer + http_decoder_rstring_refer(&rstr, hello_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // cache + http_decoder_rstring_cache(&rstr); + http_decoder_rstring_dump(&rstr, "After cache"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_CACHE); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + http_decoder_safe_free(hello_str); + + // refer + http_decoder_rstring_refer(&rstr, world_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "World", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // commit + http_decoder_rstring_commit(&rstr); + http_decoder_rstring_dump(&rstr, "After commit"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_COMMIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 10); + EXPECT_TRUE(strncmp(rstr.cache.str, "HelloWorld", 10) == 0); + EXPECT_TRUE(rstr.commit.len == 10); + EXPECT_TRUE(strncmp(rstr.commit.str, "HelloWorld", 10) == 0); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(strncmp("HelloWorld", ptr, len) == 0); + + http_decoder_safe_free(world_str); + + // reset + http_decoder_rstring_reset(&rstr); + http_decoder_rstring_dump(&rstr, "After reset"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_INIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); +} + +TEST(HTTP_DECODER_RSTRING_TEST, refer_cache_refer_cache_commit_reset) +{ + char *ptr = NULL; + size_t len = 0; + struct http_decoder_rstring rstr = {0}; + char *hello_str = http_decoder_safe_dup("Hello111", 8); + char *world_str = http_decoder_safe_dup("World222", 8); + + // refer + http_decoder_rstring_refer(&rstr, hello_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // cache + http_decoder_rstring_cache(&rstr); + http_decoder_rstring_dump(&rstr, "After cache"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_CACHE); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + http_decoder_safe_free(hello_str); + + // refer + http_decoder_rstring_refer(&rstr, world_str, 5); + http_decoder_rstring_dump(&rstr, "After refer"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_REFER); + EXPECT_TRUE(rstr.refer.len == 5); + EXPECT_TRUE(strncmp(rstr.refer.str, "World", 5) == 0); + EXPECT_TRUE(rstr.cache.len == 5); + EXPECT_TRUE(strncmp(rstr.cache.str, "Hello", 5) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + // cache + http_decoder_rstring_cache(&rstr); + http_decoder_rstring_dump(&rstr, "After cache"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_CACHE); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 10); + EXPECT_TRUE(strncmp(rstr.cache.str, "HelloWorld", 10) == 0); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); + + http_decoder_safe_free(world_str); + + // commit + http_decoder_rstring_commit(&rstr); + http_decoder_rstring_dump(&rstr, "After commit"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_COMMIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 10); + EXPECT_TRUE(strncmp(rstr.cache.str, "HelloWorld", 10) == 0); + EXPECT_TRUE(rstr.commit.len == 10); + EXPECT_TRUE(strncmp(rstr.commit.str, "HelloWorld", 10) == 0); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(strncmp("HelloWorld", ptr, len) == 0); + + // reset + http_decoder_rstring_reset(&rstr); + http_decoder_rstring_dump(&rstr, "After reset"); + + EXPECT_TRUE(http_decoder_rstring_status(&rstr) == RSTRING_STATUS_INIT); + EXPECT_TRUE(rstr.refer.len == 0); + EXPECT_TRUE(rstr.refer.str == NULL); + EXPECT_TRUE(rstr.cache.len == 0); + EXPECT_TRUE(rstr.cache.str == NULL); + EXPECT_TRUE(rstr.commit.len == 0); + EXPECT_TRUE(rstr.commit.str == NULL); + + http_decoder_rstring_read(&rstr, &ptr, &len); + EXPECT_TRUE(NULL == ptr && len == 0); +} + +int main(int argc, char **argv) +{ + int ret = 0; + ::testing::InitGoogleTest(&argc, argv); + ret = RUN_ALL_TESTS(); + return ret; +} \ No newline at end of file diff --git a/src/protocol_decoder/http/test/http_decoder_table_test.cpp b/src/protocol_decoder/http/test/http_decoder_table_test.cpp new file mode 100644 index 0000000..d4d25f8 --- /dev/null +++ b/src/protocol_decoder/http/test/http_decoder_table_test.cpp @@ -0,0 +1,431 @@ +#include + +#include "../http_decoder_table.h" + +struct key_val +{ + const char *key; + const char *val; +}; + +struct key_val simple_headers[] = { + {"Host", "www.baidu.com"}, + {"User-Agent", "curl/7.64.1"}, + {"connection", "close"}, + {"Accept", "*/*"}, +}; + +// At least 17 headers to trigger realloc +struct key_val mutil_headers[] = { + {"Server", "nginx"}, + {"content-length", "11"}, + {"Date", "Mon, 15 Aug 2022 07:40:35 GMT"}, + {"Content-Type", "text/html;charset=utf-8"}, + {"Expires", "Mon, 15 Aug 2022 07:50:34 GMT"}, + {"X-Cache-Lookup", "Hit From Disktank3"}, + {"ETag", "346f24f3000e27ce6f3d3b61f2c6a83b"}, + {"X-XSS-Protection", "0"}, + {"X-Daa-Tunnel", "hop_count=1"}, + {"X-Cache-Lookup", "Hit From Inner Cluster"}, + {"X-Cache-Lookup", "Cache Miss"}, + {"Last-Modified", "Mon, 15 Aug 2022 07:30:00 GMT"}, + {"Cache-Control", "private, max-age=600"}, + {"Content-Length", "51792"}, + {"X-NWS-LOG-UUID", "13366651529734458115"}, + {"Connection", "keep-alive"}, + {"X-Cache-Lookup", "Cache Miss"}, + {"Set-cookie", "aaa"}, + {"Set-cookie", "bbb"}, +}; + +const char *append = "000"; +const char *status = "OK"; +const char *uri = "index.html"; +const char *body = "hello world"; + +TEST(HTTP_DECODER_TABLE_TEST, add_commit) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get uri + http_decoder_table_add(data, HTTP_ITERM_URI, uri, strlen(uri)); + http_decoder_table_commit(data, HTTP_ITERM_URI); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(uri, key_str, key_len) == 0); + + // add/get status + http_decoder_table_add(data, HTTP_ITERM_STATUS, status, strlen(status)); + http_decoder_table_commit(data, HTTP_ITERM_STATUS); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(status, key_str, key_len) == 0); + + // add/get body + http_decoder_table_add(data, HTTP_ITERM_BODY, body, strlen(body)); + http_decoder_table_commit(data, HTTP_ITERM_BODY); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(body, key_str, key_len) == 0); + + // add/get header + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, simple_headers[i].key, strlen(simple_headers[i].key)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, simple_headers[i].val, strlen(simple_headers[i].val)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_VALUE); + } + + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp(simple_headers[i].key, key_str, key_len) == 0); + EXPECT_TRUE(strncmp(simple_headers[i].val, val_str, val_len) == 0); + } + + // dump + printf("After commit:\n"); + http_decoder_table_dump(data); + + // destory + http_decoder_table_destory(data); +} + +TEST(HTTP_DECODER_TABLE_TEST, add_cache_commit) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get uri + http_decoder_table_add(data, HTTP_ITERM_URI, uri, strlen(uri)); + http_decoder_table_cache(data, HTTP_ITERM_URI); + http_decoder_table_commit(data, HTTP_ITERM_URI); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(uri, key_str, key_len) == 0); + + // add/get status + http_decoder_table_add(data, HTTP_ITERM_STATUS, status, strlen(status)); + http_decoder_table_cache(data, HTTP_ITERM_STATUS); + http_decoder_table_commit(data, HTTP_ITERM_STATUS); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(status, key_str, key_len) == 0); + + // add/get body + http_decoder_table_add(data, HTTP_ITERM_BODY, body, strlen(body)); + http_decoder_table_cache(data, HTTP_ITERM_BODY); + http_decoder_table_commit(data, HTTP_ITERM_BODY); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(body, key_str, key_len) == 0); + + // add/get header + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, simple_headers[i].key, strlen(simple_headers[i].key)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_FILED); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, simple_headers[i].val, strlen(simple_headers[i].val)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_VALUE); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_VALUE); + } + + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp(simple_headers[i].key, key_str, key_len) == 0); + EXPECT_TRUE(strncmp(simple_headers[i].val, val_str, val_len) == 0); + } + + // dump + printf("After commit:\n"); + http_decoder_table_dump(data); + + // destory + http_decoder_table_destory(data); +} + +TEST(HTTP_DECODER_TABLE_TEST, add_cache_add_commit) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get uri + http_decoder_table_add(data, HTTP_ITERM_URI, uri, strlen(uri)); + http_decoder_table_cache(data, HTTP_ITERM_URI); + http_decoder_table_add(data, HTTP_ITERM_URI, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_URI); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(key_str, uri, strlen(uri)) == 0); + EXPECT_TRUE(strncmp(key_str + strlen(uri), append, strlen(append)) == 0); + + // add/get status + http_decoder_table_add(data, HTTP_ITERM_STATUS, status, strlen(status)); + http_decoder_table_cache(data, HTTP_ITERM_STATUS); + http_decoder_table_add(data, HTTP_ITERM_STATUS, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_STATUS); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(key_str, status, strlen(status)) == 0); + EXPECT_TRUE(strncmp(key_str + strlen(status), append, strlen(append)) == 0); + + // add/get body + http_decoder_table_add(data, HTTP_ITERM_BODY, body, strlen(body)); + http_decoder_table_cache(data, HTTP_ITERM_BODY); + http_decoder_table_add(data, HTTP_ITERM_BODY, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_BODY); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(key_str, body, strlen(body)) == 0); + EXPECT_TRUE(strncmp(key_str + strlen(body), append, strlen(append)) == 0); + + // add/get header + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, simple_headers[i].key, strlen(simple_headers[i].key)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_FILED); + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, simple_headers[i].val, strlen(simple_headers[i].val)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_VALUE); + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_VALUE); + } + + for (size_t i = 0; i < sizeof(simple_headers) / sizeof(simple_headers[0]); i++) + { + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + + EXPECT_TRUE(strncmp(key_str, simple_headers[i].key, strlen(simple_headers[i].key)) == 0); + EXPECT_TRUE(strncmp(key_str + strlen(simple_headers[i].key), append, strlen(append)) == 0); + + EXPECT_TRUE(strncmp(val_str, simple_headers[i].val, strlen(simple_headers[i].val)) == 0); + EXPECT_TRUE(strncmp(val_str + strlen(simple_headers[i].val), append, strlen(append)) == 0); + } + + // dump + printf("After commit:\n"); + http_decoder_table_dump(data); + + // destory + http_decoder_table_destory(data); +} + +TEST(HTTP_DECODER_TABLE_TEST, realloc_header) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get header + for (size_t i = 0; i < sizeof(mutil_headers) / sizeof(mutil_headers[0]); i++) + { + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, mutil_headers[i].key, strlen(mutil_headers[i].key)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_FILED); + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, mutil_headers[i].val, strlen(mutil_headers[i].val)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_VALUE); + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, append, strlen(append)); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_VALUE); + } + + for (size_t i = 0; i < sizeof(mutil_headers) / sizeof(mutil_headers[0]); i++) + { + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + + EXPECT_TRUE(strncmp(key_str, mutil_headers[i].key, strlen(mutil_headers[i].key)) == 0); + EXPECT_TRUE(strncmp(key_str + strlen(mutil_headers[i].key), append, strlen(append)) == 0); + + EXPECT_TRUE(strncmp(val_str, mutil_headers[i].val, strlen(mutil_headers[i].val)) == 0); + EXPECT_TRUE(strncmp(val_str + strlen(mutil_headers[i].val), append, strlen(append)) == 0); + } + + // dump + printf("After commit:\n"); + http_decoder_table_dump(data); + + // destory + http_decoder_table_destory(data); +} + +TEST(HTTP_DECODER_TABLE_TEST, remove) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get uri + http_decoder_table_add(data, HTTP_ITERM_URI, uri, strlen(uri)); + http_decoder_table_cache(data, HTTP_ITERM_URI); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get status + http_decoder_table_add(data, HTTP_ITERM_STATUS, status, strlen(status)); + http_decoder_table_cache(data, HTTP_ITERM_STATUS); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get body + http_decoder_table_add(data, HTTP_ITERM_BODY, body, strlen(body)); + http_decoder_table_cache(data, HTTP_ITERM_BODY); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get header + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, simple_headers[0].key, strlen(simple_headers[0].key)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, simple_headers[0].val, strlen(simple_headers[0].val)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_VALUE); + + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + EXPECT_TRUE(val_str == NULL && val_len == 0); + + // dump + printf("After cache:\n"); + http_decoder_table_dump(data); + + http_decoder_table_remove(data); + + printf("After remove:\n"); + http_decoder_table_dump(data); + + http_decoder_table_commit(data, HTTP_ITERM_URI); + http_decoder_table_commit(data, HTTP_ITERM_STATUS); + http_decoder_table_commit(data, HTTP_ITERM_BODY); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_FILED); + http_decoder_table_commit(data, HTTP_ITERM_HEADER_VALUE); + + printf("After commit:\n"); + http_decoder_table_dump(data); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(uri, key_str, key_len) == 0); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(status, key_str, key_len) == 0); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(strncmp(body, key_str, key_len) == 0); + + iter_index = 0; + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + + EXPECT_TRUE(strncmp(simple_headers[0].key, key_str, key_len) == 0); + EXPECT_TRUE(strncmp(simple_headers[0].val, val_str, val_len) == 0); + + // destory + http_decoder_table_destory(data); +} + +TEST(HTTP_DECODER_TABLE_TEST, reset) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + // create + struct http_decoder_table *data = http_decoder_table_create(); + EXPECT_TRUE(data != NULL); + + // add/get uri + http_decoder_table_add(data, HTTP_ITERM_URI, uri, strlen(uri)); + http_decoder_table_cache(data, HTTP_ITERM_URI); + + http_decoder_table_get_uri(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get status + http_decoder_table_add(data, HTTP_ITERM_STATUS, status, strlen(status)); + http_decoder_table_cache(data, HTTP_ITERM_STATUS); + + http_decoder_table_get_status(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get body + http_decoder_table_add(data, HTTP_ITERM_BODY, body, strlen(body)); + http_decoder_table_cache(data, HTTP_ITERM_BODY); + + http_decoder_table_get_body(data, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + // add/get header + http_decoder_table_add(data, HTTP_ITERM_HEADER_FILED, simple_headers[0].key, strlen(simple_headers[0].key)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_FILED); + + http_decoder_table_add(data, HTTP_ITERM_HEADER_VALUE, simple_headers[0].val, strlen(simple_headers[0].val)); + http_decoder_table_cache(data, HTTP_ITERM_HEADER_VALUE); + + http_decoder_table_next_header(data, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + EXPECT_TRUE(val_str == NULL && val_len == 0); + + // dump + printf("After cache:\n"); + http_decoder_table_dump(data); + + http_decoder_table_reset(data); + + printf("After reset:\n"); + http_decoder_table_dump(data); + + // destory + http_decoder_table_destory(data); +} + +int main(int argc, char **argv) +{ + int ret = 0; + ::testing::InitGoogleTest(&argc, argv); + ret = RUN_ALL_TESTS(); + return ret; +} \ No newline at end of file diff --git a/src/protocol_decoder/http/test/http_decoder_test.cpp b/src/protocol_decoder/http/test/http_decoder_test.cpp new file mode 100644 index 0000000..a627077 --- /dev/null +++ b/src/protocol_decoder/http/test/http_decoder_test.cpp @@ -0,0 +1,614 @@ +#include "../http_decoder.h" + +#include + +const char *request_post = "POST /index.html HTTP/1.1\r\nconnection:close\r\ncontent-length: 1\r\n\r\n1\r\n\r\n"; +const char *request_get = "GET /index.html HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.64.1\r\nAccept: */*\r\n\r\n"; +const char *response_200 = "HTTP/1.1 200 OK\r\nServer: nginx\r\ncontent-length: 11\r\n\r\nhello:world\r\n\r\n"; + +/****************************************************************************** + * complete http request/response + ******************************************************************************/ + +#if 1 +TEST(HTTP_DECODER_TEST, complete_post_request) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + EXPECT_TRUE(http_decoder_dispatch(decoder, request_post, strlen(request_post)) == 0); + http_decoder_dump(decoder); + + // check data + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("POST", key_str, key_len) == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("1", key_str, key_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("connection", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("close", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("content-length", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("1", val_str, val_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, complete_get_request) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + EXPECT_TRUE(http_decoder_dispatch(decoder, request_get, strlen(request_get)) == 0); + http_decoder_dump(decoder); + + // check data + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("GET", key_str, key_len) == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("Host", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("www.baidu.com", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("User-Agent", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("curl/7.64.1", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("Accept", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("*/*", val_str, val_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, complete_response) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + EXPECT_TRUE(http_decoder_dispatch(decoder, response_200, strlen(response_200)) == 0); + http_decoder_dump(decoder); + + // check data + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_RESPONSE); + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == 200); + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("OK", key_str, key_len) == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("hello:world", key_str, key_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("Server", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("nginx", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("content-length", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("11", val_str, val_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +/****************************************************************************** + * uncomplete http request + ******************************************************************************/ + +#if 1 +TEST(HTTP_DECODER_TEST, uncomplete_post_request) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + for (size_t i = 0; i < strlen(request_post); i++) + { + EXPECT_TRUE(http_decoder_dispatch(decoder, request_post + i, 1) != -2); + } + http_decoder_dump(decoder); + + // check data + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("POST", key_str, key_len) == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("1", key_str, key_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("connection", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("close", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("content-length", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("1", val_str, val_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, uncomplete_get_request) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + for (size_t i = 0; i < strlen(request_get); i++) + { + EXPECT_TRUE(http_decoder_dispatch(decoder, request_get + i, 1) != -2); + } + http_decoder_dump(decoder); + + // check data + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("GET", key_str, key_len) == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("Host", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("www.baidu.com", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("User-Agent", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("curl/7.64.1", val_str, val_len) == 0); + + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + EXPECT_TRUE(strncmp("Accept", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("*/*", val_str, val_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +/****************************************************************************** + * uncomplete http request/response and consume meta + ******************************************************************************/ + +#if 1 +TEST(HTTP_DECODER_TEST, uncomplete_post_request_and_consume_meta) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + int next_header = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + for (size_t i = 0; i < strlen(request_post); i++) + { + EXPECT_TRUE(http_decoder_dispatch(decoder, request_post + i, 1) != -2); + + if (http_decoder_status(decoder) >= ON_URI) + { + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + } + + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + } + + if (http_decoder_status(decoder) >= ON_URI) + { + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("POST", key_str, key_len) == 0); + } + + if (http_decoder_status(decoder) >= ON_URI_COMPLETE) + { + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + } + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + if (http_decoder_status(decoder) >= ON_BODY) + { + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("1", key_str, key_len) == 0); + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + if (next_header == 0) + { + EXPECT_TRUE(strncmp("connection", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("close", val_str, val_len) == 0); + + next_header++; + } + else if (next_header == 1) + { + EXPECT_TRUE(strncmp("content-length", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("1", val_str, val_len) == 0); + } + } + + http_decoder_dump(decoder); + http_decoder_remove(decoder); + } + http_decoder_dump(decoder); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, uncomplete_get_request_and_consume_meta) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + int next_header = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + for (size_t i = 0; i < strlen(request_get); i++) + { + EXPECT_TRUE(http_decoder_dispatch(decoder, request_get + i, 1) != -2); + + if (http_decoder_status(decoder) >= ON_URI) + { + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_REQUEST); + } + + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == -1); // unused + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + } + + if (http_decoder_status(decoder) >= ON_URI) + { + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("GET", key_str, key_len) == 0); + } + + if (http_decoder_status(decoder) >= ON_URI_COMPLETE) + { + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("/index.html", key_str, key_len) == 0); + } + + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + if (next_header == 0) + { + EXPECT_TRUE(strncmp("Host", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("www.baidu.com", val_str, val_len) == 0); + + next_header++; + } + else if (next_header == 1) + { + EXPECT_TRUE(strncmp("User-Agent", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("curl/7.64.1", val_str, val_len) == 0); + + next_header++; + } + else if (next_header == 2) + { + EXPECT_TRUE(strncmp("Accept", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("*/*", val_str, val_len) == 0); + } + } + + http_decoder_dump(decoder); + http_decoder_remove(decoder); + } + http_decoder_dump(decoder); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, uncomplete_response_and_consume_meta) +{ + char *key_str = NULL; + char *val_str = NULL; + size_t key_len = 0; + size_t val_len = 0; + int iter_index = 0; + int next_header = 0; + + char body_data[] = "hello:world"; + int body_offset = 0; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + for (size_t i = 0; i < strlen(response_200); i++) + { + EXPECT_TRUE(http_decoder_dispatch(decoder, response_200 + i, 1) != -2); + + if (http_decoder_status(decoder) >= ON_URI) + { + EXPECT_TRUE(http_decoder_fetch_dir(decoder) == HTTP_DIR_RESPONSE); + } + + if (http_decoder_status(decoder) >= ON_STATUS_COMPLETE) + { + EXPECT_TRUE(http_decoder_fetch_status_code(decoder) == 200); + http_decoder_fetch_status(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("OK", key_str, key_len) == 0); + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_major_version(decoder) == 1); + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + EXPECT_TRUE(http_decoder_fetch_minor_version(decoder) == 1); + } + + http_decoder_fetch_method(decoder, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + http_decoder_fetch_uri(decoder, &key_str, &key_len); + EXPECT_TRUE(key_str == NULL && key_len == 0); + + if (http_decoder_status(decoder) >= ON_BODY) + { + http_decoder_fetch_body(decoder, &key_str, &key_len); + if (key_str) + { + EXPECT_TRUE(strncmp(body_data + body_offset, key_str, key_len) == 0); + body_offset += key_len; + } + } + + if (http_decoder_status(decoder) >= ON_HEADER_FIELD) + { + http_decoder_fetch_next_header(decoder, &iter_index, &key_str, &key_len, &val_str, &val_len); + if (next_header == 0) + { + EXPECT_TRUE(strncmp("Server", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("nginx", val_str, val_len) == 0); + + next_header++; + } + else if (next_header == 1) + { + EXPECT_TRUE(strncmp("content-length", key_str, key_len) == 0); + EXPECT_TRUE(strncmp("11", val_str, val_len) == 0); + } + } + + http_decoder_dump(decoder); + http_decoder_remove(decoder); + } + http_decoder_dump(decoder); + + http_decoder_destory(decoder); +} +#endif + +/****************************************************************************** + * error test + ******************************************************************************/ + +#if 1 +TEST(HTTP_DECODER_TEST, error_test_without_http_version) +{ + const char *request_part_1 = "POST /index.html "; + const char *request_part_2 = "\r\nconnection:close\r\ncontent-length: 1\r\n\r\n1\r\n\r\n"; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + EXPECT_TRUE(http_decoder_dispatch(decoder, request_part_1, strlen(request_part_1)) != -2); + http_decoder_dump(decoder); + + // error occur + EXPECT_TRUE(http_decoder_dispatch(decoder, request_part_2, strlen(request_part_2)) == -2); + http_decoder_reset(decoder); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, error_test_without_content_length) +{ + char *key_str = NULL; + size_t key_len = 0; + const char *without_content_length = "POST /index.html HTTP/1.1\r\nconnection:close\r\n\r\n12345\r\n\r\n"; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + EXPECT_TRUE(http_decoder_dispatch(decoder, without_content_length, strlen(without_content_length)) != -2); + http_decoder_dump(decoder); + + // no content-length, so skip parsing the request body + EXPECT_TRUE(http_decoder_status(decoder) == ON_MESSAGE_COMPLETE); + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(NULL == key_str && key_len == 0); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +// escape vulnerability +TEST(HTTP_DECODER_TEST, error_test_content_length_too_small) +{ + char *key_str = NULL; + size_t key_len = 0; + const char *content_length_too_small = "POST /index.html HTTP/1.1\r\nconnection:close\r\ncontent-length: 2\r\n\r\n12345\r\n\r\n"; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + EXPECT_TRUE(http_decoder_dispatch(decoder, content_length_too_small, strlen(content_length_too_small)) != -2); + http_decoder_dump(decoder); + + // only parse data of the length specified by content-length + EXPECT_TRUE(http_decoder_status(decoder) == ON_MESSAGE_COMPLETE); + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("12", key_str, key_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +#if 1 +TEST(HTTP_DECODER_TEST, error_test_content_length_too_big) +{ + char *key_str = NULL; + size_t key_len = 0; + const char *content_length_too_big = "POST /index.html HTTP/1.1\r\nconnection:close\r\ncontent-length: 20\r\n\r\n12345\r\n\r\n"; + const char *last_body = "12345678901"; + + struct http_decoder *decoder = http_decoder_create(); + http_decoder_init(decoder); + + // parser content_length_too_big + EXPECT_TRUE(http_decoder_dispatch(decoder, content_length_too_big, strlen(content_length_too_big)) != -2); + http_decoder_dump(decoder); + + EXPECT_TRUE(http_decoder_status(decoder) == ON_BODY); + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp("12345\r\n\r\n", key_str, key_len) == 0); + + // parser last_body + http_decoder_remove(decoder); + EXPECT_TRUE(http_decoder_dispatch(decoder, last_body, strlen(last_body)) != -2); + http_decoder_dump(decoder); + + EXPECT_TRUE(http_decoder_status(decoder) == ON_MESSAGE_COMPLETE); + http_decoder_fetch_body(decoder, &key_str, &key_len); + EXPECT_TRUE(strncmp(last_body, key_str, key_len) == 0); + + http_decoder_destory(decoder); +} +#endif + +int main(int argc, char **argv) +{ + int ret = 0; + ::testing::InitGoogleTest(&argc, argv); + ret = RUN_ALL_TESTS(); + return ret; +} \ No newline at end of file diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index 6799ed0..971eb17 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -18,4 +18,18 @@ set_property(TARGET gtest PROPERTY INTERFACE_LINK_LIBRARIES pthread) add_library(gmock STATIC IMPORTED GLOBAL) add_dependencies(gmock googletest) set_property(TARGET gmock PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libgmock.a) -set_property(TARGET gmock PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) \ No newline at end of file +set_property(TARGET gmock PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) + +# llhttp +ExternalProject_Add(llhttp PREFIX llhttp + URL ${CMAKE_CURRENT_SOURCE_DIR}/llhttp-release-v6.0.9.tar.gz + URL_MD5 3f6e9a3837e4ec6998f809efcd733170 + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_STATIC_LIBS=ON) + +ExternalProject_Get_Property(llhttp INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(llhttp-static STATIC IMPORTED GLOBAL) +add_dependencies(llhttp-static llhttp) +set_property(TARGET llhttp-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib64/libllhttp.a) +set_property(TARGET llhttp-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) \ No newline at end of file diff --git a/vendor/llhttp-release-v6.0.9.tar.gz b/vendor/llhttp-release-v6.0.9.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f6155cf8e0226633e36594b4f610066b80019097 GIT binary patch literal 41388 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 literal 0 HcmV?d00001