Refactor "print request headers"
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 2s

Resolves #1
This commit is contained in:
Stephen Seo 2024-09-09 12:39:47 +09:00
parent 2791714f2c
commit 3bace3286f
10 changed files with 171 additions and 83 deletions

View file

@ -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"

View file

@ -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 \

View file

@ -8,6 +8,9 @@ A simple HTTP/1.1 server written in C.
-p <port> | --port <port>
--config=<config_file>
--disable-peer-addr-print
--req-header-to-print=<header> (can be used multiple times)
For example: --req-header-to-print=User-Agent
Note that this option is case-insensitive
## Before Compiling

View file

@ -28,6 +28,7 @@ void print_usage(void) {
puts(" --disable-peer-addr-print");
puts(" --req-header-to-print=<header> (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) {

44
src/helpers.c Normal file
View file

@ -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 <stdlib.h>
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

32
src/helpers.h Normal file
View file

@ -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 <stddef.h>
/// 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

View file

@ -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') {
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') {
flags &= 0xFFFFFFFE;
}
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

View file

@ -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

View file

@ -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;

View file

@ -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()