diff --git a/CMakeLists.txt b/CMakeLists.txt index 4557af1..0183e67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(c_simple_http_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/http.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/config.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/http_template.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/helpers.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/helpers.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/linked_list.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/hash_map.c" diff --git a/Makefile b/Makefile index 5300ea1..98d5749 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ HEADERS = \ src/constants.h \ src/http.h \ src/config.h \ - src/http_template.h + src/http_template.h \ + src/helpers.h SOURCES = \ src/main.c \ @@ -32,6 +33,7 @@ SOURCES = \ src/http.c \ src/config.c \ src/http_template.c \ + src/helpers.c \ third_party/SimpleArchiver/src/helpers.c \ third_party/SimpleArchiver/src/data_structures/linked_list.c \ third_party/SimpleArchiver/src/data_structures/hash_map.c \ diff --git a/README.md b/README.md index ca5969b..605f7ad 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ A simple HTTP/1.1 server written in C. -p | --port --config= --disable-peer-addr-print + --req-header-to-print=
(can be used multiple times) + For example: --req-header-to-print=User-Agent + Note that this option is case-insensitive ## Before Compiling diff --git a/src/arg_parse.c b/src/arg_parse.c index 4e07d33..437d9c5 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -28,6 +28,7 @@ void print_usage(void) { puts(" --disable-peer-addr-print"); puts(" --req-header-to-print=
(can be used multiple times)"); puts(" For example: --req-header-to-print=User-Agent"); + puts(" Note that this option is case-insensitive"); } Args parse_args(int argc, char **argv) { diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..c8e49d2 --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,44 @@ +// ISC License +// +// Copyright (c) 2024 Stephen Seo +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "helpers.h" + +// Standard library includes. +#include + +void c_simple_http_helper_to_lowercase_in_place(char *buf, size_t size) { + for (size_t idx = 0; idx < size; ++idx) { + if (buf[idx] >= 'A' && buf[idx] <= 'Z') { + buf[idx] += 32; + } + } +} + +char *c_simple_http_helper_to_lowercase(const char *buf, size_t size) { + char *ret_buf = malloc(size + 1); + for (size_t idx = 0; idx < size; ++idx) { + if (buf[idx] >= 'A' && buf[idx] <= 'Z') { + ret_buf[idx] = buf[idx] + 32; + } else { + ret_buf[idx] = buf[idx]; + } + } + + ret_buf[size] = 0; + return ret_buf; +} + +// vim: et ts=2 sts=2 sw=2 diff --git a/src/helpers.h b/src/helpers.h new file mode 100644 index 0000000..c6743fb --- /dev/null +++ b/src/helpers.h @@ -0,0 +1,32 @@ +// ISC License +// +// Copyright (c) 2024 Stephen Seo +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef SEODISPARATE_COM_C_SIMPLE_HTTP_HELPERS_H_ +#define SEODISPARATE_COM_C_SIMPLE_HTTP_HELPERS_H_ + +// Standard library includes. +#include + +/// Modifies "buf" in-place to change all uppercase to lowercase alpha chars. +void c_simple_http_helper_to_lowercase_in_place(char *buf, size_t size); + +/// Returns a c-string that should be free'd after use that has converted all +/// uppercase to lowercase alpha chars. +char *c_simple_http_helper_to_lowercase(const char *buf, size_t size); + +#endif + +// vim: et ts=2 sts=2 sw=2 diff --git a/src/http.c b/src/http.c index d9cb3fd..8213ffe 100644 --- a/src/http.c +++ b/src/http.c @@ -27,6 +27,7 @@ // Local includes #include "constants.h" #include "http_template.h" +#include "helpers.h" #define REQUEST_TYPE_BUFFER_SIZE 16 #define REQUEST_PATH_BUFFER_SIZE 256 @@ -209,75 +210,75 @@ char *c_simple_http_strip_path(const char *path, size_t path_size) { return stripped_path; } -char *c_simple_http_filter_request_header( - const char *request, size_t request_size, const char *header) { - if (!request) { - fprintf(stderr, "ERROR filter_request_header: request is NULL!\n"); - return NULL; - } else if (request_size == 0) { - fprintf(stderr, "ERROR filter_request_header: request_size is zero!\n"); - return NULL; - } else if (!header) { - fprintf(stderr, "ERROR filter_request_header: header is NULL!\n"); - return NULL; - } +SDArchiverHashMap *c_simple_http_request_to_headers_map( + const char *request, size_t request_size) { + SDArchiverHashMap *hash_map = simple_archiver_hash_map_init(); - // xxxx xxx0 - Start of line. - // xxxx xxx1 - In middle of line. - // xxxx xx0x - Header NOT found. - // xxxx xx1x - Header was found. - unsigned int flags = 0; + // xxxx xx00 - Beginning of line. + // xxxx xx01 - Reached end of header key. + // xxxx xx10 - Non-header line. + unsigned int state = 0; size_t idx = 0; - size_t line_start_idx = 0; - size_t header_idx = 0; - for(; idx < request_size && request[idx] != 0; ++idx) { - if ((flags & 1) == 0) { - if (request[idx] == '\n') { + size_t header_key_idx = 0; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *key_buf = NULL; + size_t key_buf_size = 0; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *value_buf = NULL; + for (; idx < request_size; ++idx) { + if ((state & 3) == 0) { + if ((request[idx] >= 'a' && request[idx] <= 'z') + || (request[idx] >= 'A' && request[idx] <= 'Z') + || request[idx] == '-') { continue; - } - line_start_idx = idx; - flags |= 1; - if (request[idx] == header[header_idx]) { - ++header_idx; - if (header[header_idx] == 0) { - flags |= 2; - break; - } + } else if (request[idx] == ':') { + key_buf_size = idx - header_key_idx + 1; + key_buf = malloc(key_buf_size); + memcpy(key_buf, request + header_key_idx, key_buf_size - 1); + key_buf[key_buf_size - 1] = 0; + c_simple_http_helper_to_lowercase_in_place(key_buf, key_buf_size); + state &= 0xFFFFFFFC; + state |= 1; } else { - header_idx = 0; + state &= 0xFFFFFFFC; + state |= 2; } - } else { - if (header_idx != 0) { - if (request[idx] == header[header_idx]) { - ++header_idx; - if (header[header_idx] == 0) { - flags |= 2; - break; - } - } else { - header_idx = 0; - } - } - + } else if ((state & 3) == 1) { if (request[idx] == '\n') { - flags &= 0xFFFFFFFE; + size_t value_buf_size = idx - header_key_idx + 1; + value_buf = malloc(value_buf_size); + memcpy(value_buf, request + header_key_idx, value_buf_size - 1); + value_buf[value_buf_size - 1] = 0; + simple_archiver_hash_map_insert( + hash_map, value_buf, key_buf, key_buf_size, NULL, NULL); + key_buf = NULL; + value_buf = NULL; + key_buf_size = 0; + header_key_idx = idx + 1; + state &= 0xFFFFFFFC; } + } else if ((state & 3) == 2) { + // Do nothing, just wait until '\n' is parsed. + } + + if (request[idx] == '\n') { + header_key_idx = idx + 1; + state &= 0xFFFFFFFC; } } - if ((flags & 2) != 0) { - // Get line end starting from line_start_idx. - for ( - idx = line_start_idx; - idx < request_size && request[idx] != 0 && request[idx] != '\n'; - ++idx); - char *line_buf = malloc(idx - line_start_idx + 1); - memcpy(line_buf, request + line_start_idx, idx - line_start_idx); - line_buf[idx - line_start_idx] = 0; - return line_buf; + if (key_buf && key_buf_size != 0 && !value_buf && idx > header_key_idx) { + size_t value_buf_size = idx - header_key_idx + 1; + value_buf = malloc(value_buf_size); + memcpy(value_buf, request + header_key_idx, value_buf_size - 1); + value_buf[value_buf_size - 1] = 0; + simple_archiver_hash_map_insert( + hash_map, value_buf, key_buf, key_buf_size, NULL, NULL); + key_buf = NULL; + value_buf = NULL; } - return NULL; + return hash_map; } // vim: ts=2 sts=2 sw=2 diff --git a/src/http.h b/src/http.h index 0c6eebe..bee2cf6 100644 --- a/src/http.h +++ b/src/http.h @@ -56,10 +56,12 @@ char *c_simple_http_request_response( /// Must be free'd if returns non-NULL. char *c_simple_http_strip_path(const char *path, size_t path_size); -/// Returns a line from the request that starts with the "header" C-string. -/// If returns non-NULL, must be free'd. -char *c_simple_http_filter_request_header( - const char *request, size_t request_size, const char *header); +/// Returns non-NULL if successful. Must be freed with +/// simple_archiver_hash_map_free if non-NULL. +/// The map is a mapping of lowercase header names to header lines. +/// E.g. "user-agent" -> "User-Agent: curl". +SDArchiverHashMap *c_simple_http_request_to_headers_map( + const char *request, size_t request_size); #endif diff --git a/src/main.c b/src/main.c index 4c84bc6..b79de09 100644 --- a/src/main.c +++ b/src/main.c @@ -38,10 +38,10 @@ #include "globals.h" #include "constants.h" #include "http.h" +#include "helpers.h" typedef struct C_SIMPLE_HTTP_INTERNAL_Header_Check_Ctx { - const unsigned char *recv_buf; - ssize_t recv_buf_size; + SDArchiverHashMap *headers_map; } C_SIMPLE_HTTP_INTERNAL_Header_Check_Ctx; #define CHECK_ERROR_WRITE(write_expr) \ @@ -56,10 +56,12 @@ int c_simple_http_headers_check_print(void *data, void *ud) { const char *header_c_str = data; __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) - char *matching_line = c_simple_http_filter_request_header( - (const char*)ctx->recv_buf, - (size_t)ctx->recv_buf_size, - header_c_str); + char *header_c_str_lowercase = c_simple_http_helper_to_lowercase( + header_c_str, strlen(header_c_str) + 1); + char *matching_line = simple_archiver_hash_map_get( + ctx->headers_map, + header_c_str_lowercase, + strlen(header_c_str) + 1); if (matching_line) { printf("Printing header line: %s\n", matching_line); } @@ -168,12 +170,14 @@ int main(int argc, char **argv) { #endif { C_SIMPLE_HTTP_INTERNAL_Header_Check_Ctx ctx; - ctx.recv_buf = recv_buf; - ctx.recv_buf_size = read_ret; + ctx.headers_map = c_simple_http_request_to_headers_map( + (const char*)recv_buf, + (size_t)read_ret); simple_archiver_list_get( args.list_of_headers_to_log, c_simple_http_headers_check_print, &ctx); + simple_archiver_hash_map_free(&ctx.headers_map); } size_t response_size = 0; diff --git a/src/test.c b/src/test.c index 2cb3e8f..bff1ce0 100644 --- a/src/test.c +++ b/src/test.c @@ -447,21 +447,19 @@ int main(void) { // Test http. { - const char *request = - "GET /path HTTP/1.1\nA-Header: Something\nAnother-Header: Other\n" - "Different-Header: Different\n"; - size_t request_size = 92; + __attribute((cleanup(simple_archiver_hash_map_free))) + SDArchiverHashMap *headers_map = c_simple_http_request_to_headers_map( + "GET / HTTP/1.1\nUser-Agent: Blah\nHost: some host", 47); + ASSERT_TRUE(headers_map); - __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) - char *header_line_buf = c_simple_http_filter_request_header( - request, request_size, "Another-Header"); - ASSERT_TRUE(header_line_buf); - ASSERT_STREQ(header_line_buf, "Another-Header: Other"); + const char *ret = + simple_archiver_hash_map_get(headers_map, "user-agent", 11); + ASSERT_TRUE(ret); + CHECK_STREQ(ret, "User-Agent: Blah"); - simple_archiver_helper_cleanup_c_string(&header_line_buf); - header_line_buf = c_simple_http_filter_request_header( - request, request_size, "Non-Existant-Header"); - ASSERT_FALSE(header_line_buf); + ret = simple_archiver_hash_map_get(headers_map, "host", 5); + ASSERT_TRUE(ret); + CHECK_STREQ(ret, "Host: some host"); } RETURN()