├── .gitattributes ├── .gitignore ├── include ├── hs_external_libs.h ├── hs_routes_payload_limit.h ├── hs_routes_favicon.h ├── hs_routes_error.h ├── hs_routes_powered_by.h ├── hs_routes.h ├── hs_openssl.h ├── hs_routes_common.h ├── hs.h ├── hs_routes_ratelimit.h ├── hs_socket.h ├── hs_routes_redirection.h ├── hs_routes_static.h ├── hs_routes_security.h ├── hs_types_array.h ├── hs_route.h ├── hs_types_cookie.h ├── hs_io.h ├── hs_router.h ├── hs_parser.h ├── hs_routes_fs.h ├── hs_routes_session.h ├── hs_server.h └── hs_types.h ├── .editorconfig ├── post-example-build.cmake ├── tests ├── test_io_read_line_no_buffer.c ├── test_io_read_line_no_socket.c ├── test.h ├── test_hs_routes_session_route_generate_cookie_id.c ├── test_hs_routes_session_new_session.c ├── test_types_array_string_pair_release.c ├── test_types_release_cookie.c ├── test_route_release_route.c ├── test_types_release_post_response_callback.c ├── test_hs_routes_error_404_not_found_route_serve.c ├── test_types_release_post_response_callbacks.c ├── test_constants_file_extension_to_mime_type.c ├── test_parser_parse_protocol_from_url.c ├── test_parser_parse_method.c ├── test_types_release_http_response.c ├── test_router_release.c ├── test_parser_parse_header.c ├── test_hs_routes_error_411_length_required_route_serve.c ├── test_hs_routes_favicon_serve.c ├── test_router_remote_path_prefix.c ├── test_hs_routes_session_route_session_read_write_file_based_storage.c ├── test.c ├── test_io_write_string_to_socket.c ├── test_parser_parse_request_line.c ├── test_types_release_http_request.c ├── test_hs_routes_session_route_session_to_string.c ├── test_hs_routes_powered_by_route_serve.c ├── test_route_is_allowed_for_method.c ├── test_types_release_cookies.c ├── test_hs_routes_payload_limit_route_serve.c ├── test_hs_routes_security_basic_auth_serve.c ├── test_io_write_file_to_socket.c ├── test_types_http_request_payload_to_string.c ├── test_hs_routes_ratelimit_max_connection_time_route_serve.c ├── test_io_read_line.c ├── test_types_http_request_payload_to_file.c ├── test_hs_routes_redirection_serve.c ├── test_hs_routes_session_route_session_from_string.c ├── test_types_array_string_pair_mutations.c ├── test_hs_routes_session_route_serve.c ├── test_route_is_supported_path.c ├── test_hs_routes_security_headers_serve.c ├── test_types_release_serve_flow_params.c ├── test_router_serve_binary_file.c ├── test_parser_parse_query_string.c ├── test_parser_parse_request.c ├── test_hs_routes_static_serve.c ├── test_parser_parse_cookie_header.c ├── test_router_write_common_response_header.c ├── test_hs_routes_ratelimit_max_connection_requests_route_serve.c ├── test_router_serve_text_file.c ├── test_router_as_route.c ├── test_router_serve_text.c ├── test_parser_create_request_from_url.c ├── test_router_serve_multi_routes.c ├── test_router_serve_next.c └── test_router_serve_forever.c ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── src ├── hs_routes_common.c ├── hs_routes_powered_by.c ├── hs_routes_payload_limit.c ├── hs_routes_error.c ├── hs_openssl.c ├── hs_routes_static.c ├── hs_routes_favicon.c ├── hs_routes_redirection.c ├── hs_routes_ratelimit.c ├── hs_route.c ├── hs_types_cookie.c ├── hs_types_array.c └── hs_io.c ├── examples ├── cert.pem └── key.pem ├── CMakeLists.txt ├── CHANGELOG.md └── uncrustify.cfg /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.ico binary 3 | *.woff binary 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | temp/ 3 | **/*.log 4 | /core 5 | /sessions 6 | 7 | -------------------------------------------------------------------------------- /include/hs_external_libs.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_EXTERNAL_LIBS_H 2 | #define HS_EXTERNAL_LIBS_H 3 | 4 | #include "hashtable.h" 5 | 6 | #endif 7 | 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | -------------------------------------------------------------------------------- /post-example-build.cmake: -------------------------------------------------------------------------------- 1 | 2 | include("./cmake-modules/src/utils.cmake") 3 | 4 | utils_embed_example_source_in_readme( 5 | EXAMPLE_FILE "../examples/example.c" 6 | DOCUMENT_FILE "../README.md" 7 | SOURCE_TYPE "c" 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /include/hs_routes_payload_limit.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_PAYLOAD_LIMIT_H 2 | #define HS_ROUTES_PAYLOAD_LIMIT_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Enables to limit the payload size. 8 | * Requests with content length which is bigger will get rejected 9 | * with a 413 error. 10 | */ 11 | struct HSRoute *hs_routes_payload_limit_route_new(size_t); 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /tests/test_io_read_line_no_buffer.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSSocket *socket = hs_socket_plain_new(1); 7 | char *line = hs_io_read_line(socket, NULL); 8 | 9 | hs_socket_close_and_release(socket); 10 | 11 | assert_true(line == NULL); 12 | } 13 | 14 | 15 | int main() 16 | { 17 | test_run(test_impl); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /include/hs_routes_favicon.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_FAVICON_H 2 | #define HS_ROUTES_FAVICON_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Returns a new route which serves the provided file as the favicon. 8 | * The provided string will be released with the route. 9 | */ 10 | struct HSRoute *hs_routes_favicon_route_new(char * /* file path */, int /* max age in seconds */); 11 | 12 | #endif 13 | 14 | -------------------------------------------------------------------------------- /tests/test_io_read_line_no_socket.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct StringBuffer *buffer = stringbuffer_new(); 7 | char *line = hs_io_read_line(0, buffer); 8 | 9 | assert_true(line == NULL); 10 | assert_true(stringbuffer_is_empty(buffer)); 11 | 12 | stringbuffer_release(buffer); 13 | } 14 | 15 | 16 | int main() 17 | { 18 | test_run(test_impl); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /include/hs_routes_error.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_ERROR_H 2 | #define HS_ROUTES_ERROR_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Simple route which returns 404 when invoked. 8 | */ 9 | struct HSRoute *hs_routes_error_404_not_found_route_new(void); 10 | 11 | /** 12 | * Validates content length header is provided and if not returns 411. 13 | */ 14 | struct HSRoute *hs_routes_error_411_length_required_route_new(void); 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Feature Description 11 | 12 | 13 | ### Describe The Solution You'd Like 14 | 15 | 16 | ### Code Sample 17 | 18 | ```c 19 | // paste code here 20 | ``` 21 | -------------------------------------------------------------------------------- /include/hs_routes_powered_by.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_POWERED_BY_H 2 | #define HS_ROUTES_POWERED_BY_H 3 | 4 | #include "hs_route.h" 5 | 6 | #define HS_ROUTES_POWERED_BY "CHS" 7 | 8 | /** 9 | * Adds the X-Powered-By response header. 10 | * If no value is provided, the default HS server value is returned. 11 | * The provided value will not be released when the route is released. 12 | */ 13 | struct HSRoute *hs_routes_powered_by_route_new(char *); 14 | 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_H__ 2 | #define __TEST_H__ 3 | 4 | #include "hs.h" 5 | #include "hs_io.h" 6 | #include "hs_socket.h" 7 | #include 8 | #include 9 | 10 | #define TEST_BINARY_FILE "./temp_test_file.bin" 11 | 12 | void test_run(void (*fn)()); 13 | void test_fail(); 14 | void assert_true(bool); 15 | 16 | void assert_num_equal(size_t, size_t); 17 | void assert_string_equal(char *, char *); 18 | 19 | void test_generate_binary_file(); 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Describe The Bug 11 | 12 | 13 | ### To Reproduce 14 | 15 | 16 | ### Error Stack 17 | 18 | ```console 19 | The error stack trace 20 | ``` 21 | 22 | ### Code Sample 23 | 24 | ```c 25 | // paste code here 26 | ``` 27 | -------------------------------------------------------------------------------- /include/hs_routes.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_H 2 | #define HS_ROUTES_H 3 | 4 | #include "hs_route.h" 5 | #include "hs_routes_common.h" 6 | #include "hs_routes_error.h" 7 | #include "hs_routes_favicon.h" 8 | #include "hs_routes_fs.h" 9 | #include "hs_routes_payload_limit.h" 10 | #include "hs_routes_powered_by.h" 11 | #include "hs_routes_ratelimit.h" 12 | #include "hs_routes_redirection.h" 13 | #include "hs_routes_security.h" 14 | #include "hs_routes_session.h" 15 | #include "hs_routes_static.h" 16 | 17 | #endif 18 | 19 | -------------------------------------------------------------------------------- /src/hs_routes_common.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_routes_common.h" 3 | 4 | 5 | void hs_routes_common_extension_release(struct HSRoute *route) 6 | { 7 | if (route == NULL) 8 | { 9 | return; 10 | } 11 | 12 | hs_io_free(route->extension); 13 | } 14 | 15 | struct HSRoute *hs_routes_common_serve_all_route_new(void) 16 | { 17 | struct HSRoute *route = hs_route_new(); 18 | 19 | hs_route_set_all_methods(route, true); 20 | route->is_parent_path = true; 21 | 22 | return(route); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_route_generate_cookie_id.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | char *id1 = hs_routes_session_route_generate_cookie_id(NULL); 8 | 9 | assert_true(id1 != NULL); 10 | char *id2 = hs_routes_session_route_generate_cookie_id(NULL); 11 | assert_true(id2 != NULL); 12 | assert_true(!stringfn_equal(id1, id2)); 13 | 14 | hs_io_free(id1); 15 | hs_io_free(id2); 16 | } /* test_impl */ 17 | 18 | 19 | int main() 20 | { 21 | test_run(test_impl); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_new_session.c: -------------------------------------------------------------------------------- 1 | #include "hs_external_libs.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | 6 | 7 | void test_impl() 8 | { 9 | struct HSSession *session = hs_routes_session_new_session(); 10 | 11 | hs_routes_session_release_session(session); 12 | 13 | session = hs_routes_session_new_session(); 14 | hashtable_insert(session->data, "key", "value", NULL); 15 | hs_routes_session_release_session(session); 16 | } /* test_impl */ 17 | 18 | 19 | int main() 20 | { 21 | test_run(test_impl); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /include/hs_openssl.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_OPENSSL_H 2 | #define HS_OPENSSL_H 3 | 4 | #include 5 | 6 | /** 7 | * This is an internal header and should not be used outside the library. 8 | */ 9 | 10 | bool hs_openssl_supported(void); 11 | 12 | #ifdef HS_SSL_SUPPORTED 13 | #include 14 | 15 | void hs_openssl_init(void); 16 | void hs_openssl_cleanup(void); 17 | SSL_CTX *hs_openssl_context_create(char * /* private_key_pem_file */, char * /* certificate_pem_file */); 18 | void hs_openssl_context_release(SSL_CTX *); 19 | 20 | #endif 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /include/hs_routes_common.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_COMMON_H 2 | #define HS_ROUTES_COMMON_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Simply common utility for routes to have a simple release function that 8 | * does free on the extension member. 9 | */ 10 | void hs_routes_common_extension_release(struct HSRoute *); 11 | 12 | /** 13 | * Returns a route that is setup to serve all methods and all paths. 14 | * This utility is good for middlewares that modify requests/responses. 15 | */ 16 | struct HSRoute *hs_routes_common_serve_all_route_new(void); 17 | 18 | #endif 19 | 20 | -------------------------------------------------------------------------------- /include/hs.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_H 2 | #define HS_H 3 | 4 | /** 5 | * This is the top level header which includes all other headers. 6 | * It can be used to easily import all public capabilities of this library. 7 | * Headers that are not included in this file should be considered internal 8 | * and should not be included directly. 9 | */ 10 | 11 | #include "hs_constants.h" 12 | #include "hs_external_libs.h" 13 | #include "hs_parser.h" 14 | #include "hs_route.h" 15 | #include "hs_router.h" 16 | #include "hs_routes.h" 17 | #include "hs_server.h" 18 | #include "hs_socket.h" 19 | #include "hs_types.h" 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /tests/test_types_array_string_pair_release.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSArrayStringPair *array = hs_types_array_string_pair_new(); 8 | 9 | hs_types_array_string_pair_release(array); 10 | 11 | array = hs_types_array_string_pair_new(); 12 | 13 | for (size_t index = 0; index < 10; index++) 14 | { 15 | hs_types_array_string_pair_add(array, stringfn_new_empty_string(), stringfn_new_empty_string()); 16 | } 17 | 18 | hs_types_array_string_pair_release(array); 19 | } 20 | 21 | 22 | int main() 23 | { 24 | test_run(test_impl); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /tests/test_types_release_cookie.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSCookie *cookie = hs_types_cookie_new(); 8 | 9 | hs_types_cookie_release(cookie); 10 | 11 | cookie = hs_types_cookie_new(); 12 | 13 | cookie->name = stringfn_new_empty_string(); 14 | cookie->value = stringfn_new_empty_string(); 15 | cookie->expires = stringfn_new_empty_string(); 16 | cookie->domain = stringfn_new_empty_string(); 17 | cookie->path = stringfn_new_empty_string(); 18 | 19 | hs_types_cookie_release(cookie); 20 | } 21 | 22 | 23 | int main() 24 | { 25 | test_run(test_impl); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/test_route_release_route.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | size_t count = 0; 5 | 6 | 7 | void _test_release_fn(struct HSRoute *route) 8 | { 9 | assert_true(route != NULL); 10 | count = count + 1; 11 | } 12 | 13 | 14 | void test_impl() 15 | { 16 | struct HSRoute *route = hs_route_new(); 17 | 18 | hs_route_release_route(route); 19 | 20 | route = hs_route_new(); 21 | route->path = stringfn_new_empty_string(); 22 | route->release = _test_release_fn; 23 | 24 | hs_route_release_route(route); 25 | 26 | assert_num_equal(count, 1); 27 | } 28 | 29 | 30 | int main() 31 | { 32 | test_run(test_impl); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/test_types_release_post_response_callback.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void _test_release_callback(struct HSPostResponseCallback *callback) 6 | { 7 | hs_io_free(callback->context); 8 | } 9 | 10 | 11 | void test_impl() 12 | { 13 | struct HSPostResponseCallback *callback = hs_types_post_response_callback_new(); 14 | 15 | hs_types_post_response_callback_release(callback); 16 | 17 | callback = hs_types_post_response_callback_new(); 18 | callback->context = stringfn_new_empty_string(); 19 | callback->release = _test_release_callback; 20 | 21 | hs_types_post_response_callback_release(callback); 22 | } 23 | 24 | 25 | int main() 26 | { 27 | test_run(test_impl); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /tests/test_hs_routes_error_404_not_found_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSRoute *route = hs_routes_error_404_not_found_route_new(); 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | 10 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 11 | enum HSServeFlowResponse response = route->serve(route, params); 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 13 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_NOT_FOUND); 14 | 15 | hs_types_serve_flow_params_release(params); 16 | hs_route_release_route(route); 17 | } /* test_impl */ 18 | 19 | 20 | int main() 21 | { 22 | test_run(test_impl); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /tests/test_types_release_post_response_callbacks.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void _test_release(struct HSPostResponseCallback *callback) 6 | { 7 | hs_io_free(callback->context); 8 | } 9 | 10 | 11 | void test_impl() 12 | { 13 | struct HSPostResponseCallbacks *callbacks = hs_types_post_response_callbacks_new(1); 14 | 15 | for (size_t index = 0; index < 10; index++) 16 | { 17 | struct HSPostResponseCallback *callback = hs_types_post_response_callback_new(); 18 | callback->context = stringfn_new_empty_string(); 19 | callback->release = _test_release; 20 | hs_types_post_response_callbacks_add(callbacks, callback); 21 | } 22 | 23 | hs_types_post_response_callbacks_release(callbacks); 24 | } 25 | 26 | 27 | int main() 28 | { 29 | test_run(test_impl); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /include/hs_routes_ratelimit.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_RATELIMIT_H 2 | #define HS_ROUTES_RATELIMIT_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Sets an upper bound of requests that can be made from the same connection. 8 | * Value smaller than 2 will close the connection after a single request. 9 | */ 10 | struct HSRoute *hs_routes_ratelimit_max_connection_requests_route_new(size_t /* max requests */); 11 | 12 | /** 13 | * Sets an upper bound of the amount of time a connection can stay open since 14 | * it was first created. 15 | * Any request coming after the max time will ensure the connection is closed 16 | * after it is handled. 17 | * Value 0 will close the connection after a single request. 18 | */ 19 | struct HSRoute *hs_routes_ratelimit_max_connection_time_route_new(size_t /* max time in seconds */); 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /tests/test_constants_file_extension_to_mime_type.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | enum HSMimeType mime_type = hs_constants_file_extension_to_mime_type(NULL); 13 | 14 | assert_num_equal(mime_type, HS_MIME_TYPE_APPLICATION_OCTET_STREAM); 15 | 16 | mime_type = hs_constants_file_extension_to_mime_type("test"); 17 | assert_num_equal(mime_type, HS_MIME_TYPE_APPLICATION_OCTET_STREAM); 18 | 19 | mime_type = hs_constants_file_extension_to_mime_type("test.html"); 20 | assert_num_equal(mime_type, HS_MIME_TYPE_TEXT_HTML); 21 | 22 | mime_type = hs_constants_file_extension_to_mime_type("test.HTML"); 23 | assert_num_equal(mime_type, HS_MIME_TYPE_TEXT_HTML); 24 | } 25 | 26 | 27 | int main() 28 | { 29 | test_run(test_impl); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /tests/test_parser_parse_protocol_from_url.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | assert_num_equal(hs_parser_parse_protocol_from_url(NULL), HS_HTTP_PROTOCOL_UNKNOWN); 7 | assert_num_equal(hs_parser_parse_protocol_from_url("http"), HS_HTTP_PROTOCOL_UNKNOWN); 8 | assert_num_equal(hs_parser_parse_protocol_from_url("abcd://abc"), HS_HTTP_PROTOCOL_UNKNOWN); 9 | assert_num_equal(hs_parser_parse_protocol_from_url("http://"), HS_HTTP_PROTOCOL_HTTP); 10 | assert_num_equal(hs_parser_parse_protocol_from_url("http://1"), HS_HTTP_PROTOCOL_HTTP); 11 | assert_num_equal(hs_parser_parse_protocol_from_url("HTTP://"), HS_HTTP_PROTOCOL_HTTP); 12 | assert_num_equal(hs_parser_parse_protocol_from_url("https://"), HS_HTTP_PROTOCOL_HTTPS); 13 | assert_num_equal(hs_parser_parse_protocol_from_url("https://1"), HS_HTTP_PROTOCOL_HTTPS); 14 | assert_num_equal(hs_parser_parse_protocol_from_url("HTTPS://"), HS_HTTP_PROTOCOL_HTTPS); 15 | } 16 | 17 | 18 | int main() 19 | { 20 | test_run(test_impl); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/test_parser_parse_method.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | assert_num_equal(hs_parser_parse_method(NULL), HS_HTTP_METHOD_UNKNOWN); 7 | assert_num_equal(hs_parser_parse_method("BAD"), HS_HTTP_METHOD_UNKNOWN); 8 | assert_num_equal(hs_parser_parse_method("GET"), HS_HTTP_METHOD_GET); 9 | assert_num_equal(hs_parser_parse_method("POST"), HS_HTTP_METHOD_POST); 10 | assert_num_equal(hs_parser_parse_method("PUT"), HS_HTTP_METHOD_PUT); 11 | assert_num_equal(hs_parser_parse_method("DELETE"), HS_HTTP_METHOD_DELETE); 12 | assert_num_equal(hs_parser_parse_method("HEAD"), HS_HTTP_METHOD_HEAD); 13 | assert_num_equal(hs_parser_parse_method("CONNECT"), HS_HTTP_METHOD_CONNECT); 14 | assert_num_equal(hs_parser_parse_method("OPTIONS"), HS_HTTP_METHOD_OPTIONS); 15 | assert_num_equal(hs_parser_parse_method("TRACE"), HS_HTTP_METHOD_TRACE); 16 | assert_num_equal(hs_parser_parse_method("PATCH"), HS_HTTP_METHOD_PATCH); 17 | } 18 | 19 | 20 | int main() 21 | { 22 | test_run(test_impl); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Issues 4 | 5 | Found a bug? Got a question? Want some enhancement?
6 | First place to go is the repository issues section, and I'll try to help as much as possible. 7 | 8 | ## Pull Requests 9 | 10 | Fixed a bug or just want to provided additional functionality?
11 | Simply fork this repository, implement your changes and create a pull request.
12 | Few guidelines regarding pull requests: 13 | 14 | * This repository is integrated with github actions for continuous integration.
15 | 16 | Your pull request build must pass (the build will run automatically).
17 | You can run the following command locally to ensure the build will pass: 18 | 19 | ```sh 20 | mkdir ./target 21 | cd ./target 22 | cmake .. 23 | make 24 | ctest -C Release --output-on-failure 25 | ``` 26 | 27 | * There are many automatic unit tests as part of the library which provide full coverage of the functionality.
Any fix/enhancement must come with a set of tests to ensure it's working well. 28 | -------------------------------------------------------------------------------- /tests/test_types_release_http_response.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSHttpResponse *response = hs_types_http_response_new(); 8 | 9 | hs_types_http_response_release(response); 10 | 11 | response = hs_types_http_response_new(); 12 | 13 | for (size_t index = 0; index < 3; index++) 14 | { 15 | struct HSCookie *cookie = hs_types_cookie_new(); 16 | cookie->name = stringfn_new_empty_string(); 17 | cookie->value = stringfn_new_empty_string(); 18 | hs_types_cookies_add(response->cookies, cookie); 19 | } 20 | 21 | for (size_t index = 0; index < 5; index++) 22 | { 23 | hs_types_array_string_pair_add(response->headers, stringfn_new_empty_string(), stringfn_new_empty_string()); 24 | } 25 | 26 | response->content_string = stringfn_new_empty_string(); 27 | response->content_file = stringfn_new_empty_string(); 28 | 29 | hs_types_http_response_release(response); 30 | } 31 | 32 | 33 | int main() 34 | { 35 | test_run(test_impl); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /include/hs_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_SOCKET_H 2 | #define HS_SOCKET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct HSSocket; 9 | 10 | bool hs_socket_is_open(struct HSSocket *); 11 | void hs_socket_close(struct HSSocket *); 12 | void hs_socket_close_and_release(struct HSSocket *); 13 | 14 | ssize_t hs_socket_read(struct HSSocket *, void * /* buffer */, size_t /* count */); 15 | ssize_t hs_socket_write(struct HSSocket *, const void * /* buffer */, size_t /* count */); 16 | bool hs_socket_set_recv_timeout_in_seconds(struct HSSocket *, long); 17 | 18 | struct HSSocket *hs_socket_plain_new(int /* socket */); 19 | struct HSSocket *hs_socket_plain_accept(struct HSSocket *, struct sockaddr *, int /* address size */); 20 | 21 | #ifdef HS_SSL_SUPPORTED 22 | 23 | #include 24 | 25 | struct HSSocket *hs_socket_ssl_new(int /* socket */, SSL_CTX *); 26 | struct HSSocket *hs_socket_ssl_accept(struct HSSocket *, struct sockaddr *, int /* address size */, SSL_CTX *); 27 | 28 | #endif 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /tests/test_router_release.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | size_t count = 0; 5 | 6 | 7 | void _test_release_fn(struct HSRoute *route) 8 | { 9 | assert_true(route != NULL); 10 | count = count + 1; 11 | } 12 | 13 | 14 | void test_impl() 15 | { 16 | struct HSRouter *router = hs_router_new(); 17 | 18 | hs_router_release(router); 19 | 20 | router = hs_router_new(); 21 | struct HSRoute *route = hs_route_new(); 22 | route->path = stringfn_new_empty_string(); 23 | hs_router_add_route(router, route); 24 | route = hs_route_new(); 25 | route->path = stringfn_new_empty_string(); 26 | route->release = _test_release_fn; 27 | route->extension = "do not free"; 28 | hs_router_add_route(router, route); 29 | route = hs_route_new(); 30 | route->path = stringfn_new_empty_string(); 31 | route->release = _test_release_fn; 32 | hs_router_add_route(router, route); 33 | 34 | hs_router_release(router); 35 | assert_num_equal(count, 2); 36 | } 37 | 38 | 39 | int main() 40 | { 41 | test_run(test_impl); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /tests/test_parser_parse_header.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | char **header = hs_parser_parse_header("Content-Type: application/x-www-form-urlencoded"); 7 | 8 | assert_string_equal(header[0], "content-type"); 9 | assert_string_equal(header[1], "application/x-www-form-urlencoded"); 10 | 11 | hs_io_free(header[0]); 12 | hs_io_free(header[1]); 13 | hs_io_free(header); 14 | 15 | header = hs_parser_parse_header("someheader: a b 123"); 16 | 17 | assert_string_equal(header[0], "someheader"); 18 | assert_string_equal(header[1], "a b 123"); 19 | 20 | hs_io_free(header[0]); 21 | hs_io_free(header[1]); 22 | hs_io_free(header); 23 | 24 | header = hs_parser_parse_header("someheader: "); 25 | 26 | assert_string_equal(header[0], "someheader"); 27 | assert_true(header[1] == NULL); 28 | 29 | hs_io_free(header[0]); 30 | hs_io_free(header[1]); 31 | hs_io_free(header); 32 | 33 | header = hs_parser_parse_header("someheader:"); 34 | 35 | assert_true(header == NULL); 36 | } 37 | 38 | 39 | int main() 40 | { 41 | test_run(test_impl); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /include/hs_routes_redirection.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_REDIRECTION_H 2 | #define HS_ROUTES_REDIRECTION_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Returns a new route which simply redirects the request from the path to the requested path. 8 | * The redirection will be using temporary redirect code. 9 | * The from and to paths will be released when the route is released. 10 | */ 11 | struct HSRoute *hs_routes_redirection_route_new(char * /* from path */, char * /* to path */); 12 | 13 | /** 14 | * Returns a new route which simply redirects the request from the path to the requested path. 15 | * The from and to paths will be released when the route is released. 16 | */ 17 | struct HSRoute *hs_routes_redirection_route_new_with_options(char * /* from path */, char * /* to path */, bool /* temporary redirect */); 18 | 19 | /** 20 | * Utility function to set the location header to enable redirection 21 | * and the relevant status code. 22 | */ 23 | bool hs_routes_redirection_set_header_and_status_code(struct HSServeFlowParams *, char * /* to path */, bool /* temporary redirect */); 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /tests/test_hs_routes_error_411_length_required_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSRoute *route = hs_routes_error_411_length_required_route_new(); 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | 10 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 11 | enum HSServeFlowResponse response = route->serve(route, params); 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 13 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_LENGTH_REQUIRED); 14 | 15 | hs_types_array_string_pair_add(params->request->headers, strdup("content-length"), strdup("0")); 16 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 17 | response = route->serve(route, params); 18 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 19 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 20 | 21 | hs_types_serve_flow_params_release(params); 22 | hs_route_release_route(route); 23 | } /* test_impl */ 24 | 25 | 26 | int main() 27 | { 28 | test_run(test_impl); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/hs_routes_powered_by.c: -------------------------------------------------------------------------------- 1 | #include "hs_routes_common.h" 2 | #include "hs_routes_powered_by.h" 3 | #include 4 | 5 | static enum HSServeFlowResponse _hs_routes_powered_by_serve(struct HSRoute *, struct HSServeFlowParams *); 6 | 7 | struct HSRoute *hs_routes_powered_by_route_new(char *powered_by) 8 | { 9 | struct HSRoute *route = hs_routes_common_serve_all_route_new(); 10 | 11 | route->extension = powered_by; 12 | if (route->extension == NULL) 13 | { 14 | route->extension = HS_ROUTES_POWERED_BY; 15 | } 16 | route->serve = _hs_routes_powered_by_serve; 17 | 18 | return(route); 19 | } 20 | 21 | static enum HSServeFlowResponse _hs_routes_powered_by_serve(struct HSRoute *route, struct HSServeFlowParams *params) 22 | { 23 | if (route == NULL || params == NULL || params->response == NULL || params->response->headers == NULL) 24 | { 25 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 26 | } 27 | 28 | char *value = (char *)route->extension; 29 | 30 | hs_types_array_string_pair_add(params->response->headers, strdup("X-Powered-By"), strdup(value)); 31 | 32 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/test_hs_routes_favicon_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | 10 | test_generate_binary_file(); 11 | 12 | struct HSRoute *route = hs_routes_favicon_route_new(strdup(TEST_BINARY_FILE), 60); 13 | enum HSServeFlowResponse response = route->serve(route, params); 14 | 15 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 16 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Cache-Control"), "public, max-age=60"); 17 | assert_string_equal(params->response->content_file, TEST_BINARY_FILE); 18 | hs_route_release_route(route); 19 | 20 | fsio_remove(TEST_BINARY_FILE); 21 | 22 | route = hs_routes_favicon_route_new(strdup(TEST_BINARY_FILE), 60); 23 | response = route->serve(route, params); 24 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 25 | hs_route_release_route(route); 26 | 27 | hs_types_serve_flow_params_release(params); 28 | } /* test_impl */ 29 | 30 | 31 | int main() 32 | { 33 | test_run(test_impl); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /tests/test_router_remote_path_prefix.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | void _test_with_values(char *route_path, char *request_path, char *expected_path) 12 | { 13 | struct HSRoute *route = hs_route_new(); 14 | 15 | route->path = strdup(route_path); 16 | struct HSHttpRequest *request = hs_types_http_request_new(); 17 | request->resource = strdup(request_path); 18 | char *value = hs_router_remove_path_prefix(route, request); 19 | assert_string_equal(value, request_path); 20 | assert_string_equal(request->resource, expected_path); 21 | request->resource = value; 22 | hs_route_release_route(route); 23 | hs_types_http_request_release(request); 24 | } 25 | 26 | 27 | void test_impl() 28 | { 29 | _test_with_values("/admin", "/admin", "/"); 30 | _test_with_values("/admin", "/admin/", "/"); 31 | _test_with_values("/admin/", "/admin/", "/"); 32 | _test_with_values("/admin/", "/admin/index.html", "/index.html"); 33 | } 34 | 35 | 36 | int main() 37 | { 38 | test_run(test_impl); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_route_session_read_write_file_based_storage.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | char *filename = "./sessions/hs_routes_session_route_session_read_from_file_based_storage.txt"; 8 | 9 | char *session_string = hs_routes_session_route_session_read_from_file_based_storage(NULL, NULL); 10 | 11 | assert_true(session_string == NULL); 12 | 13 | fsio_write_text_file(filename, "test 123\nsecond line"); 14 | session_string = hs_routes_session_route_session_read_from_file_based_storage("hs_routes_session_route_session_read_from_file_based_storage.txt", NULL); 15 | assert_string_equal(session_string, "test 123\nsecond line"); 16 | hs_io_free(session_string); 17 | 18 | bool written = hs_routes_session_route_session_write_to_file_based_storage("mysession", "test content", NULL); 19 | assert_true(written); 20 | session_string = hs_routes_session_route_session_read_from_file_based_storage("mysession", NULL); 21 | assert_string_equal(session_string, "test content"); 22 | hs_io_free(session_string); 23 | 24 | fsio_remove("./sessions"); 25 | } /* test_impl */ 26 | 27 | 28 | int main() 29 | { 30 | test_run(test_impl); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | void test_run(void (*fn)(void)) 9 | { 10 | printf("Test ... "); 11 | fn(); 12 | printf("Done\n"); 13 | } 14 | 15 | 16 | void test_fail() 17 | { 18 | printf(" Error\n"); 19 | exit(1); 20 | } 21 | 22 | 23 | void assert_true(bool value) 24 | { 25 | if (!value) 26 | { 27 | test_fail(); 28 | } 29 | } 30 | 31 | 32 | void assert_num_equal(size_t value1, size_t value2) 33 | { 34 | if (value1 != value2) 35 | { 36 | #ifdef linux 37 | printf("Assert Failed, value: %zu not equals to value: %zu", value1, value2); 38 | #endif 39 | test_fail(); 40 | } 41 | } 42 | 43 | 44 | void assert_string_equal(char *value1, char *value2) 45 | { 46 | if (strcmp(value1, value2) != 0) 47 | { 48 | printf("Assert Failed, value: %s not equals to value: %s", value1, value2); 49 | test_fail(); 50 | } 51 | } 52 | 53 | 54 | void test_generate_binary_file() 55 | { 56 | char content[500]; 57 | 58 | for (int index = 0; index < 500; index++) 59 | { 60 | content[index] = index - 100; 61 | } 62 | 63 | fsio_write_binary_file(TEST_BINARY_FILE, content, 500); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /tests/test_io_write_string_to_socket.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | bool done = hs_io_write_string_to_socket(0, "test", 4); 13 | 14 | assert_true(!done); 15 | 16 | struct StringBuffer *buffer = stringbuffer_new(); 17 | 18 | for (size_t index = 0; index < 5000; index++) 19 | { 20 | stringbuffer_append_unsigned_int(buffer, index); 21 | } 22 | char *raw = stringbuffer_to_string(buffer); 23 | stringbuffer_release(buffer); 24 | 25 | char *filename = "./test_io_write_string_to_socket.txt"; 26 | 27 | fsio_create_empty_file(filename); 28 | int socket = open(filename, O_WRONLY); 29 | 30 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 31 | done = hs_io_write_string_to_socket(hssocket, raw, strlen(raw)); 32 | hs_socket_close_and_release(hssocket); 33 | assert_true(done); 34 | 35 | char *text = fsio_read_text_file(filename); 36 | fsio_remove(filename); 37 | 38 | assert_string_equal(text, raw); 39 | hs_io_free(raw); 40 | hs_io_free(text); 41 | } /* test_impl */ 42 | 43 | 44 | int main() 45 | { 46 | test_run(test_impl); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /tests/test_parser_parse_request_line.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSHttpRequest *request = hs_parser_parse_request_line("POST /testpost HTTP/1.0"); 7 | 8 | assert_string_equal(request->domain, ""); 9 | assert_string_equal(request->resource, "/testpost"); 10 | assert_true(!request->ssl); 11 | assert_num_equal(request->port, -1); 12 | assert_num_equal(request->method, HS_HTTP_METHOD_POST); 13 | assert_true(request->query_string == NULL); 14 | 15 | hs_types_http_request_release(request); 16 | 17 | request = hs_parser_parse_request_line("GET /testget HTTP/1.0"); 18 | 19 | assert_string_equal(request->domain, ""); 20 | assert_string_equal(request->resource, "/testget"); 21 | assert_true(!request->ssl); 22 | assert_num_equal(request->port, -1); 23 | assert_num_equal(request->method, HS_HTTP_METHOD_GET); 24 | assert_true(request->query_string == NULL); 25 | 26 | hs_types_http_request_release(request); 27 | 28 | request = hs_parser_parse_request_line("GET /testget HTTP/1.0 a"); 29 | assert_true(request == NULL); 30 | 31 | request = hs_parser_parse_request_line("GET /testget"); 32 | assert_true(request == NULL); 33 | } 34 | 35 | 36 | int main() 37 | { 38 | test_run(test_impl); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /include/hs_routes_static.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_STATIC_H 2 | #define HS_ROUTES_STATIC_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Returns a new route which simply returns the provided content string with the given mime type. 8 | * The content will be released with the route. 9 | */ 10 | struct HSRoute *hs_routes_static_route_new(char * /* content */, enum HSMimeType); 11 | 12 | /** 13 | * Returns a new route which simply returns the provided plain text. 14 | * The content will be released with the route. 15 | */ 16 | struct HSRoute *hs_routes_static_text_route_new(char * /* text */); 17 | 18 | /** 19 | * Returns a new route which simply returns the provided HTML. 20 | * The content will be released with the route. 21 | */ 22 | struct HSRoute *hs_routes_static_html_route_new(char * /* HTML */); 23 | 24 | /** 25 | * Returns a new route which simply returns the provided CSS text. 26 | * The content will be released with the route. 27 | */ 28 | struct HSRoute *hs_routes_static_css_route_new(char * /* css text */); 29 | 30 | /** 31 | * Returns a new route which simply returns the provided JS text. 32 | * The content will be released with the route. 33 | */ 34 | struct HSRoute *hs_routes_static_js_route_new(char * /* js text */); 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /tests/test_types_release_http_request.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSHttpRequest *request = hs_types_http_request_new(); 8 | 9 | hs_types_http_request_release(request); 10 | 11 | request = hs_types_http_request_new(); 12 | 13 | request->domain = stringfn_new_empty_string(); 14 | request->resource = stringfn_new_empty_string(); 15 | request->query_string = stringfn_new_empty_string(); 16 | request->user_agent = stringfn_new_empty_string(); 17 | request->authorization = stringfn_new_empty_string(); 18 | request->payload = hs_types_http_request_new_payload(NULL); 19 | 20 | for (size_t index = 0; index < 3; index++) 21 | { 22 | struct HSCookie *cookie = hs_types_cookie_new(); 23 | cookie->name = stringfn_new_empty_string(); 24 | cookie->value = stringfn_new_empty_string(); 25 | hs_types_cookies_add(request->cookies, cookie); 26 | } 27 | 28 | for (size_t index = 0; index < 5; index++) 29 | { 30 | hs_types_array_string_pair_add(request->headers, stringfn_new_empty_string(), stringfn_new_empty_string()); 31 | } 32 | 33 | hs_types_http_request_release(request); 34 | } 35 | 36 | 37 | int main() 38 | { 39 | test_run(test_impl); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_route_session_to_string.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSSession *session = NULL; 9 | char *session_string = hs_routes_session_route_session_to_string(session); 10 | 11 | assert_true(session_string == NULL); 12 | 13 | session = hs_routes_session_new_session(); 14 | session_string = hs_routes_session_route_session_to_string(session); 15 | assert_string_equal(session_string, ""); 16 | hs_routes_session_release_session(session); 17 | hs_io_free(session_string); 18 | 19 | session = hs_routes_session_new_session(); 20 | hashtable_insert(session->data, "test1", "value1", NULL); 21 | hashtable_insert(session->data, "test2", "value2", NULL); 22 | hashtable_insert(session->data, "test3", "value3", NULL); 23 | session_string = hs_routes_session_route_session_to_string(session); 24 | assert_string_equal(session_string, "[session]\n" 25 | "test1=value1\n" 26 | "test2=value2\n" 27 | "test3=value3\n\n"); 28 | hs_routes_session_release_session(session); 29 | hs_io_free(session_string); 30 | } /* test_impl */ 31 | 32 | 33 | int main() 34 | { 35 | test_run(test_impl); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /tests/test_hs_routes_powered_by_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 7 | 8 | struct HSRoute *route = hs_routes_powered_by_route_new(NULL); 9 | 10 | assert_string_equal((char *)route->extension, "CHS"); 11 | hs_types_array_string_pair_remove_by_key(params->response->headers, "X-Powered-By"); 12 | enum HSServeFlowResponse response = route->serve(route, params); 13 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 14 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Powered-By"), "CHS"); 15 | hs_route_release_route(route); 16 | 17 | route = hs_routes_powered_by_route_new("test"); 18 | assert_string_equal((char *)route->extension, "test"); 19 | hs_types_array_string_pair_remove_by_key(params->response->headers, "X-Powered-By"); 20 | response = route->serve(route, params); 21 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 22 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Powered-By"), "test"); 23 | hs_route_release_route(route); 24 | 25 | hs_types_serve_flow_params_release(params); 26 | } /* test_impl */ 27 | 28 | 29 | int main() 30 | { 31 | test_run(test_impl); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | env: 4 | BUILD_TYPE: Release 5 | jobs: 6 | ci: 7 | name: CI 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | open_ssl: [true, false] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup Env 17 | run: cmake -E make_directory target 18 | - name: Configure 19 | shell: bash 20 | working-directory: target 21 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 22 | env: 23 | X_CMAKE_OPEN_SSL: ${{ matrix.open_ssl }} 24 | - name: Build 25 | shell: bash 26 | working-directory: target 27 | run: cmake --build . --config $BUILD_TYPE 28 | - name: Test 29 | shell: bash 30 | working-directory: target 31 | run: ctest -C $BUILD_TYPE --output-on-failure 32 | - name: Memory Leak Test 33 | if: matrix.os == 'ubuntu-latest' 34 | shell: bash 35 | working-directory: target 36 | run: | 37 | sudo apt update 38 | sudo apt install -y valgrind --fix-missing 39 | for testfile in ./bin/test_*; do echo "Testing ${testfile}" && valgrind --leak-check=full --show-leak-kinds=definite,indirect,possible --error-exitcode=1 "${testfile}"; done 40 | 41 | -------------------------------------------------------------------------------- /include/hs_routes_security.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_SECURITY_H 2 | #define HS_ROUTES_SECURITY_H 3 | 4 | #include "hs_route.h" 5 | 6 | struct HSRoutesSecurityResponseHeaders 7 | { 8 | enum HSXFrameOptionsReponseHeader x_frame_options; 9 | enum HSXContentTypeOptionsResponseHeader x_content_type_options; 10 | enum HSReferrerPolicyResponseHeader referrer_policy; 11 | char *content_security_policy; 12 | char *permissions_policy; 13 | }; 14 | 15 | /** 16 | * Returns a new instance of the security response headers with default values. 17 | */ 18 | struct HSRoutesSecurityResponseHeaders *hs_routes_security_headers_response_headers_new(void); 19 | 20 | /** 21 | * Adds security related response headers. 22 | * If no headers are provided, the default will be used. 23 | * The headers struct and content will be released with the route. 24 | */ 25 | struct HSRoute *hs_routes_security_headers_route_new(struct HSRoutesSecurityResponseHeaders *); 26 | 27 | /** 28 | * Returns a new basic auth route. 29 | * Only the realm value will be released when the route is released. 30 | */ 31 | struct HSRoute *hs_routes_security_basic_auth_route_new(char * /* realm */, bool (*auth)(char * /* base64 auth value */, void * /* context */), void * /* context */); 32 | 33 | #endif 34 | 35 | -------------------------------------------------------------------------------- /tests/test_route_is_allowed_for_method.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSRoute *route = hs_route_new(); 7 | struct HSHttpRequest *request = hs_types_http_request_new(); 8 | 9 | route->is_get = true; 10 | request->method = HS_HTTP_METHOD_GET; 11 | assert_true(hs_route_is_allowed_for_method(route, request)); 12 | route->is_get = false; 13 | assert_true(!hs_route_is_allowed_for_method(route, request)); 14 | 15 | route->is_post = true; 16 | request->method = HS_HTTP_METHOD_POST; 17 | assert_true(hs_route_is_allowed_for_method(route, request)); 18 | route->is_post = false; 19 | assert_true(!hs_route_is_allowed_for_method(route, request)); 20 | 21 | route->is_put = true; 22 | request->method = HS_HTTP_METHOD_PUT; 23 | assert_true(hs_route_is_allowed_for_method(route, request)); 24 | route->is_put = false; 25 | assert_true(!hs_route_is_allowed_for_method(route, request)); 26 | 27 | route->is_delete = true; 28 | request->method = HS_HTTP_METHOD_DELETE; 29 | assert_true(hs_route_is_allowed_for_method(route, request)); 30 | route->is_delete = false; 31 | assert_true(!hs_route_is_allowed_for_method(route, request)); 32 | 33 | hs_route_release_route(route); 34 | hs_types_http_request_release(request); 35 | } 36 | 37 | 38 | int main() 39 | { 40 | test_run(test_impl); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /tests/test_types_release_cookies.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSCookies *cookies = hs_types_cookies_new(); 9 | 10 | hs_types_cookies_release(cookies); 11 | 12 | cookies = hs_types_cookies_new(); 13 | 14 | struct HSCookie *cookie = NULL; 15 | for (size_t index = 0; index < 5; index++) 16 | { 17 | cookie = hs_types_cookie_new(); 18 | cookie->name = stringfn_new_empty_string(); 19 | cookie->value = stringfn_new_empty_string(); 20 | hs_types_cookies_add(cookies, cookie); 21 | } 22 | 23 | cookie = hs_types_cookie_new(); 24 | cookie->name = strdup("test"); 25 | cookie->value = stringfn_new_empty_string(); 26 | hs_types_cookies_add(cookies, cookie); 27 | 28 | for (size_t index = 0; index < 5; index++) 29 | { 30 | cookie = hs_types_cookie_new(); 31 | cookie->name = stringfn_new_empty_string(); 32 | cookie->value = stringfn_new_empty_string(); 33 | hs_types_cookies_add(cookies, cookie); 34 | } 35 | 36 | assert_num_equal(11, hs_types_cookies_count(cookies)); 37 | hs_types_cookies_remove_by_name(cookies, "test"); 38 | assert_num_equal(10, hs_types_cookies_count(cookies)); 39 | 40 | hs_types_cookies_release(cookies); 41 | } 42 | 43 | 44 | int main() 45 | { 46 | test_run(test_impl); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /tests/test_hs_routes_payload_limit_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSRoute *route = hs_routes_payload_limit_route_new(20); 7 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 8 | 9 | params->request->content_length = 0; 10 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 11 | enum HSServeFlowResponse response = route->serve(route, params); 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 13 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 14 | 15 | params->request->content_length = 20; 16 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 17 | response = route->serve(route, params); 18 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 19 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 20 | 21 | params->request->content_length = 21; 22 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 23 | response = route->serve(route, params); 24 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 25 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_PAYLOAD_TOO_LARGE); 26 | 27 | hs_types_serve_flow_params_release(params); 28 | hs_route_release_route(route); 29 | } /* test_impl */ 30 | 31 | 32 | int main() 33 | { 34 | test_run(test_impl); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /tests/test_hs_routes_security_basic_auth_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | bool _test_auth(char *value, void *context) 7 | { 8 | assert_string_equal("test context", (char *)context); 9 | assert_string_equal("test value", value); 10 | 11 | return(true); 12 | } 13 | 14 | 15 | void test_impl() 16 | { 17 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 18 | struct HSRoute *route = hs_routes_security_basic_auth_route_new(strdup("Test Realm"), _test_auth, "test context"); 19 | enum HSServeFlowResponse response = route->serve(route, params); 20 | 21 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 22 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_UNAUTHORIZED); 23 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "WWW-Authenticate"), "Basic realm=\"Test Realm\""); 24 | hs_types_serve_flow_params_release(params); 25 | 26 | params = hs_types_serve_flow_params_new(); 27 | params->request->authorization = strdup("Basic test value"); 28 | response = route->serve(route, params); 29 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 30 | hs_types_serve_flow_params_release(params); 31 | 32 | hs_route_release_route(route); 33 | } /* test_impl */ 34 | 35 | 36 | int main() 37 | { 38 | test_run(test_impl); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/test_io_write_file_to_socket.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | char *filename = "./test_io_write_file_to_socket.txt"; 13 | 14 | bool done = hs_io_write_file_to_socket(0, filename); 15 | 16 | assert_true(!done); 17 | 18 | struct HSSocket *hssocket = hs_socket_plain_new(1); 19 | done = hs_io_write_file_to_socket(hssocket, NULL); 20 | hs_socket_close_and_release(hssocket); 21 | assert_true(!done); 22 | 23 | struct StringBuffer *buffer = stringbuffer_new(); 24 | 25 | for (size_t index = 0; index < 5000; index++) 26 | { 27 | stringbuffer_append_unsigned_int(buffer, index); 28 | } 29 | char *raw = stringbuffer_to_string(buffer); 30 | stringbuffer_release(buffer); 31 | 32 | char *input_filename = "./test_io_write_file_to_socket.in.txt"; 33 | fsio_write_text_file(input_filename, raw); 34 | 35 | fsio_create_empty_file(filename); 36 | int socket = open(filename, O_WRONLY); 37 | 38 | hssocket = hs_socket_plain_new(socket); 39 | done = hs_io_write_file_to_socket(hssocket, input_filename); 40 | hs_socket_close_and_release(hssocket); 41 | fsio_remove(input_filename); 42 | assert_true(done); 43 | 44 | char *text = fsio_read_text_file(filename); 45 | fsio_remove(filename); 46 | 47 | assert_string_equal(text, raw); 48 | hs_io_free(raw); 49 | hs_io_free(text); 50 | } /* test_impl */ 51 | 52 | 53 | int main() 54 | { 55 | test_run(test_impl); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /include/hs_types_array.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_TYPES_ARRAY_H 2 | #define HS_TYPES_ARRAY_H 3 | 4 | #include 5 | #include 6 | 7 | struct HSArrayStringPair; 8 | 9 | /** 10 | * Creates and returns a new array struct. 11 | */ 12 | struct HSArrayStringPair *hs_types_array_string_pair_new(void); 13 | 14 | /** 15 | * Frees all memory used by the provided struct, including 16 | * any internal member/struct. 17 | */ 18 | void hs_types_array_string_pair_release(struct HSArrayStringPair *); 19 | 20 | /** 21 | * Returns the current count. 22 | */ 23 | size_t hs_types_array_string_pair_count(struct HSArrayStringPair *); 24 | 25 | /** 26 | * Adds the provided pair. 27 | */ 28 | bool hs_types_array_string_pair_add(struct HSArrayStringPair *, char * /* key */, char * /* value */); 29 | 30 | /** 31 | * Returns the key for the given index. 32 | * If out of bounds, null will be returned. 33 | */ 34 | char *hs_types_array_string_pair_get_key(struct HSArrayStringPair *, size_t); 35 | 36 | /** 37 | * Returns the value for the given index. 38 | * If out of bounds, null will be returned. 39 | */ 40 | char *hs_types_array_string_pair_get_value(struct HSArrayStringPair *, size_t); 41 | 42 | /** 43 | * Searches the value by key and returns the first one. 44 | * If not found, null will be returned. 45 | */ 46 | char *hs_types_array_string_pair_get_by_key(struct HSArrayStringPair *, char *); 47 | 48 | /** 49 | * Removes and frees the memory of the requested key. 50 | */ 51 | void hs_types_array_string_pair_remove_by_key(struct HSArrayStringPair *, char *); 52 | 53 | #endif 54 | 55 | -------------------------------------------------------------------------------- /include/hs_route.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTE_H 2 | #define HS_ROUTE_H 3 | 4 | #include "hs_types.h" 5 | 6 | struct HSRoute 7 | { 8 | // The path (resource) this route handles 9 | char *path; 10 | // if true, all sub resources are also handled by this route 11 | bool is_parent_path; 12 | // The HTTP methods that are supported by this route 13 | bool is_get; 14 | bool is_post; 15 | bool is_put; 16 | bool is_delete; 17 | bool is_head; 18 | bool is_connect; 19 | bool is_options; 20 | bool is_trace; 21 | bool is_patch; 22 | // The route implementation 23 | enum HSServeFlowResponse (*serve)(struct HSRoute *, struct HSServeFlowParams *); 24 | // Called by the server in order to free the route internal memory 25 | void (*release)(struct HSRoute *); 26 | // Any extended data/functions needed on the route (must be manually released) 27 | void *extension; 28 | }; 29 | 30 | /** 31 | * Creates and returns a new route struct. 32 | */ 33 | struct HSRoute *hs_route_new(void); 34 | 35 | /** 36 | * Frees all internal memory and struct. 37 | */ 38 | void hs_route_release_route(struct HSRoute *); 39 | 40 | /** 41 | * Enables/disables all http methods for route. 42 | */ 43 | void hs_route_set_all_methods(struct HSRoute *, bool /* enable */); 44 | 45 | /** 46 | * Returns true if the request method is supported by the given route. 47 | */ 48 | bool hs_route_is_allowed_for_method(struct HSRoute *, struct HSHttpRequest *); 49 | 50 | /** 51 | * Returns true if the request path is supported by the given route. 52 | */ 53 | bool hs_route_is_supported_path(struct HSRoute *, struct HSHttpRequest *); 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /tests/test_types_http_request_payload_to_string.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | char *raw = "POST /testpost HTTP/1.0\r\n" 13 | "Host: MyHost\r\n" 14 | "Connection: close\r\n" 15 | "Content-Length: 13\r\n" 16 | "Content-Type: application/x-www-form-urlencoded\r\n" 17 | "\r\n" 18 | "12345\n67890\n\nDO NOT READ THIS"; 19 | 20 | char *filename = "./test_types_http_request_payload_to_file.txt"; 21 | 22 | fsio_write_text_file(filename, raw); 23 | 24 | int socket = open(filename, O_RDONLY); 25 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 26 | struct HSHttpRequest *request = hs_parser_parse_request(hssocket); 27 | 28 | struct HSHttpRequestPayload *payload = request->payload; 29 | assert_true(payload != NULL); 30 | 31 | assert_true(!hs_types_http_request_payload_is_loaded(request)); 32 | char *content = hs_types_http_request_payload_to_string(request); 33 | assert_true(hs_types_http_request_payload_is_loaded(request)); 34 | assert_string_equal(content, "12345\n67890\n\n"); 35 | hs_io_free(content); 36 | content = hs_types_http_request_payload_to_string(request); 37 | assert_true(content == NULL); 38 | 39 | hs_types_http_request_release(request); 40 | hs_socket_close_and_release(hssocket); 41 | fsio_remove(filename); 42 | } 43 | 44 | 45 | int main() 46 | { 47 | test_run(test_impl); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/hs_routes_payload_limit.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_routes_common.h" 3 | #include "hs_routes_payload_limit.h" 4 | #include 5 | 6 | static enum HSServeFlowResponse _hs_routes_payload_limit_serve(struct HSRoute *, struct HSServeFlowParams *); 7 | static void _hs_routes_payload_limit_release(struct HSRoute *); 8 | 9 | struct HSRoute *hs_routes_payload_limit_route_new(size_t size_limit) 10 | { 11 | struct HSRoute *route = hs_routes_common_serve_all_route_new(); 12 | 13 | // 0 means, no limit 14 | if (size_limit) 15 | { 16 | size_t *ptr = malloc(sizeof(size_t)); 17 | *ptr = size_limit; 18 | route->extension = ptr; 19 | route->serve = _hs_routes_payload_limit_serve; 20 | route->release = _hs_routes_payload_limit_release; 21 | } 22 | 23 | return(route); 24 | } 25 | 26 | static enum HSServeFlowResponse _hs_routes_payload_limit_serve(struct HSRoute *route, struct HSServeFlowParams *params) 27 | { 28 | if (route == NULL || route->extension == NULL || params == NULL || params->request == NULL) 29 | { 30 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 31 | } 32 | 33 | size_t *ptr = (size_t *)route->extension; 34 | size_t size_limit = *ptr; 35 | 36 | if (params->request->content_length <= size_limit) 37 | { 38 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 39 | } 40 | 41 | params->response->code = HS_HTTP_RESPONSE_CODE_PAYLOAD_TOO_LARGE; 42 | return(HS_SERVE_FLOW_RESPONSE_DONE); 43 | } 44 | 45 | 46 | static void _hs_routes_payload_limit_release(struct HSRoute *route) 47 | { 48 | if (route == NULL) 49 | { 50 | return; 51 | } 52 | 53 | hs_io_free(route->extension); 54 | } 55 | 56 | -------------------------------------------------------------------------------- /tests/test_hs_routes_ratelimit_max_connection_time_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | 6 | 7 | void test_impl() 8 | { 9 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 10 | 11 | params->connection_state = hs_types_server_connection_state_new(); 12 | 13 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 14 | struct HSRoute *route = hs_routes_ratelimit_max_connection_time_route_new(0); 15 | enum HSServeFlowResponse response = route->serve(route, params); 16 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 17 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 18 | hs_route_release_route(route); 19 | 20 | sleep(2); 21 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 22 | route = hs_routes_ratelimit_max_connection_time_route_new(1); 23 | response = route->serve(route, params); 24 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 25 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 26 | hs_route_release_route(route); 27 | 28 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 29 | route = hs_routes_ratelimit_max_connection_time_route_new(20); 30 | response = route->serve(route, params); 31 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 32 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_KEEP_ALIVE); 33 | hs_route_release_route(route); 34 | 35 | hs_types_server_connection_state_release(params->connection_state); 36 | hs_types_serve_flow_params_release(params); 37 | } /* test_impl */ 38 | 39 | 40 | int main() 41 | { 42 | test_run(test_impl); 43 | } 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 23 * * 5' 8 | 9 | jobs: 10 | analyse: 11 | name: Analyse 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | with: 18 | # We must fetch at least the immediate parents so that if this is 19 | # a pull request then we can checkout the head. 20 | fetch-depth: 2 21 | 22 | # If this run was triggered by a pull request event, then checkout 23 | # the head of the pull request instead of the merge commit. 24 | - run: git checkout HEAD^2 25 | if: ${{ github.event_name == 'pull_request' }} 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v1 30 | with: 31 | languages: cpp 32 | # Override language selection by uncommenting this and choosing your languages 33 | # with: 34 | # languages: go, javascript, csharp, python, cpp, java 35 | 36 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 37 | # If this step fails, then you should remove it and run the build manually (see below) 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v1 40 | 41 | # ℹ️ Command-line programs to run using the OS shell. 42 | # 📚 https://git.io/JvXDl 43 | 44 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 45 | # and modify them (or add more) to build your code if your project 46 | # uses a compiled language 47 | 48 | #- run: | 49 | # make bootstrap 50 | # make release 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v1 54 | -------------------------------------------------------------------------------- /tests/test_io_read_line.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | char *raw = "POST /testpost HTTP/1.0\r\n" 13 | "Host: MyHost\r\n" 14 | "Connection: close\r\n" 15 | "Content-Length: 11\r\n" 16 | "Content-Type: application/x-www-form-urlencoded\r\n" 17 | "\r\n" 18 | "12345\n67890\n\n"; 19 | 20 | char *filename = "./test_io_read_line.txt"; 21 | 22 | fsio_write_text_file(filename, raw); 23 | 24 | int socket = open(filename, O_RDONLY); 25 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 26 | 27 | struct StringBuffer *buffer = stringbuffer_new(); 28 | 29 | char *line = hs_io_read_line(hssocket, buffer); 30 | assert_string_equal(line, "POST /testpost HTTP/1.0"); 31 | hs_io_free(line); 32 | 33 | line = hs_io_read_line(hssocket, buffer); 34 | assert_string_equal(line, "Host: MyHost"); 35 | hs_io_free(line); 36 | 37 | line = hs_io_read_line(hssocket, buffer); 38 | assert_string_equal(line, "Connection: close"); 39 | hs_io_free(line); 40 | 41 | line = hs_io_read_line(hssocket, buffer); 42 | assert_string_equal(line, "Content-Length: 11"); 43 | hs_io_free(line); 44 | 45 | line = hs_io_read_line(hssocket, buffer); 46 | assert_string_equal(line, "Content-Type: application/x-www-form-urlencoded"); 47 | hs_io_free(line); 48 | 49 | line = hs_io_read_line(hssocket, buffer); 50 | assert_true(line == NULL); 51 | 52 | hs_socket_close_and_release(hssocket); 53 | fsio_remove(filename); 54 | stringbuffer_release(buffer); 55 | } /* test_impl */ 56 | 57 | 58 | int main() 59 | { 60 | test_run(test_impl); 61 | } 62 | 63 | -------------------------------------------------------------------------------- /tests/test_types_http_request_payload_to_file.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | char *raw = "POST /testpost HTTP/1.0\r\n" 13 | "Host: MyHost\r\n" 14 | "Connection: close\r\n" 15 | "Content-Length: 13\r\n" 16 | "Content-Type: application/x-www-form-urlencoded\r\n" 17 | "\r\n" 18 | "12345\n67890\n\nDO NOT READ THIS"; 19 | 20 | char *filename = "./test_types_http_request_payload_to_file.txt"; 21 | char *output_filename = "./test_types_http_request_payload_to_file.out.txt"; 22 | 23 | fsio_write_text_file(filename, raw); 24 | 25 | int socket = open(filename, O_RDONLY); 26 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 27 | struct HSHttpRequest *request = hs_parser_parse_request(hssocket); 28 | 29 | struct HSHttpRequestPayload *payload = request->payload; 30 | assert_true(payload != NULL); 31 | 32 | assert_true(!hs_types_http_request_payload_is_loaded(request)); 33 | bool done = hs_types_http_request_payload_to_file(request, output_filename); 34 | assert_true(hs_types_http_request_payload_is_loaded(request)); 35 | assert_true(done); 36 | char *content = fsio_read_text_file(output_filename); 37 | assert_string_equal(content, "12345\n67890\n\n"); 38 | hs_io_free(content); 39 | done = hs_types_http_request_payload_to_file(request, output_filename); 40 | assert_true(!done); 41 | 42 | hs_types_http_request_release(request); 43 | hs_socket_close_and_release(hssocket); 44 | fsio_remove(filename); 45 | fsio_remove(output_filename); 46 | } /* test_impl */ 47 | 48 | 49 | int main() 50 | { 51 | test_run(test_impl); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /examples/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/jCCAuagAwIBAgIJAJxCakka4mgFMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMTEyMDgxNjIwMzdaFw0yMjEyMDgxNjIwMzdaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAL6LPfoHC+7qs2vU0XTm0TUS+Gl4lixog4e6ZBres1YJFmOodUtLGUcs8+Qx 6 | e7agnIzsyeCZ7OMcTSFvThBy/XazelBv/4KQyYbCuCFr3SspRuoXV7N6a6Jg6kdL 7 | uGoqi+fjy62wXyc7fcn15GMSrsGKFRhHiIySen77bLXyror7F7cZN8N124r7PYg5 8 | c7GUA0MdbyNLVa9H8Y13e76sAduegb3hASsbUocPTzf7vAhY+4pER6NzoiLL/JvR 9 | jPc/J4caIqgMUSv6M70ktAfK7erkOcdX5GBd58gHLTz+JZ6Daye/xDxCYkr27qYG 10 | kCJ/YybuCAxJEDImxgZp5I11hYGqDFzCveyveR7Ppf4wnFiXZq4bP3AaRXa6L+lR 11 | cQ1dxVP7NCDykhHWJx6QEbWw52X7V8dfQNQ9uM/SCHTJUIvsZ04slN3rbxGreRho 12 | Ma972PN4FWHvcPU9/8bapyS5xx5UgsFgXY6Xqu90nfS9RxMMsAhKlaqP+cyrER9Q 13 | fp49/SMPYILpXMcEp4sxff4pgIBty1RBEU6nwYfxsMP+GnhGUT1VQ7sYxFS+Pc3M 14 | HqD+hHFjERoHgcBfyNWEEC3BxI3mJFOax2tnrH6fmG069iGkVW4VITgzbvw1JJMe 15 | Sj7L8/4eyDoq/zWTdDdD5GOOftNY2SgTwMuR8GuENffndv4hAgMBAAGjUzBRMB0G 16 | A1UdDgQWBBTSm+FjWSlz9vpGfjY7iqsImQBQBjAfBgNVHSMEGDAWgBTSm+FjWSlz 17 | 9vpGfjY7iqsImQBQBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC 18 | AQB6mAFjN31L2txzzKGiXldFGojfA108WHFU+gU751f+uS21eUmGpefmmWHv0riv 19 | jRZNrs1xDdmDWWTdImZ1nhjoY7kVELvzUmUXutAhftJM/5wSwbDwLhSfJve04pQq 20 | liv+u78nV5KBnBv0RBL85cKdoB9r0a82+B2g+K6N3olsZKjaaDswWAykB4DGYmg7 21 | /tRW5FMosAkyA6kTQYSgiSLYfh757mg6BWXFO8n/eCkaNpA5Z+3o0PSkovxPsCHS 22 | 2k94a8dDzLF/+P7ZpOU9K8gQmkI3eyYoYTjxL/EsjX6B7C29wx3HEx6LCWmauOvo 23 | tCXr+vHHBSd/A052bTedjL4chhefCMdEwXTetjXcom+zKUceIZRUYJOsTTC3ah6E 24 | DbaUcOTYoOoD++CXSpY2Al7aB821XLwmrd4Z0LjeDmkP3SQotEFn/ojRrP+XF2w/ 25 | Fw+mAeV85JO4BIfaCsNQldQrJSgRTYC7Eg+iF1iMK0sD2lIUrjVSXMbqntcYTm+C 26 | psh6G5FCRYVrq1Aevlos+MSlU7CJmV2oIbxTNih1c4FaqJ5gddRw9jy9r6g89gLD 27 | 885gZeFxS6yt6sBrhRpEljdmZOm7iJflZnK+bJuiSAjiWkroAFkylDRas7fQJ6IV 28 | SvqGDqvkDN74Pu7e98SfXNxfEmYRwTSuyVQwYpzWcOfrfw== 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /tests/test_hs_routes_redirection_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | struct HSRoute *route = hs_routes_redirection_route_new(strdup("/fs"), strdup("/fs/")); 10 | enum HSServeFlowResponse response = route->serve(route, params); 11 | 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 13 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Location"), "/fs/"); 14 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT); 15 | hs_route_release_route(route); 16 | hs_types_serve_flow_params_release(params); 17 | 18 | params = hs_types_serve_flow_params_new(); 19 | route = hs_routes_redirection_route_new_with_options(strdup("/fs"), strdup("/fs/"), true); 20 | response = route->serve(route, params); 21 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 22 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Location"), "/fs/"); 23 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT); 24 | hs_route_release_route(route); 25 | hs_types_serve_flow_params_release(params); 26 | 27 | params = hs_types_serve_flow_params_new(); 28 | route = hs_routes_redirection_route_new_with_options(strdup("/fs"), strdup("/fs/"), false); 29 | response = route->serve(route, params); 30 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 31 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Location"), "/fs/"); 32 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_PERMANENT_REDIRECT); 33 | hs_route_release_route(route); 34 | hs_types_serve_flow_params_release(params); 35 | } /* test_impl */ 36 | 37 | 38 | int main() 39 | { 40 | test_run(test_impl); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/hs_routes_error.c: -------------------------------------------------------------------------------- 1 | #include "hs_routes_common.h" 2 | #include "hs_routes_error.h" 3 | 4 | static enum HSServeFlowResponse _hs_routes_404_not_found_serve(struct HSRoute *, struct HSServeFlowParams *); 5 | static enum HSServeFlowResponse _hs_routes_411_length_required_serve(struct HSRoute *, struct HSServeFlowParams *); 6 | 7 | struct HSRoute *hs_routes_error_404_not_found_route_new(void) 8 | { 9 | struct HSRoute *route = hs_routes_common_serve_all_route_new(); 10 | 11 | route->serve = _hs_routes_404_not_found_serve; 12 | 13 | return(route); 14 | } 15 | 16 | struct HSRoute *hs_routes_error_411_length_required_route_new(void) 17 | { 18 | struct HSRoute *route = hs_route_new(); 19 | 20 | route->serve = _hs_routes_411_length_required_serve; 21 | route->is_post = true; 22 | route->is_put = true; 23 | route->is_parent_path = true; 24 | 25 | return(route); 26 | } 27 | 28 | static enum HSServeFlowResponse _hs_routes_404_not_found_serve(struct HSRoute *route, struct HSServeFlowParams *params) 29 | { 30 | if (route == NULL || params == NULL || params->response == NULL) 31 | { 32 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 33 | } 34 | 35 | params->response->code = HS_HTTP_RESPONSE_CODE_NOT_FOUND; 36 | 37 | return(HS_SERVE_FLOW_RESPONSE_DONE); 38 | } 39 | 40 | static enum HSServeFlowResponse _hs_routes_411_length_required_serve(struct HSRoute *route, struct HSServeFlowParams *params) 41 | { 42 | if (route == NULL || params == NULL || params->request == NULL || params->response == NULL) 43 | { 44 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 45 | } 46 | 47 | char *value = hs_types_array_string_pair_get_by_key(params->request->headers, "content-length"); 48 | if (value != NULL) 49 | { 50 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 51 | } 52 | 53 | params->response->code = HS_HTTP_RESPONSE_CODE_LENGTH_REQUIRED; 54 | return(HS_SERVE_FLOW_RESPONSE_DONE); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /include/hs_types_cookie.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_TYPES_COOKIE_H 2 | #define HS_TYPES_COOKIE_H 3 | 4 | #include "hs_constants.h" 5 | #include 6 | #include 7 | 8 | struct HSCookie 9 | { 10 | char *name; 11 | char *value; 12 | char *expires; 13 | int max_age; 14 | bool secure; 15 | bool http_only; 16 | char *domain; 17 | char *path; 18 | enum HSCookieSameSite same_site; 19 | }; 20 | 21 | struct HSCookies; 22 | 23 | /** 24 | * Creates and returns a new cookie struct. 25 | */ 26 | struct HSCookie *hs_types_cookie_new(void); 27 | 28 | /** 29 | * Frees all memory used by the provided struct, including 30 | * any internal member/struct. 31 | */ 32 | void hs_types_cookie_release(struct HSCookie *); 33 | 34 | /** 35 | * Creates and returns a new cookies struct. 36 | */ 37 | struct HSCookies *hs_types_cookies_new(void); 38 | 39 | /** 40 | * Frees all memory used by the provided struct, including 41 | * any internal member/struct. 42 | */ 43 | void hs_types_cookies_release(struct HSCookies *); 44 | 45 | /** 46 | * Returns the current count. 47 | */ 48 | size_t hs_types_cookies_count(struct HSCookies *); 49 | 50 | /** 51 | * Adds the cookie to the cookies structure. 52 | */ 53 | bool hs_types_cookies_add(struct HSCookies *, struct HSCookie *); 54 | 55 | /** 56 | * Returns the cookie for the given index. 57 | * If out of bounds, null will be returned. 58 | */ 59 | struct HSCookie *hs_types_cookies_get(struct HSCookies *, size_t); 60 | 61 | /** 62 | * Searches the cookie by name and returns the first one. 63 | * If not found, null will be returned. 64 | */ 65 | struct HSCookie *hs_types_cookies_get_by_name(struct HSCookies *, char *); 66 | 67 | /** 68 | * Removes and frees the memory of the requested cookie. 69 | */ 70 | void hs_types_cookies_remove_by_name(struct HSCookies *, char *); 71 | 72 | #endif 73 | 74 | -------------------------------------------------------------------------------- /src/hs_openssl.c: -------------------------------------------------------------------------------- 1 | #include "hs_openssl.h" 2 | 3 | #ifdef HS_SSL_SUPPORTED 4 | #include 5 | 6 | static int _hs_openssl_global_ssl_init = 0; 7 | 8 | 9 | bool hs_openssl_supported(void) 10 | { 11 | return(true); 12 | } 13 | 14 | 15 | void hs_openssl_init(void) 16 | { 17 | _hs_openssl_global_ssl_init++; 18 | 19 | if (_hs_openssl_global_ssl_init > 1) 20 | { 21 | return; 22 | } 23 | 24 | SSL_load_error_strings(); 25 | SSL_library_init(); 26 | OpenSSL_add_all_algorithms(); 27 | } 28 | 29 | 30 | void hs_openssl_cleanup(void) 31 | { 32 | _hs_openssl_global_ssl_init--; 33 | 34 | if (_hs_openssl_global_ssl_init < 0) 35 | { 36 | _hs_openssl_global_ssl_init = 0; 37 | } 38 | else if (_hs_openssl_global_ssl_init) 39 | { 40 | return; 41 | } 42 | 43 | ERR_free_strings(); 44 | EVP_cleanup(); 45 | } 46 | 47 | 48 | SSL_CTX *hs_openssl_context_create(char *private_key_pem_file, char *certificate_pem_file) 49 | { 50 | if (private_key_pem_file == NULL || certificate_pem_file == NULL) 51 | { 52 | return(NULL); 53 | } 54 | 55 | SSL_CTX *ssl_context = SSL_CTX_new(SSLv23_method()); 56 | if (ssl_context == NULL) 57 | { 58 | return(NULL); 59 | } 60 | 61 | SSL_CTX_set_options(ssl_context, SSL_OP_ALL); 62 | SSL_CTX_set_options(ssl_context, SSL_OP_SINGLE_DH_USE); 63 | 64 | if ((SSL_CTX_use_certificate_file(ssl_context, certificate_pem_file, SSL_FILETYPE_PEM)) <= 0) 65 | { 66 | hs_openssl_context_release(ssl_context); 67 | return(NULL); 68 | } 69 | 70 | if ((SSL_CTX_use_PrivateKey_file(ssl_context, private_key_pem_file, SSL_FILETYPE_PEM)) <= 0) 71 | { 72 | hs_openssl_context_release(ssl_context); 73 | return(NULL); 74 | } 75 | 76 | return(ssl_context); 77 | } 78 | 79 | 80 | void hs_openssl_context_release(SSL_CTX *ssl_context) 81 | { 82 | if (ssl_context == NULL) 83 | { 84 | return; 85 | } 86 | 87 | SSL_CTX_free(ssl_context); 88 | } 89 | #else 90 | 91 | 92 | bool hs_openssl_supported(void) 93 | { 94 | return(false); 95 | } 96 | 97 | #endif 98 | 99 | -------------------------------------------------------------------------------- /include/hs_io.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_IO_H 2 | #define HS_IO_H 3 | 4 | #include "hs_socket.h" 5 | #include "stringbuffer.h" 6 | #include 7 | #include 8 | 9 | /** 10 | * This is an internal header and should not be used outside the library. 11 | */ 12 | 13 | struct HSIOHttpRequestPayload 14 | { 15 | struct StringBuffer *partial; 16 | struct HSSocket *socket; 17 | }; 18 | 19 | /** 20 | * Frees the provided pointer if not NULL. 21 | */ 22 | void hs_io_free(void *); 23 | 24 | /** 25 | * Reads the next line from the socket. 26 | * In case more is pulled from the socket, it will remain in the provided work buffer. 27 | * In case no line is read, NULL will be returned. 28 | */ 29 | char *hs_io_read_line(struct HSSocket *, struct StringBuffer *); 30 | 31 | /** 32 | * Creates an internal payload structure to be used to fetch the rest of the payload content. 33 | */ 34 | struct HSIOHttpRequestPayload *hs_io_new_http_request_payload(struct HSSocket *, struct StringBuffer *); 35 | 36 | /** 37 | * Releases the provided payload. 38 | */ 39 | void hs_io_release_http_request_payload(struct HSIOHttpRequestPayload *); 40 | 41 | /** 42 | * Reads the entire content into the provided buffer. 43 | */ 44 | bool hs_io_read_fully(struct HSSocket *, struct StringBuffer *, size_t /* length */); 45 | 46 | /** 47 | * Reads the entire content and writes it to the file. 48 | */ 49 | bool hs_io_read_and_write_to_file(struct HSSocket *, FILE *, size_t /* length */); 50 | 51 | /** 52 | * Writes the entire string to the socket. 53 | * In case of any error, false will be returned. 54 | */ 55 | bool hs_io_write_string_to_socket(struct HSSocket *, char *, size_t); 56 | 57 | /** 58 | * Writes the entire file content to the socket. 59 | * In case of any error, false will be returned. 60 | */ 61 | bool hs_io_write_file_to_socket(struct HSSocket *, char *); 62 | 63 | /** 64 | * Used by hashtables to release only the key. 65 | */ 66 | void hs_io_release_hashtable_key(char *, void *); 67 | 68 | /** 69 | * Used by hashtables to release both key and value. 70 | */ 71 | void hs_io_release_hashtable_key_and_value(char *, void *); 72 | 73 | void hs_io_noop(void *); 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /include/hs_router.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTER_H 2 | #define HS_ROUTER_H 3 | 4 | #include "hs_route.h" 5 | #include "hs_socket.h" 6 | #include "hs_types.h" 7 | 8 | struct HSRouter; 9 | 10 | /** 11 | * Creates a new router and returns it. 12 | */ 13 | struct HSRouter *hs_router_new(void); 14 | 15 | /** 16 | * Frees all memory used by the router directly (including routes). 17 | */ 18 | void hs_router_release(struct HSRouter *); 19 | 20 | /** 21 | * Sets the keep alive support for the router (default is true) 22 | */ 23 | void hs_router_set_keep_alive_support(struct HSRouter *, bool); 24 | 25 | /** 26 | * Adds a new route. 27 | */ 28 | void hs_router_add_route(struct HSRouter *, struct HSRoute *); 29 | 30 | /** 31 | * Loops over HTTP requests and handles them until socket closed. 32 | */ 33 | bool hs_router_serve_forever(struct HSRouter *, struct HSSocket *, void * /* context */, bool (*should_stop)(struct HSRouter *, struct HSSocket *, size_t /* request counter */, void * /* context */)); 34 | 35 | /** 36 | * Reads the next HTTP request and handles it. 37 | */ 38 | bool hs_router_serve_next(struct HSRouter *, struct HSServerConnectionState *); 39 | 40 | /** 41 | * Handles a HTTP request. 42 | */ 43 | bool hs_router_serve(struct HSRouter *, struct HSServeFlowParams *); 44 | 45 | /** 46 | * Returns a route which calls an internal router which will contain sub routes. 47 | */ 48 | struct HSRoute *hs_router_as_route(struct HSRouter *); 49 | 50 | /** 51 | * Creates a new buffer and writes common response header content, including 52 | * status line, headers and set cookie headers. 53 | * This is an internal function. 54 | */ 55 | struct StringBuffer *hs_router_write_common_response_header(enum HSHttpResponseCode, struct HSArrayStringPair *, struct HSCookies *, bool /* close_connection */); 56 | 57 | /** 58 | * Internal function. 59 | * Removes the route path from the request resource, ensuring it starts with / character. 60 | * The value is replaced inside the request object, while the original value is returned. 61 | * The updated value in the request should not be freed directly. 62 | */ 63 | char *hs_router_remove_path_prefix(struct HSRoute *, struct HSHttpRequest *); 64 | 65 | #endif 66 | 67 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_route_session_from_string.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSSession *session = NULL; 9 | 10 | hs_routes_session_route_session_from_string(session, "[session]\n" 11 | "test1=value1\n" 12 | "test2=value2\n" 13 | "test3=value3\n\n"); 14 | 15 | session = hs_routes_session_new_session(); 16 | hs_routes_session_route_session_from_string(session, "[session]\n" 17 | "test1=value1\n" 18 | "test2=value2\n" 19 | "test3=value3\n\n"); 20 | assert_num_equal(hashtable_size(session->data), 3); 21 | assert_string_equal(hashtable_get(session->data, "test1"), "value1"); 22 | assert_string_equal(hashtable_get(session->data, "test2"), "value2"); 23 | assert_string_equal(hashtable_get(session->data, "test3"), "value3"); 24 | hs_routes_session_release_session(session); 25 | 26 | session = hs_routes_session_new_session(); 27 | hs_routes_session_route_session_from_string(session, "bad=1\n\n" 28 | "[bad]\n" 29 | "k1=v1\n\n" 30 | "[session]\n" 31 | "test1=value1\n" 32 | "test2=value2\n" 33 | "test3=value3\n\n" 34 | "[bad2]\n" 35 | "k1=v1\n\n" 36 | ); 37 | assert_num_equal(hashtable_size(session->data), 3); 38 | assert_string_equal(hashtable_get(session->data, "test1"), "value1"); 39 | assert_string_equal(hashtable_get(session->data, "test2"), "value2"); 40 | assert_string_equal(hashtable_get(session->data, "test3"), "value3"); 41 | hs_routes_session_release_session(session); 42 | } /* test_impl */ 43 | 44 | 45 | int main() 46 | { 47 | test_run(test_impl); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /tests/test_types_array_string_pair_mutations.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | 4 | 5 | void test_impl() 6 | { 7 | struct HSArrayStringPair *array = hs_types_array_string_pair_new(); 8 | 9 | assert_num_equal(hs_types_array_string_pair_count(array), 0); 10 | hs_types_array_string_pair_remove_by_key(array, "test"); 11 | assert_num_equal(hs_types_array_string_pair_count(array), 0); 12 | hs_types_array_string_pair_add(array, strdup("test"), strdup("value1")); 13 | hs_types_array_string_pair_add(array, strdup("test"), strdup("value2")); 14 | assert_num_equal(hs_types_array_string_pair_count(array), 2); 15 | assert_string_equal(hs_types_array_string_pair_get_by_key(array, "test"), "value1"); 16 | assert_string_equal(hs_types_array_string_pair_get_key(array, 0), "test"); 17 | assert_string_equal(hs_types_array_string_pair_get_value(array, 0), "value1"); 18 | assert_string_equal(hs_types_array_string_pair_get_key(array, 1), "test"); 19 | assert_string_equal(hs_types_array_string_pair_get_value(array, 1), "value2"); 20 | hs_types_array_string_pair_remove_by_key(array, "test"); 21 | assert_num_equal(hs_types_array_string_pair_count(array), 0); 22 | hs_types_array_string_pair_remove_by_key(array, "test"); 23 | assert_num_equal(hs_types_array_string_pair_count(array), 0); 24 | 25 | hs_types_array_string_pair_add(array, strdup("test1"), strdup("value1")); 26 | hs_types_array_string_pair_add(array, strdup("test2"), strdup("value2")); 27 | assert_num_equal(hs_types_array_string_pair_count(array), 2); 28 | assert_string_equal(hs_types_array_string_pair_get_by_key(array, "test1"), "value1"); 29 | assert_string_equal(hs_types_array_string_pair_get_by_key(array, "test2"), "value2"); 30 | assert_string_equal(hs_types_array_string_pair_get_key(array, 0), "test1"); 31 | assert_string_equal(hs_types_array_string_pair_get_value(array, 0), "value1"); 32 | assert_string_equal(hs_types_array_string_pair_get_key(array, 1), "test2"); 33 | assert_string_equal(hs_types_array_string_pair_get_value(array, 1), "value2"); 34 | hs_types_array_string_pair_remove_by_key(array, "test2"); 35 | assert_num_equal(hs_types_array_string_pair_count(array), 1); 36 | hs_types_array_string_pair_remove_by_key(array, "test1"); 37 | assert_num_equal(hs_types_array_string_pair_count(array), 0); 38 | 39 | hs_types_array_string_pair_release(array); 40 | } 41 | 42 | 43 | int main() 44 | { 45 | test_run(test_impl); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /include/hs_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_PARSER_H 2 | #define HS_PARSER_H 3 | 4 | #include "hs_socket.h" 5 | #include "hs_types.h" 6 | 7 | /** 8 | * Creates a basic http request struct representing the given resource path 9 | * and query string. 10 | * Since a lot of information is missing, only the URL based info 11 | * will be set in the returned request. All other information is 12 | * defaulted. 13 | */ 14 | struct HSHttpRequest *hs_parser_create_request_from_resource(char * /* resource */); 15 | 16 | /** 17 | * Creates a basic http request struct representing the given URL. 18 | * Since a lot of information is missing, only the URL based info 19 | * will be set in the returned request. All other information is 20 | * defaulted. 21 | */ 22 | struct HSHttpRequest *hs_parser_create_request_from_url(char * /* url */); 23 | 24 | /** 25 | * Parses the request line and populates the request struct. 26 | * If the line is invalid, NULL will be returned. 27 | */ 28 | struct HSHttpRequest *hs_parser_parse_request_line(char *); 29 | 30 | /** 31 | * Parsers a http header and returns it. If invalid content, it will return NULL. 32 | */ 33 | char **hs_parser_parse_header(char *); 34 | 35 | /** 36 | * Parsers the cookie header value (without key) and updates the cookies container. 37 | */ 38 | bool hs_parser_parse_cookie_header(struct HSCookies *, char *); 39 | 40 | /** 41 | * Sets the specific header attribute in the given request from the header key/value. 42 | * If no specific header attribute is supported, no changes will be done. 43 | */ 44 | void hs_parser_set_header(struct HSHttpRequest *, char * /* key */, char * /* value */); 45 | 46 | /** 47 | * Parsers the http header and creates a new http request struct. 48 | * The payload is not parsed. 49 | */ 50 | struct HSHttpRequest *hs_parser_parse_request(struct HSSocket *); 51 | 52 | /** 53 | * Parsers the HTTP method and returns the enum value. 54 | * In case of unknown value or NULL, a HS_HTTP_METHOD_UNKNOWN method type will be returned. 55 | */ 56 | enum HSHttpMethod hs_parser_parse_method(char *); 57 | 58 | /** 59 | * Parsers the query string and returns an array of key/value pairs. 60 | */ 61 | struct HSArrayStringPair *hs_parser_parse_query_string(char *); 62 | 63 | /** 64 | * Returns the protocol from the given URL. 65 | * If no protocol, URL or unknown protocol, HS_HTTP_PROTOCOL_UNKNOWN will be returned. 66 | */ 67 | enum HSHttpProtocol hs_parser_parse_protocol_from_url(char *); 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /include/hs_routes_fs.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_FS_H 2 | #define HS_ROUTES_FS_H 3 | 4 | #include "hs_route.h" 5 | 6 | /** 7 | * Returns the file content with the relevant mime type. 8 | * It will ignore non file paths or paths that do not exist. 9 | * The base directory value will be released with the route. 10 | */ 11 | struct HSRoute *hs_routes_fs_file_route_new(char * /* base directory */); 12 | 13 | /** 14 | * Returns the file content with the relevant mime type. 15 | * It will ignore non file paths or paths that do not exist. 16 | * The base directory value will be released with the route. 17 | */ 18 | struct HSRoute *hs_routes_fs_file_route_new_with_options(char * /* base directory */, enum HSMimeType (*get_mime_type)(char * /* file */, enum HSMimeType /* detected mime type */), bool /* close connection */); 19 | 20 | /** 21 | * Returns the directory content. 22 | * It will ignore non file paths or paths that do not exist. 23 | * The base directory value will be released with the route. 24 | */ 25 | struct HSRoute *hs_routes_fs_directory_route_new(char * /* base directory */); 26 | 27 | /** 28 | * Returns the directory content. 29 | * It will ignore non file paths or paths that do not exist. 30 | * For media entities, such as images and videos, the relevant html elements will be created. 31 | * The base directory value will be released with the route. 32 | */ 33 | struct HSRoute *hs_routes_fs_directory_route_new_with_media_support(char * /* base directory */); 34 | 35 | /** 36 | * Returns the directory content. 37 | * It will ignore non file paths or paths that do not exist. 38 | * The base directory and optional additional_head_content value will be released with the route. 39 | * The optional context will not be released with the route. 40 | */ 41 | struct HSRoute *hs_routes_fs_directory_route_new_with_options(char * /* base directory */, char * /* additional_head_content */, bool (*filter)(char * /* name */, bool /* is directory */), char * (*render_directory_entry)(char * /* name */, char * /* href */, void * /* context */), char * (*render_file_entry)(char * /* name */, char * /* href */, void * /* context */), char * (*create_href)(struct HSServeFlowParams *params, char * /* base directory */, char * /* entry */), void *context); 42 | 43 | /** 44 | * The render_file_entry used by the directory with media support route as standalone function. 45 | */ 46 | char *hs_routes_fs_directory_route_render_file_entry_with_media_support(char * /* name */, char * /* href */, void * /* context */); 47 | 48 | #endif 49 | 50 | -------------------------------------------------------------------------------- /tests/test_hs_routes_session_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | 5 | 6 | char *_test_with_values(struct HSCookie *cookie, struct HSRoute *route) 7 | { 8 | bool release_route = false; 9 | 10 | if (route == NULL) 11 | { 12 | route = hs_routes_session_route_new_default(); 13 | release_route = true; 14 | } 15 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 16 | 17 | if (cookie != NULL) 18 | { 19 | hs_types_cookies_add(params->request->cookies, cookie); 20 | } 21 | 22 | enum HSServeFlowResponse response = route->serve(route, params); 23 | 24 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 25 | 26 | assert_num_equal(params->callbacks->count, 1); 27 | struct HSPostResponseCallback *callback = params->callbacks->callbacks[0]; 28 | assert_true(callback != NULL); 29 | 30 | assert_num_equal(hs_types_cookies_count(params->response->cookies), 1); 31 | struct HSCookie *session_cookie = hs_types_cookies_get(params->response->cookies, 0); 32 | assert_true(session_cookie != NULL); 33 | assert_string_equal(HS_DEFAULT_SESSION_COOKIE_NAME, session_cookie->name); 34 | assert_true(session_cookie->value != NULL); 35 | char *session_id = strdup(session_cookie->value); 36 | if (cookie != NULL) 37 | { 38 | assert_string_equal(session_id, cookie->value); 39 | hs_io_free(session_id); 40 | } 41 | 42 | callback->run(callback); 43 | 44 | hs_types_serve_flow_params_release(params); 45 | if (release_route) 46 | { 47 | hs_route_release_route(route); 48 | } 49 | 50 | return(session_id); 51 | } /* _test_with_values */ 52 | 53 | 54 | void _test_flow(struct HSRoute *route) 55 | { 56 | char *session_id = _test_with_values(NULL, route); 57 | 58 | assert_true(session_id != NULL); 59 | 60 | struct HSCookie *cookie = hs_types_cookie_new(); 61 | cookie->name = strdup(HS_DEFAULT_SESSION_COOKIE_NAME); 62 | cookie->value = strdup(session_id); 63 | _test_with_values(cookie, route); 64 | 65 | cookie = hs_types_cookie_new(); 66 | cookie->name = strdup(HS_DEFAULT_SESSION_COOKIE_NAME); 67 | cookie->value = strdup(session_id); 68 | _test_with_values(cookie, route); 69 | 70 | hs_io_free(session_id); 71 | } 72 | 73 | 74 | void test_impl() 75 | { 76 | _test_flow(NULL); 77 | 78 | struct HSRoute *route = hs_routes_session_route_new_default(); 79 | _test_flow(route); 80 | hs_route_release_route(route); 81 | } /* test_impl */ 82 | 83 | 84 | int main() 85 | { 86 | test_run(test_impl); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/hs_routes_static.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_routes_static.h" 3 | #include 4 | #include 5 | 6 | static enum HSServeFlowResponse _hs_routes_static_serve(struct HSRoute *, struct HSServeFlowParams *); 7 | static void _hs_routes_static_release(struct HSRoute *); 8 | 9 | struct HsRoutesStaticContext 10 | { 11 | char *content; 12 | enum HSMimeType mime_type; 13 | }; 14 | 15 | struct HSRoute *hs_routes_static_route_new(char *content, enum HSMimeType mime_type) 16 | { 17 | if (content == NULL) 18 | { 19 | return(NULL); 20 | } 21 | 22 | struct HsRoutesStaticContext *context = malloc(sizeof(struct HsRoutesStaticContext)); 23 | context->content = content; 24 | context->mime_type = mime_type; 25 | 26 | struct HSRoute *route = hs_route_new(); 27 | route->is_get = true; 28 | route->serve = _hs_routes_static_serve; 29 | route->release = _hs_routes_static_release; 30 | route->extension = context; 31 | 32 | return(route); 33 | } 34 | 35 | struct HSRoute *hs_routes_static_text_route_new(char *text) 36 | { 37 | return(hs_routes_static_route_new(text, HS_MIME_TYPE_TEXT_PLAIN)); 38 | } 39 | 40 | struct HSRoute *hs_routes_static_html_route_new(char *html) 41 | { 42 | return(hs_routes_static_route_new(html, HS_MIME_TYPE_TEXT_HTML)); 43 | } 44 | 45 | struct HSRoute *hs_routes_static_css_route_new(char *css_text) 46 | { 47 | return(hs_routes_static_route_new(css_text, HS_MIME_TYPE_TEXT_CSS)); 48 | } 49 | 50 | struct HSRoute *hs_routes_static_js_route_new(char *js_text) 51 | { 52 | return(hs_routes_static_route_new(js_text, HS_MIME_TYPE_TEXT_JAVASCRIPT)); 53 | } 54 | 55 | static enum HSServeFlowResponse _hs_routes_static_serve(struct HSRoute *route, struct HSServeFlowParams *params) 56 | { 57 | if (route == NULL || route->extension == NULL) 58 | { 59 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 60 | } 61 | 62 | struct HsRoutesStaticContext *context = (struct HsRoutesStaticContext *)route->extension; 63 | 64 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 65 | params->response->content_string = strdup(context->content); 66 | params->response->mime_type = context->mime_type; 67 | 68 | return(HS_SERVE_FLOW_RESPONSE_DONE); 69 | } 70 | 71 | 72 | static void _hs_routes_static_release(struct HSRoute *route) 73 | { 74 | if (route == NULL || route->extension == NULL) 75 | { 76 | return; 77 | } 78 | 79 | struct HsRoutesStaticContext *context = (struct HsRoutesStaticContext *)route->extension; 80 | 81 | hs_io_free(context->content); 82 | hs_io_free(context); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/hs_routes_favicon.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "hs_io.h" 3 | #include "hs_routes_favicon.h" 4 | #include 5 | #include 6 | 7 | static enum HSServeFlowResponse _hs_routes_favicon_route_serve(struct HSRoute *, struct HSServeFlowParams *); 8 | static void _hs_routes_favicon_route_release(struct HSRoute *); 9 | 10 | struct HSRouteFaviconContext 11 | { 12 | char *path; 13 | char *cache_control_header; 14 | }; 15 | 16 | struct HSRoute *hs_routes_favicon_route_new(char *path, int max_age_seconds) 17 | { 18 | if (path == NULL) 19 | { 20 | return(NULL); 21 | } 22 | 23 | struct HSRoute *route = hs_route_new(); 24 | 25 | route->serve = _hs_routes_favicon_route_serve; 26 | route->release = _hs_routes_favicon_route_release; 27 | route->is_get = true; 28 | route->path = strdup("/favicon.ico"); 29 | 30 | struct HSRouteFaviconContext *context = malloc(sizeof(struct HSRouteFaviconContext)); 31 | context->path = path; 32 | context->cache_control_header = NULL; 33 | if (max_age_seconds > 0) 34 | { 35 | struct StringBuffer *buffer = stringbuffer_new(); 36 | stringbuffer_append_string(buffer, "public, max-age="); 37 | stringbuffer_append_int(buffer, max_age_seconds); 38 | context->cache_control_header = stringbuffer_to_string(buffer); 39 | stringbuffer_release(buffer); 40 | } 41 | route->extension = context; 42 | 43 | return(route); 44 | } 45 | 46 | static enum HSServeFlowResponse _hs_routes_favicon_route_serve(struct HSRoute *route, struct HSServeFlowParams *params) 47 | { 48 | if (route == NULL || route->extension == NULL || params == NULL || params->response == NULL) 49 | { 50 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 51 | } 52 | 53 | struct HSRouteFaviconContext *context = (struct HSRouteFaviconContext *)route->extension; 54 | 55 | if (!fsio_file_exists(context->path)) 56 | { 57 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 58 | } 59 | 60 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 61 | params->response->content_file = strdup(context->path); 62 | 63 | if (context->cache_control_header != NULL) 64 | { 65 | hs_types_array_string_pair_add(params->response->headers, strdup("Cache-Control"), strdup(context->cache_control_header)); 66 | } 67 | 68 | return(HS_SERVE_FLOW_RESPONSE_DONE); 69 | } 70 | 71 | 72 | static void _hs_routes_favicon_route_release(struct HSRoute *route) 73 | { 74 | if (route == NULL) 75 | { 76 | return; 77 | } 78 | 79 | struct HSRouteFaviconContext *context = (struct HSRouteFaviconContext *)route->extension; 80 | 81 | hs_io_free(context->path); 82 | hs_io_free(context->cache_control_header); 83 | hs_io_free(context); 84 | } 85 | 86 | -------------------------------------------------------------------------------- /tests/test_route_is_supported_path.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSRoute *route = hs_route_new(); 7 | struct HSHttpRequest *request = hs_types_http_request_new(); 8 | 9 | assert_true(!hs_route_is_supported_path(route, request)); 10 | 11 | route->path = "/"; 12 | assert_true(!hs_route_is_supported_path(route, request)); 13 | 14 | route->path = NULL; 15 | request->resource = "/"; 16 | assert_true(hs_route_is_supported_path(route, request)); 17 | 18 | route->path = "/"; 19 | assert_true(hs_route_is_supported_path(route, request)); 20 | 21 | route->path = "/test/test/"; 22 | request->resource = "/test/test/"; 23 | assert_true(hs_route_is_supported_path(route, request)); 24 | 25 | route->path = "/test/test/"; 26 | request->resource = "/test/test/test/"; 27 | assert_true(!hs_route_is_supported_path(route, request)); 28 | 29 | route->is_parent_path = true; 30 | assert_true(hs_route_is_supported_path(route, request)); 31 | 32 | route->is_parent_path = false; 33 | route->path = "/test1/test/"; 34 | request->resource = "/test2/test/"; 35 | 36 | assert_true(!hs_route_is_supported_path(route, request)); 37 | 38 | route->is_parent_path = true; 39 | route->path = ""; 40 | request->resource = "/test2/test/"; 41 | assert_true(!hs_route_is_supported_path(route, request)); 42 | 43 | route->is_parent_path = true; 44 | route->path = "test2/test/"; 45 | request->resource = "test2/test/"; 46 | assert_true(!hs_route_is_supported_path(route, request)); 47 | 48 | route->is_parent_path = false; 49 | route->path = "/test2/test/index.html"; 50 | request->resource = "/test2/test/index.html"; 51 | assert_true(hs_route_is_supported_path(route, request)); 52 | 53 | route->is_parent_path = true; 54 | route->path = "/test2/test/index.html"; 55 | request->resource = "/test2/test/index.html"; 56 | assert_true(hs_route_is_supported_path(route, request)); 57 | 58 | route->is_parent_path = true; 59 | route->path = "/test2/test/index.html/"; 60 | request->resource = "/test2/test/index.html/abc"; 61 | assert_true(hs_route_is_supported_path(route, request)); 62 | 63 | route->is_parent_path = true; 64 | route->path = "/test2/test/index.html"; 65 | request->resource = "/test2/test/index.html/abc"; 66 | assert_true(hs_route_is_supported_path(route, request)); 67 | 68 | route->path = NULL; 69 | request->resource = NULL; 70 | hs_route_release_route(route); 71 | hs_types_http_request_release(request); 72 | } /* test_impl */ 73 | 74 | 75 | int main() 76 | { 77 | test_run(test_impl); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /include/hs_routes_session.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_ROUTES_SESSION_H 2 | #define HS_ROUTES_SESSION_H 3 | 4 | #include "hs_external_libs.h" 5 | #include "hs_route.h" 6 | 7 | #define HS_DEFAULT_SESSION_COOKIE_NAME "sc" 8 | #define HS_DEFAULT_SESSION_STATE_NAME "http-session" 9 | 10 | struct HSSession 11 | { 12 | char *id; 13 | // should only hold string values to enable serialization 14 | struct HashTable *data; 15 | }; 16 | 17 | /** 18 | * The session route enables to initialize a session, store/read it and save its ID 19 | * in a specific cookie to persist it for future requests. 20 | * All provided data will be released when the route is released. 21 | */ 22 | struct HSRoute *hs_routes_session_route_new(char * /* cookie name */, char * /* session name in route context */, char * (*generate_cookie_id)(void * /* context */), void (*modify_cookie)(struct HSCookie *), char * (*to_string)(struct HSSession *), void (*from_string)(struct HSSession *, char *), char * (*read_from_storage)(char * /* ID */, void * /* context */), bool (*write_to_storage)(char * /* ID */, char * /* serialized session */, void * /* context */), void (*release_context)(void *), void * /* context */ 23 | ); 24 | 25 | /** 26 | * The session route with file based storage and ini serialization format. 27 | */ 28 | struct HSRoute *hs_routes_session_route_new_default(void); 29 | 30 | /** 31 | * Creates a new session struct. 32 | */ 33 | struct HSSession *hs_routes_session_new_session(void); 34 | 35 | /** 36 | * Releases the session and all its content. 37 | */ 38 | void hs_routes_session_release_session(struct HSSession *); 39 | 40 | /** 41 | * Simple implementation to the generate_cookie_id function. 42 | */ 43 | char *hs_routes_session_route_generate_cookie_id(void *); 44 | 45 | /** 46 | * ini based implementation. 47 | */ 48 | char *hs_routes_session_route_session_to_string(struct HSSession *); 49 | 50 | /** 51 | * ini based implementation. 52 | */ 53 | void hs_routes_session_route_session_from_string(struct HSSession *, char *); 54 | 55 | /** 56 | * Reads the session content from a file under sessions directory. 57 | * Files are named after session ID. 58 | */ 59 | char *hs_routes_session_route_session_read_from_file_based_storage(char *, void *); 60 | 61 | /** 62 | * Writes the session to a file under sessions directory. 63 | * Files are named after session ID. 64 | */ 65 | bool hs_routes_session_route_session_write_to_file_based_storage(char *, char *, void *); 66 | 67 | /** 68 | * Creates a cookie for the given session. 69 | * All provided data will be automatically released. 70 | */ 71 | struct HSCookie *hs_routes_session_new_cookie(char * /* name */, char * /* session_id */); 72 | 73 | #endif 74 | 75 | -------------------------------------------------------------------------------- /tests/test_hs_routes_security_headers_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | struct HSRoute *route = hs_routes_security_headers_route_new(NULL); 10 | enum HSServeFlowResponse response = route->serve(route, params); 11 | 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 13 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Frame-Options"), "DENY"); 14 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Content-Type-Options"), "nosniff"); 15 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Referrer-Policy"), "same-origin"); 16 | assert_true(hs_types_array_string_pair_get_by_key(params->response->headers, "Content-Policy") == NULL); 17 | assert_true(hs_types_array_string_pair_get_by_key(params->response->headers, "Permissions-Policy") == NULL); 18 | hs_types_serve_flow_params_release(params); 19 | hs_route_release_route(route); 20 | 21 | params = hs_types_serve_flow_params_new(); 22 | struct HSRoutesSecurityResponseHeaders *headers = hs_routes_security_headers_response_headers_new(); 23 | headers->x_frame_options = HS_X_FRAME_OPTIONS_RESPONSE_HEADER_SAMEORIGIN; 24 | headers->x_content_type_options = HS_X_CONTENT_TYPE_OPTIONS_RESPONSE_HEADER_NONE; 25 | headers->referrer_policy = HS_REFERRER_POLICY_RESPONSE_HEADER_ORIGIN_WHEN_CROSS_ORIGIN; 26 | headers->content_security_policy = strdup("test content policy"); 27 | headers->permissions_policy = strdup("test permission policy"); 28 | route = hs_routes_security_headers_route_new(headers); 29 | response = route->serve(route, params); 30 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 31 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Frame-Options"), "SAMEORIGIN"); 32 | assert_true(hs_types_array_string_pair_get_by_key(params->response->headers, "X-Content-Type-Options") == NULL); 33 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Referrer-Policy"), "origin-when-cross-origin"); 34 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Content-Security-Policy"), "test content policy"); 35 | assert_string_equal(hs_types_array_string_pair_get_by_key(params->response->headers, "Permissions-Policy"), "test permission policy"); 36 | hs_types_serve_flow_params_release(params); 37 | hs_route_release_route(route); 38 | } /* test_impl */ 39 | 40 | 41 | int main() 42 | { 43 | test_run(test_impl); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /tests/test_types_release_serve_flow_params.c: -------------------------------------------------------------------------------- 1 | #include "stringfn.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void _test_release_both(char *key, void *value) 7 | { 8 | hs_io_free(key); 9 | hs_io_free(value); 10 | } 11 | 12 | 13 | void _test_set_cookies(struct HSCookies *cookies) 14 | { 15 | for (size_t index = 0; index < 3; index++) 16 | { 17 | struct HSCookie *cookie = hs_types_cookie_new(); 18 | cookie->name = stringfn_new_empty_string(); 19 | cookie->value = stringfn_new_empty_string(); 20 | hs_types_cookies_add(cookies, cookie); 21 | } 22 | } 23 | 24 | 25 | void _test_set_strings(struct HSArrayStringPair *array) 26 | { 27 | for (size_t index = 0; index < 5; index++) 28 | { 29 | bool added = hs_types_array_string_pair_add(array, stringfn_new_empty_string(), stringfn_new_empty_string()); 30 | assert_true(added); 31 | } 32 | } 33 | 34 | 35 | void _test_set_data(struct HashTable *data) 36 | { 37 | for (size_t index = 0; index < 5; index++) 38 | { 39 | bool added = hashtable_insert(data, stringfn_new_empty_string(), strdup("test"), _test_release_both); 40 | assert_true(added); 41 | } 42 | } 43 | 44 | 45 | void _test_release_callback(struct HSPostResponseCallback *callback) 46 | { 47 | hs_io_free(callback->context); 48 | } 49 | 50 | 51 | void test_impl() 52 | { 53 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 54 | 55 | hs_types_serve_flow_params_release(params); 56 | 57 | params = hs_types_serve_flow_params_new(); 58 | 59 | _test_set_cookies(params->request->cookies); 60 | _test_set_cookies(params->response->cookies); 61 | 62 | _test_set_strings(params->request->headers); 63 | _test_set_strings(params->response->headers); 64 | _test_set_strings(params->route_state->string_pairs); 65 | _test_set_data(params->route_state->data); 66 | 67 | params->request->domain = stringfn_new_empty_string(); 68 | params->request->resource = stringfn_new_empty_string(); 69 | params->request->query_string = stringfn_new_empty_string(); 70 | params->request->user_agent = stringfn_new_empty_string(); 71 | params->request->authorization = stringfn_new_empty_string(); 72 | params->request->payload = hs_types_http_request_new_payload(NULL); 73 | 74 | params->response->content_string = stringfn_new_empty_string(); 75 | params->response->content_file = stringfn_new_empty_string(); 76 | 77 | struct HSPostResponseCallback *callback = hs_types_post_response_callback_new(); 78 | callback->context = stringfn_new_empty_string(); 79 | callback->release = _test_release_callback; 80 | hs_types_post_response_callbacks_add(params->callbacks, callback); 81 | 82 | params->router_state->base_path = stringfn_new_empty_string(); 83 | 84 | hs_types_serve_flow_params_release(params); 85 | } 86 | 87 | 88 | int main() 89 | { 90 | test_run(test_impl); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/hs_routes_redirection.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_routes_redirection.h" 3 | #include 4 | #include 5 | 6 | static enum HSServeFlowResponse _hs_routes_redirection_serve(struct HSRoute *, struct HSServeFlowParams *); 7 | static void _hs_routes_redirection_release(struct HSRoute *); 8 | 9 | struct HsRoutesRedirectionContext 10 | { 11 | char *to_path; 12 | bool temporary_redirect; 13 | }; 14 | 15 | struct HSRoute *hs_routes_redirection_route_new(char *from_path, char *to_path) 16 | { 17 | return(hs_routes_redirection_route_new_with_options(from_path, to_path, true)); 18 | } 19 | 20 | struct HSRoute *hs_routes_redirection_route_new_with_options(char *from_path, char *to_path, bool temporary_redirect) 21 | { 22 | if (from_path == NULL || to_path == NULL) 23 | { 24 | return(NULL); 25 | } 26 | 27 | struct HsRoutesRedirectionContext *context = malloc(sizeof(struct HsRoutesRedirectionContext)); 28 | context->to_path = to_path; 29 | context->temporary_redirect = temporary_redirect; 30 | 31 | struct HSRoute *route = hs_route_new(); 32 | route->path = from_path; 33 | route->is_get = true; 34 | route->serve = _hs_routes_redirection_serve; 35 | route->release = _hs_routes_redirection_release; 36 | route->extension = context; 37 | 38 | return(route); 39 | } 40 | 41 | 42 | bool hs_routes_redirection_set_header_and_status_code(struct HSServeFlowParams *params, char *path, bool temporary_redirect) 43 | { 44 | if (params == NULL || params->response == NULL || params->response->headers == NULL || path == NULL) 45 | { 46 | return(false); 47 | } 48 | 49 | params->response->code = HS_HTTP_RESPONSE_CODE_PERMANENT_REDIRECT; 50 | if (temporary_redirect) 51 | { 52 | params->response->code = HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT; 53 | } 54 | 55 | hs_types_array_string_pair_add(params->response->headers, strdup("Location"), strdup(path)); 56 | 57 | return(true); 58 | } 59 | 60 | static enum HSServeFlowResponse _hs_routes_redirection_serve(struct HSRoute *route, struct HSServeFlowParams *params) 61 | { 62 | if (route == NULL || route->extension == NULL) 63 | { 64 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 65 | } 66 | 67 | struct HsRoutesRedirectionContext *context = (struct HsRoutesRedirectionContext *)route->extension; 68 | if (!hs_routes_redirection_set_header_and_status_code(params, context->to_path, context->temporary_redirect)) 69 | { 70 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 71 | } 72 | 73 | return(HS_SERVE_FLOW_RESPONSE_DONE); 74 | } 75 | 76 | 77 | static void _hs_routes_redirection_release(struct HSRoute *route) 78 | { 79 | if (route == NULL || route->extension == NULL) 80 | { 81 | return; 82 | } 83 | 84 | struct HsRoutesRedirectionContext *context = (struct HsRoutesRedirectionContext *)route->extension; 85 | 86 | hs_io_free(context->to_path); 87 | hs_io_free(context); 88 | } 89 | 90 | -------------------------------------------------------------------------------- /tests/test_router_serve_binary_file.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 11 | { 12 | assert_true(route != NULL); 13 | assert_true(params != NULL); 14 | assert_true(fsio_file_exists(TEST_BINARY_FILE)); 15 | 16 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 17 | params->response->mime_type = HS_MIME_TYPE_IMAGE_PNG; 18 | 19 | params->response->content_file = strdup(TEST_BINARY_FILE); 20 | 21 | return(HS_SERVE_FLOW_RESPONSE_DONE); 22 | } /* _test_serve */ 23 | 24 | 25 | void test_impl() 26 | { 27 | struct HSRouter *router = hs_router_new(); 28 | 29 | struct HSRoute *route = hs_route_new(); 30 | 31 | route->path = strdup("/test"); 32 | route->serve = _test_serve; 33 | route->is_get = true; 34 | hs_router_add_route(router, route); 35 | 36 | struct HSHttpRequest *request = hs_types_http_request_new(); 37 | request->resource = strdup("/test"); 38 | request->method = HS_HTTP_METHOD_GET; 39 | request->connection = HS_CONNECTION_TYPE_CLOSE; 40 | 41 | char *filename = "./test_router_serve_file.png"; 42 | 43 | fsio_create_empty_file(filename); 44 | test_generate_binary_file(); 45 | int socket = open(filename, O_WRONLY); 46 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 47 | 48 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 49 | connection_state->socket = hssocket; 50 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new_pre_populated(request); 51 | params->connection_state = connection_state; 52 | 53 | bool done = hs_router_serve(router, params); 54 | assert_true(done); 55 | 56 | hs_socket_close_and_release(hssocket); 57 | 58 | char *content = fsio_read_binary_file(filename); 59 | char *png_content = fsio_read_binary_file(TEST_BINARY_FILE); 60 | 61 | struct StringBuffer *buffer = stringbuffer_new(); 62 | stringbuffer_append_string(buffer, "HTTP/1.1 200 200\r\n" 63 | "Connection: close\r\n" 64 | "Content-Type: image/png\r\n" 65 | "Content-Length: 500\r\n" 66 | "\r\n"); 67 | stringbuffer_append_string(buffer, png_content); 68 | char *expected_content = stringbuffer_to_string(buffer); 69 | stringbuffer_release(buffer); 70 | hs_io_free(png_content); 71 | 72 | assert_string_equal(content, expected_content); 73 | 74 | hs_io_free(expected_content); 75 | hs_io_free(content); 76 | fsio_remove(filename); 77 | fsio_remove(TEST_BINARY_FILE); 78 | hs_types_serve_flow_params_release(params); 79 | hs_types_server_connection_state_release(connection_state); 80 | hs_router_release(router); 81 | } /* test_impl */ 82 | 83 | 84 | int main() 85 | { 86 | test_run(test_impl); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /tests/test_parser_parse_query_string.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSArrayStringPair *pairs = hs_parser_parse_query_string(NULL); 7 | 8 | assert_true(pairs == NULL); 9 | 10 | pairs = hs_parser_parse_query_string(""); 11 | assert_true(pairs == NULL); 12 | 13 | pairs = hs_parser_parse_query_string("abc123"); 14 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 15 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 16 | assert_true(hs_types_array_string_pair_get_value(pairs, 0) == NULL); 17 | hs_types_array_string_pair_release(pairs); 18 | 19 | pairs = hs_parser_parse_query_string("abc123&"); 20 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 21 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 22 | assert_true(hs_types_array_string_pair_get_value(pairs, 0) == NULL); 23 | hs_types_array_string_pair_release(pairs); 24 | 25 | pairs = hs_parser_parse_query_string("abc123="); 26 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 27 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 28 | assert_true(hs_types_array_string_pair_get_value(pairs, 0) == NULL); 29 | hs_types_array_string_pair_release(pairs); 30 | 31 | pairs = hs_parser_parse_query_string("abc123=&"); 32 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 33 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 34 | assert_true(hs_types_array_string_pair_get_value(pairs, 0) == NULL); 35 | hs_types_array_string_pair_release(pairs); 36 | 37 | pairs = hs_parser_parse_query_string("abc123=A123"); 38 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 39 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 40 | assert_string_equal(hs_types_array_string_pair_get_value(pairs, 0), "A123"); 41 | hs_types_array_string_pair_release(pairs); 42 | 43 | pairs = hs_parser_parse_query_string("abc123=A123&"); 44 | assert_num_equal(hs_types_array_string_pair_count(pairs), 1); 45 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "abc123"); 46 | assert_string_equal(hs_types_array_string_pair_get_value(pairs, 0), "A123"); 47 | hs_types_array_string_pair_release(pairs); 48 | 49 | pairs = hs_parser_parse_query_string("key1=value1&key2=value2&key3=value3"); 50 | assert_num_equal(hs_types_array_string_pair_count(pairs), 3); 51 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 0), "key1"); 52 | assert_string_equal(hs_types_array_string_pair_get_value(pairs, 0), "value1"); 53 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 1), "key2"); 54 | assert_string_equal(hs_types_array_string_pair_get_value(pairs, 1), "value2"); 55 | assert_string_equal(hs_types_array_string_pair_get_key(pairs, 2), "key3"); 56 | assert_string_equal(hs_types_array_string_pair_get_value(pairs, 2), "value3"); 57 | hs_types_array_string_pair_release(pairs); 58 | } /* test_impl */ 59 | 60 | 61 | int main() 62 | { 63 | test_run(test_impl); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /tests/test_parser_parse_request.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void test_impl() 11 | { 12 | char *raw = "POST /testpost HTTP/1.0\r\n" 13 | "Host: MyHost\r\n" 14 | "User-Agent: MyBrowser\r\n" 15 | "Authorization: Basic 123\r\n" 16 | "Connection: close\r\n" 17 | "Content-Length: 11\r\n" 18 | "Content-Type: application/x-www-form-urlencoded\r\n" 19 | "\r\n" 20 | "12345\n67890\n\n"; 21 | 22 | char *filename = "./test_parser_parse_request.txt"; 23 | 24 | fsio_write_text_file(filename, raw); 25 | 26 | int socket = open(filename, O_RDONLY); 27 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 28 | struct HSHttpRequest *request = hs_parser_parse_request(hssocket); 29 | 30 | assert_string_equal(request->domain, ""); 31 | assert_string_equal(request->resource, "/testpost"); 32 | assert_true(!request->ssl); 33 | assert_num_equal(request->port, -1); 34 | assert_num_equal(request->method, HS_HTTP_METHOD_POST); 35 | assert_string_equal(request->user_agent, "MyBrowser"); 36 | assert_string_equal(request->authorization, "Basic 123"); 37 | assert_true(request->query_string == NULL); 38 | assert_num_equal(request->content_length, 11); 39 | assert_true(request->payload != NULL); 40 | 41 | assert_num_equal(hs_types_array_string_pair_count(request->headers), 6); 42 | size_t index = 0; 43 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "host"); 44 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "MyHost"); 45 | index++; 46 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "user-agent"); 47 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "MyBrowser"); 48 | index++; 49 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "authorization"); 50 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "Basic 123"); 51 | index++; 52 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "connection"); 53 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "close"); 54 | index++; 55 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "content-length"); 56 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "11"); 57 | index++; 58 | assert_string_equal(hs_types_array_string_pair_get_key(request->headers, index), "content-type"); 59 | assert_string_equal(hs_types_array_string_pair_get_value(request->headers, index), "application/x-www-form-urlencoded"); 60 | 61 | hs_types_http_request_release(request); 62 | hs_socket_close_and_release(hssocket); 63 | fsio_remove(filename); 64 | } /* test_impl */ 65 | 66 | 67 | int main() 68 | { 69 | test_run(test_impl); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/test_hs_routes_static_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | struct HSRoute *route = hs_routes_static_route_new(strdup("test\nsecond line"), HS_MIME_TYPE_TEXT_XML); 10 | enum HSServeFlowResponse response = route->serve(route, params); 11 | 12 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 13 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 14 | assert_string_equal(params->response->content_string, "test\nsecond line"); 15 | assert_num_equal(params->response->mime_type, HS_MIME_TYPE_TEXT_XML); 16 | hs_route_release_route(route); 17 | hs_types_serve_flow_params_release(params); 18 | 19 | params = hs_types_serve_flow_params_new(); 20 | route = hs_routes_static_text_route_new(strdup("test\nsecond line")); 21 | response = route->serve(route, params); 22 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 23 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 24 | assert_string_equal(params->response->content_string, "test\nsecond line"); 25 | assert_num_equal(params->response->mime_type, HS_MIME_TYPE_TEXT_PLAIN); 26 | hs_route_release_route(route); 27 | hs_types_serve_flow_params_release(params); 28 | 29 | params = hs_types_serve_flow_params_new(); 30 | route = hs_routes_static_html_route_new(strdup("test\nsecond line")); 31 | response = route->serve(route, params); 32 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 33 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 34 | assert_string_equal(params->response->content_string, "test\nsecond line"); 35 | assert_num_equal(params->response->mime_type, HS_MIME_TYPE_TEXT_HTML); 36 | hs_route_release_route(route); 37 | hs_types_serve_flow_params_release(params); 38 | 39 | params = hs_types_serve_flow_params_new(); 40 | route = hs_routes_static_css_route_new(strdup("test\nsecond line")); 41 | response = route->serve(route, params); 42 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 43 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 44 | assert_string_equal(params->response->content_string, "test\nsecond line"); 45 | assert_num_equal(params->response->mime_type, HS_MIME_TYPE_TEXT_CSS); 46 | hs_route_release_route(route); 47 | hs_types_serve_flow_params_release(params); 48 | 49 | params = hs_types_serve_flow_params_new(); 50 | route = hs_routes_static_js_route_new(strdup("test\nsecond line")); 51 | response = route->serve(route, params); 52 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_DONE); 53 | assert_num_equal(params->response->code, HS_HTTP_RESPONSE_CODE_OK); 54 | assert_string_equal(params->response->content_string, "test\nsecond line"); 55 | assert_num_equal(params->response->mime_type, HS_MIME_TYPE_TEXT_JAVASCRIPT); 56 | hs_route_release_route(route); 57 | hs_types_serve_flow_params_release(params); 58 | } /* test_impl */ 59 | 60 | 61 | int main() 62 | { 63 | test_run(test_impl); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/hs_routes_ratelimit.c: -------------------------------------------------------------------------------- 1 | #include "hs_constants.h" 2 | #include "hs_routes_common.h" 3 | #include "hs_routes_ratelimit.h" 4 | #include 5 | 6 | static struct HSRoute *_hs_routes_ratelimit_base_route_new(size_t); 7 | static enum HSServeFlowResponse _hs_routes_ratelimit_max_connection_requests_route_serve(struct HSRoute *, struct HSServeFlowParams *); 8 | static enum HSServeFlowResponse _hs_routes_ratelimit_max_connection_time_route_serve(struct HSRoute *, struct HSServeFlowParams *); 9 | 10 | struct HSRoutesRateLimitValue 11 | { 12 | size_t value; 13 | }; 14 | 15 | struct HSRoute *hs_routes_ratelimit_max_connection_requests_route_new(size_t max_requests) 16 | { 17 | struct HSRoute *route = _hs_routes_ratelimit_base_route_new(max_requests); 18 | 19 | route->serve = _hs_routes_ratelimit_max_connection_requests_route_serve; 20 | 21 | return(route); 22 | } 23 | 24 | struct HSRoute *hs_routes_ratelimit_max_connection_time_route_new(size_t max_time_seconds) 25 | { 26 | struct HSRoute *route = _hs_routes_ratelimit_base_route_new(max_time_seconds); 27 | 28 | route->serve = _hs_routes_ratelimit_max_connection_time_route_serve; 29 | 30 | return(route); 31 | } 32 | 33 | static struct HSRoute *_hs_routes_ratelimit_base_route_new(size_t value) 34 | { 35 | struct HSRoute *route = hs_routes_common_serve_all_route_new(); 36 | 37 | route->release = hs_routes_common_extension_release; 38 | 39 | struct HSRoutesRateLimitValue *extension = malloc(sizeof(struct HSRoutesRateLimitValue)); 40 | extension->value = value; 41 | route->extension = extension; 42 | 43 | return(route); 44 | } 45 | 46 | static enum HSServeFlowResponse _hs_routes_ratelimit_max_connection_requests_route_serve(struct HSRoute *route, struct HSServeFlowParams *params) 47 | { 48 | if ( route == NULL 49 | || params == NULL 50 | || params->request == NULL 51 | || params->connection_state == NULL 52 | || route->extension == NULL 53 | ) 54 | { 55 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 56 | } 57 | 58 | struct HSRoutesRateLimitValue *extension = (struct HSRoutesRateLimitValue *)route->extension; 59 | if (extension->value < 2 || params->connection_state->request_counter >= extension->value) 60 | { 61 | params->request->connection = HS_CONNECTION_TYPE_CLOSE; 62 | } 63 | 64 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 65 | } 66 | 67 | static enum HSServeFlowResponse _hs_routes_ratelimit_max_connection_time_route_serve(struct HSRoute *route, struct HSServeFlowParams *params) 68 | { 69 | if ( route == NULL 70 | || params == NULL 71 | || params->request == NULL 72 | || params->connection_state == NULL 73 | || route->extension == NULL 74 | ) 75 | { 76 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 77 | } 78 | 79 | struct HSRoutesRateLimitValue *extension = (struct HSRoutesRateLimitValue *)route->extension; 80 | time_t connection_time = time(NULL) - params->connection_state->creation_time; 81 | if (extension->value < 1 || connection_time < 0 || (size_t)connection_time >= extension->value) 82 | { 83 | params->request->connection = HS_CONNECTION_TYPE_CLOSE; 84 | } 85 | 86 | return(HS_SERVE_FLOW_RESPONSE_CONTINUE); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /tests/test_parser_parse_cookie_header.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSCookies *cookies = hs_types_cookies_new(); 7 | bool done = hs_parser_parse_cookie_header(cookies, "name=value"); 8 | 9 | assert_true(done); 10 | assert_num_equal(hs_types_cookies_count(cookies), 1); 11 | struct HSCookie *cookie = hs_types_cookies_get(cookies, 0); 12 | assert_string_equal(cookie->name, "name"); 13 | assert_string_equal(cookie->value, "value"); 14 | hs_types_cookies_release(cookies); 15 | 16 | cookies = hs_types_cookies_new(); 17 | done = hs_parser_parse_cookie_header(cookies, "name=value;"); 18 | 19 | assert_true(done); 20 | assert_num_equal(hs_types_cookies_count(cookies), 1); 21 | cookie = hs_types_cookies_get(cookies, 0); 22 | assert_string_equal(cookie->name, "name"); 23 | assert_string_equal(cookie->value, "value"); 24 | hs_types_cookies_release(cookies); 25 | 26 | cookies = hs_types_cookies_new(); 27 | done = hs_parser_parse_cookie_header(cookies, "name=value; "); 28 | 29 | assert_true(done); 30 | assert_num_equal(hs_types_cookies_count(cookies), 1); 31 | cookie = hs_types_cookies_get(cookies, 0); 32 | assert_string_equal(cookie->name, "name"); 33 | assert_string_equal(cookie->value, "value"); 34 | hs_types_cookies_release(cookies); 35 | 36 | cookies = hs_types_cookies_new(); 37 | done = hs_parser_parse_cookie_header(cookies, "name=value; name2=value2; name3=value3"); 38 | 39 | assert_true(done); 40 | assert_num_equal(hs_types_cookies_count(cookies), 3); 41 | cookie = hs_types_cookies_get(cookies, 0); 42 | assert_string_equal(cookie->name, "name"); 43 | assert_string_equal(cookie->value, "value"); 44 | cookie = hs_types_cookies_get(cookies, 1); 45 | assert_string_equal(cookie->name, "name2"); 46 | assert_string_equal(cookie->value, "value2"); 47 | cookie = hs_types_cookies_get(cookies, 2); 48 | assert_string_equal(cookie->name, "name3"); 49 | assert_string_equal(cookie->value, "value3"); 50 | hs_types_cookies_release(cookies); 51 | 52 | cookies = hs_types_cookies_new(); 53 | done = hs_parser_parse_cookie_header(cookies, "name=value; name2=value2; name3="); 54 | 55 | assert_true(done); 56 | assert_num_equal(hs_types_cookies_count(cookies), 2); 57 | cookie = hs_types_cookies_get(cookies, 0); 58 | assert_string_equal(cookie->name, "name"); 59 | assert_string_equal(cookie->value, "value"); 60 | cookie = hs_types_cookies_get(cookies, 1); 61 | assert_string_equal(cookie->name, "name2"); 62 | assert_string_equal(cookie->value, "value2"); 63 | hs_types_cookies_release(cookies); 64 | 65 | cookies = hs_types_cookies_new(); 66 | done = hs_parser_parse_cookie_header(cookies, NULL); 67 | assert_true(!done); 68 | hs_types_cookies_release(cookies); 69 | 70 | cookies = hs_types_cookies_new(); 71 | done = hs_parser_parse_cookie_header(cookies, "name"); 72 | assert_true(!done); 73 | hs_types_cookies_release(cookies); 74 | 75 | cookies = hs_types_cookies_new(); 76 | done = hs_parser_parse_cookie_header(cookies, "name="); 77 | assert_true(!done); 78 | hs_types_cookies_release(cookies); 79 | 80 | done = hs_parser_parse_cookie_header(NULL, "name=value; name2=value2; name3="); 81 | assert_true(!done); 82 | } /* test_impl */ 83 | 84 | 85 | int main() 86 | { 87 | test_run(test_impl); 88 | } 89 | 90 | -------------------------------------------------------------------------------- /tests/test_router_write_common_response_header.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | void test_impl() 12 | { 13 | struct HSArrayStringPair *headers = NULL; 14 | struct HSCookies *cookies = NULL; 15 | struct StringBuffer *buffer = hs_router_write_common_response_header(HS_HTTP_RESPONSE_CODE_NOT_FOUND, headers, cookies, false); 16 | char *text = stringbuffer_to_string(buffer); 17 | 18 | stringbuffer_release(buffer); 19 | assert_string_equal(text, "HTTP/1.1 404 404\r\nConnection: keep-alive\r\n"); 20 | hs_io_free(text); 21 | hs_types_array_string_pair_release(headers); 22 | hs_types_cookies_release(cookies); 23 | 24 | headers = hs_types_array_string_pair_new(); 25 | cookies = hs_types_cookies_new(); 26 | buffer = hs_router_write_common_response_header(HS_HTTP_RESPONSE_CODE_OK, headers, cookies, true); 27 | text = stringbuffer_to_string(buffer); 28 | stringbuffer_release(buffer); 29 | assert_string_equal(text, "HTTP/1.1 200 200\r\nConnection: close\r\n"); 30 | hs_io_free(text); 31 | hs_types_array_string_pair_release(headers); 32 | hs_types_cookies_release(cookies); 33 | 34 | headers = hs_types_array_string_pair_new(); 35 | cookies = hs_types_cookies_new(); 36 | hs_types_array_string_pair_add(headers, strdup("header1"), strdup("value1")); 37 | hs_types_array_string_pair_add(headers, strdup("header2"), strdup("value2")); 38 | hs_types_array_string_pair_add(headers, strdup("header3"), strdup("value3")); 39 | struct HSCookie *cookie = hs_types_cookie_new(); 40 | cookie->name = strdup("c1"); 41 | cookie->value = strdup("v1"); 42 | cookie->expires = strdup("1 1 1980"); 43 | cookie->max_age = 200; 44 | cookie->secure = true; 45 | cookie->http_only = true; 46 | cookie->domain = strdup("mydomain"); 47 | cookie->path = strdup("/somepath"); 48 | cookie->same_site = HS_COOKIE_SAME_SITE_NONE; 49 | hs_types_cookies_add(cookies, cookie); 50 | cookie = hs_types_cookie_new(); 51 | cookie->name = strdup("c2"); 52 | cookie->value = strdup("v2"); 53 | cookie->same_site = HS_COOKIE_SAME_SITE_STRICT; 54 | hs_types_cookies_add(cookies, cookie); 55 | cookie = hs_types_cookie_new(); 56 | cookie->name = strdup("c3"); 57 | cookie->value = strdup("v3"); 58 | hs_types_cookies_add(cookies, cookie); 59 | buffer = hs_router_write_common_response_header(HS_HTTP_RESPONSE_CODE_FORBIDDEN, headers, cookies, true); 60 | text = stringbuffer_to_string(buffer); 61 | stringbuffer_release(buffer); 62 | assert_string_equal(text, "HTTP/1.1 403 403\r\n" 63 | "header1: value1\r\n" 64 | "header2: value2\r\n" 65 | "header3: value3\r\n" 66 | "Set-Cookie: c1=v1; Expires=1 1 1980; Max-Age=200; Secure; HttpOnly; Domain=mydomain; Path=/somepath; SameSite=None\r\n" 67 | "Set-Cookie: c2=v2; SameSite=Strict\r\n" 68 | "Set-Cookie: c3=v3; SameSite=Lax\r\n" 69 | "Connection: close\r\n"); 70 | hs_io_free(text); 71 | hs_types_array_string_pair_release(headers); 72 | hs_types_cookies_release(cookies); 73 | } /* test_impl */ 74 | 75 | 76 | int main() 77 | { 78 | test_run(test_impl); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /examples/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC+iz36Bwvu6rNr 3 | 1NF05tE1EvhpeJYsaIOHumQa3rNWCRZjqHVLSxlHLPPkMXu2oJyM7MngmezjHE0h 4 | b04Qcv12s3pQb/+CkMmGwrgha90rKUbqF1ezemuiYOpHS7hqKovn48utsF8nO33J 5 | 9eRjEq7BihUYR4iMknp++2y18q6K+xe3GTfDdduK+z2IOXOxlANDHW8jS1WvR/GN 6 | d3u+rAHbnoG94QErG1KHD083+7wIWPuKREejc6Iiy/yb0Yz3PyeHGiKoDFEr+jO9 7 | JLQHyu3q5DnHV+RgXefIBy08/iWeg2snv8Q8QmJK9u6mBpAif2Mm7ggMSRAyJsYG 8 | aeSNdYWBqgxcwr3sr3kez6X+MJxYl2auGz9wGkV2ui/pUXENXcVT+zQg8pIR1ice 9 | kBG1sOdl+1fHX0DUPbjP0gh0yVCL7GdOLJTd628Rq3kYaDGve9jzeBVh73D1Pf/G 10 | 2qckucceVILBYF2Ol6rvdJ30vUcTDLAISpWqj/nMqxEfUH6ePf0jD2CC6VzHBKeL 11 | MX3+KYCAbctUQRFOp8GH8bDD/hp4RlE9VUO7GMRUvj3NzB6g/oRxYxEaB4HAX8jV 12 | hBAtwcSN5iRTmsdrZ6x+n5htOvYhpFVuFSE4M278NSSTHko+y/P+Hsg6Kv81k3Q3 13 | Q+Rjjn7TWNkoE8DLkfBrhDX353b+IQIDAQABAoICAA6ZImVudsaWKqrfQjDfojWb 14 | v3ZtK6w5UaJrcFHyr/Cuf42hEWN0XHpsgj8AX4cNZRES7yCLCnAX8fzWxzdRaAPZ 15 | GkzhV8UMDLSLZIoC9o3g+rLimPCCN47YN/rAXV+c5N0eQbOL6K5by3ZWpWnFu6fO 16 | SXFzwY1rh4EFCD4kWqUBOb7oarJJ7EkImJ1AMZHwUhOpRKOCmQz1yRbaiZuBGB9Y 17 | VH5Up79Ct57rWFlkqvJExvi/5HEsYcaitDbH5jHnH8MlCHSxhOKGp8Ic9sIGFRZC 18 | 5/5SyIdtil1z105t9jnfvazs7z7iox81LfGAXTiXY+ROpkUyr8/30oIZSM5hq5my 19 | msDYBefT1khUm17MU3NbsnVyhiR8DeIZcHMKpm8sKkqmkAhxl4LCahAAdqhOovkJ 20 | SNd925LG/U4gEU/iD0DVC5MwV7MJR8S/lJNoW1zs1hCao99nH3DmhZ1eGwCAXLWQ 21 | XfyB19m+WTtbVUdic/bo7sR3Jl8anZsPx6bGCEVgQFVR4YVFnz22N67/I5NKYTSU 22 | Q4WrgOzjPCXN4aZiKjUtCfpTjTvuCj9OgMjvXPnTTZg1mzhpWjhcV2DHc6Df3RZb 23 | s6hZTe2aEdaVRr6ETkaKVAD7JdY06hiVreFLRRslncwNFe5ZnHFXJubqayGnM2pz 24 | UoiUlSPrA8iltntFVtfBAoIBAQDt+/MAzEsufwHXpDQMhl4YlrNttuTj42JdNPT/ 25 | hTquyIzRQBIxJ3JSpTk+37WZ4eB8sARJMAwVYH+IYszrQFKbkMY43K6w6OSmt+4x 26 | SHJ5S2t2zrJbsJJOqsv9eJMpqjbLfubDowq0gjAJGtB6ePBg4h/rsWiOjepCrFfh 27 | ZDfHg40WFUmkKNQWKqUYo9f2YykypOTV4+DQeTgxy5jFBI0N7kebdcrMKT1r/BZ3 28 | Xa9U8rbxzb6JAR+x5UbYM71YMLDhoXgjHtkovEYiv6XNfvWBwxk3osLaqMMhHkA+ 29 | B8nKCVK8Qb5vALSl9nrYEsmZG0DIxoOlppi20kjwoxcivmsJAoIBAQDM9+rDYl5E 30 | P0rcl4OxqTDWuKvax1uh8c0Efembn1ptpp8LogVkrPATPuEO+2f77w2qNBYoUK+N 31 | wGvAA5L+yhKnPQAuTtQRIjDtO8lYRH0Wy3Tme/bA7tVZWzSyO6QabUMNClXnm8qy 32 | UON9ORLWZDgkMsnVmFOyw9n5t86JgvOQpUBomqbh9VJAc/ciqcrv8OIXtbV4z7J1 33 | sZhnKBmKU7JBqHxnR99OldJmsoaHsW9G/f8CF49EPpCKAhd5noaQ8MycZbDYXVIo 34 | IBAm6z8Wv8i7mnk13Jx3More+chW/DpwWtr17SWKZT4bHHOY1jzKWna84+SqlfeA 35 | H4dmsxoV74hZAoIBAQCyN3k04chzW11P1YG812rj0wcvZEVoe0GQNzY5m9XkSf9a 36 | PDbjZHzycxS5huICCOsJtO6guEB0Sb13rzVx/steAD7RGkIf7Wg9iYPwmCZRuy8Q 37 | 250pmMMX8kOs33ylOPirz66UVyaPIudCCjiRXrv0caIh6Ms55gDGSTXObjRvTo57 38 | ORnau1CIAQt9tkTmGEAj0uQjYPFcbUqtz5/p+ox/IC+ztbRWy5NZqIBSh2/jl5d0 39 | 4PSMuLC5kc9J3QUwY9YyQa8r7dAF92g2YXqStz63HBloVpTBFpx50qOdUEvG5tB0 40 | dm9bxRWDK1+3K+qYo5YSRMcVZ/r4/eTtf9S1ig+xAoIBAQCVpuWHpojBN50HFrEW 41 | xDteaxM+95PV1+AiyXQJJf42VROGwNNDQGlBBAX6lGDFtzK49LLA2Lh0vOtvFKz8 42 | zrlz1bwOE4wOvnIOpDpL8iWKwR4nMpdlInwvc8Iz6AvXJ7NvD/7MzhjevOR8B3HT 43 | ivTaqSJoi4GA4jKCe0uXZz9CewVd2pzCGgXl/UWRz3rWWYAaY5eOD8dy2yo1Fzge 44 | oSFjgwrhtb47eHRYezxyUoC2yrin5F+8KhoKSaCuMap6pPYfsre+IdTcaxyf7d7w 45 | UzLSluStCNhMx2BRkPpPWHWqlwuLx4xDUcxYqxtTmghkjkxnedT5H+nHaD3KJDaF 46 | kZBxAoIBABty+SumG2DfTr/9Oin2etplOzWdpWxkldyyRMZryxcuLS6BhaOy0teZ 47 | DY5S9E8ef3gEkhIT2fRdlgr6aX163/o0yc+4rlMnNIK2+VbXgYjFbjXAmT9Ymkvh 48 | 37OFa6V+8/6fukH8jvNtl+GsNF1iusOyLiMx5jdhbxI8gh2ROAgQKolvBWa0ibEr 49 | /ejMfkzSQoGHp4Vmga1NrFDvebG0BygGmaU8W9mhJatUPva5SxMf3Sa+X8fxXFG3 50 | sBDViGom6F3IF9FXx6TqF4wSGxVX/r5xFfnmJ+v3npTlBILEfXua+P4duoWeK7By 51 | Y8rGZ2/YVCYooJJK0z8ATz+mZmvksPU= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /tests/test_hs_routes_ratelimit_max_connection_requests_route_serve.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "test.h" 3 | #include 4 | 5 | 6 | void test_impl() 7 | { 8 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 9 | 10 | params->connection_state = hs_types_server_connection_state_new(); 11 | 12 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 13 | params->connection_state->request_counter = 0; 14 | struct HSRoute *route = hs_routes_ratelimit_max_connection_requests_route_new(0); 15 | enum HSServeFlowResponse response = route->serve(route, params); 16 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 17 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 18 | hs_route_release_route(route); 19 | 20 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 21 | params->connection_state->request_counter = 0; 22 | route = hs_routes_ratelimit_max_connection_requests_route_new(1); 23 | response = route->serve(route, params); 24 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 25 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 26 | hs_route_release_route(route); 27 | 28 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 29 | params->connection_state->request_counter = 0; 30 | route = hs_routes_ratelimit_max_connection_requests_route_new(2); 31 | response = route->serve(route, params); 32 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 33 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_KEEP_ALIVE); 34 | hs_route_release_route(route); 35 | 36 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 37 | params->connection_state->request_counter = 1; 38 | route = hs_routes_ratelimit_max_connection_requests_route_new(2); 39 | response = route->serve(route, params); 40 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 41 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_KEEP_ALIVE); 42 | hs_route_release_route(route); 43 | 44 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 45 | params->connection_state->request_counter = 2; 46 | route = hs_routes_ratelimit_max_connection_requests_route_new(2); 47 | response = route->serve(route, params); 48 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 49 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 50 | hs_route_release_route(route); 51 | 52 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 53 | params->connection_state->request_counter = 3; 54 | route = hs_routes_ratelimit_max_connection_requests_route_new(2); 55 | response = route->serve(route, params); 56 | assert_num_equal(response, HS_SERVE_FLOW_RESPONSE_CONTINUE); 57 | assert_num_equal(params->request->connection, HS_CONNECTION_TYPE_CLOSE); 58 | hs_route_release_route(route); 59 | 60 | hs_types_server_connection_state_release(params->connection_state); 61 | hs_types_serve_flow_params_release(params); 62 | } /* test_impl */ 63 | 64 | 65 | int main() 66 | { 67 | test_run(test_impl); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/hs_route.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_route.h" 3 | #include "stringfn.h" 4 | #include 5 | #include 6 | 7 | struct HSRoute *hs_route_new(void) 8 | { 9 | struct HSRoute *route = malloc(sizeof(struct HSRoute)); 10 | 11 | route->path = NULL; 12 | route->is_parent_path = false; 13 | hs_route_set_all_methods(route, false); 14 | route->serve = NULL; 15 | route->release = NULL; 16 | route->extension = NULL; 17 | 18 | return(route); 19 | } 20 | 21 | 22 | void hs_route_release_route(struct HSRoute *route) 23 | { 24 | if (route == NULL) 25 | { 26 | return; 27 | } 28 | 29 | if (route->release != NULL) 30 | { 31 | route->release(route); 32 | } 33 | 34 | hs_io_free(route->path); 35 | 36 | hs_io_free(route); 37 | } 38 | 39 | 40 | void hs_route_set_all_methods(struct HSRoute *route, bool enable) 41 | { 42 | if (route == NULL) 43 | { 44 | return; 45 | } 46 | 47 | route->is_get = enable; 48 | route->is_post = enable; 49 | route->is_put = enable; 50 | route->is_delete = enable; 51 | route->is_head = enable; 52 | route->is_connect = enable; 53 | route->is_options = enable; 54 | route->is_trace = enable; 55 | route->is_patch = enable; 56 | } 57 | 58 | 59 | bool hs_route_is_allowed_for_method(struct HSRoute *route, struct HSHttpRequest *request) 60 | { 61 | if (route == NULL || request == NULL) 62 | { 63 | return(false); 64 | } 65 | 66 | if (route->is_get && request->method == HS_HTTP_METHOD_GET) 67 | { 68 | return(true); 69 | } 70 | if (route->is_post && request->method == HS_HTTP_METHOD_POST) 71 | { 72 | return(true); 73 | } 74 | if (route->is_put && request->method == HS_HTTP_METHOD_PUT) 75 | { 76 | return(true); 77 | } 78 | if (route->is_delete && request->method == HS_HTTP_METHOD_DELETE) 79 | { 80 | return(true); 81 | } 82 | if (route->is_head && request->method == HS_HTTP_METHOD_HEAD) 83 | { 84 | return(true); 85 | } 86 | if (route->is_connect && request->method == HS_HTTP_METHOD_CONNECT) 87 | { 88 | return(true); 89 | } 90 | if (route->is_options && request->method == HS_HTTP_METHOD_OPTIONS) 91 | { 92 | return(true); 93 | } 94 | if (route->is_trace && request->method == HS_HTTP_METHOD_TRACE) 95 | { 96 | return(true); 97 | } 98 | if (route->is_patch && request->method == HS_HTTP_METHOD_PATCH) 99 | { 100 | return(true); 101 | } 102 | 103 | return(false); 104 | } /* hs_route_is_allowed_for_method */ 105 | 106 | 107 | bool hs_route_is_supported_path(struct HSRoute *route, struct HSHttpRequest *request) 108 | { 109 | if (route == NULL || request == NULL || request->resource == NULL) 110 | { 111 | return(false); 112 | } 113 | 114 | // if route does not define path, it is catch all 115 | if (route->path == NULL) 116 | { 117 | return(true); 118 | } 119 | 120 | // skip invalid route paths 121 | size_t route_length = strlen(route->path); 122 | if (!route_length || route->path[0] != '/') 123 | { 124 | return(false); 125 | } 126 | 127 | // request resource is shorter than the route path 128 | size_t request_length = strlen(request->resource); 129 | if (request_length < route_length) 130 | { 131 | return(false); 132 | } 133 | 134 | if (request_length == route_length) 135 | { 136 | return(stringfn_equal(route->path, request->resource)); 137 | } 138 | 139 | if ( !route->is_parent_path 140 | || ( request->resource[route_length - 1] != '/' 141 | && request->resource[route_length] != '/')) 142 | { 143 | return(false); 144 | } 145 | 146 | return(stringfn_starts_with(request->resource, route->path)); 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/hs_types_cookie.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_types.h" 3 | #include "stringfn.h" 4 | #include "vector.h" 5 | #include 6 | #include 7 | 8 | struct HSCookies 9 | { 10 | struct Vector *vector; 11 | }; 12 | 13 | struct HSCookie *hs_types_cookie_new(void) 14 | { 15 | struct HSCookie *cookie = malloc(sizeof(struct HSCookie)); 16 | 17 | cookie->name = NULL; 18 | cookie->value = NULL; 19 | cookie->expires = NULL; 20 | cookie->max_age = -1; 21 | cookie->secure = false; 22 | cookie->http_only = false; 23 | cookie->domain = NULL; 24 | cookie->path = NULL; 25 | cookie->same_site = HS_COOKIE_SAME_SITE_LAX; 26 | 27 | return(cookie); 28 | } 29 | 30 | 31 | void hs_types_cookie_release(struct HSCookie *cookie) 32 | { 33 | if (cookie == NULL) 34 | { 35 | return; 36 | } 37 | 38 | hs_io_free(cookie->name); 39 | hs_io_free(cookie->value); 40 | hs_io_free(cookie->expires); 41 | hs_io_free(cookie->domain); 42 | hs_io_free(cookie->path); 43 | 44 | hs_io_free(cookie); 45 | } 46 | 47 | struct HSCookies *hs_types_cookies_new(void) 48 | { 49 | struct HSCookies *cookies = malloc(sizeof(struct HSCookies)); 50 | 51 | cookies->vector = vector_new(); 52 | 53 | return(cookies); 54 | } 55 | 56 | 57 | void hs_types_cookies_release(struct HSCookies *cookies) 58 | { 59 | if (cookies == NULL) 60 | { 61 | return; 62 | } 63 | 64 | size_t count = vector_size(cookies->vector); 65 | for (size_t index = 0; index < count; index++) 66 | { 67 | struct HSCookie *cookie = (struct HSCookie *)vector_get(cookies->vector, index); 68 | hs_types_cookie_release(cookie); 69 | } 70 | vector_release(cookies->vector); 71 | 72 | hs_io_free(cookies); 73 | } 74 | 75 | 76 | size_t hs_types_cookies_count(struct HSCookies *cookies) 77 | { 78 | if (cookies == NULL) 79 | { 80 | return(0); 81 | } 82 | 83 | return(vector_size(cookies->vector)); 84 | } 85 | 86 | 87 | bool hs_types_cookies_add(struct HSCookies *cookies, struct HSCookie *cookie) 88 | { 89 | if (cookies == NULL || cookie == NULL || cookie->name == NULL || cookie->value == NULL) 90 | { 91 | return(false); 92 | } 93 | 94 | vector_push(cookies->vector, cookie); 95 | 96 | return(true); 97 | } 98 | 99 | struct HSCookie *hs_types_cookies_get(struct HSCookies *cookies, size_t index) 100 | { 101 | if (cookies == NULL) 102 | { 103 | return(NULL); 104 | } 105 | 106 | return((struct HSCookie *)vector_get(cookies->vector, index)); 107 | } 108 | 109 | struct HSCookie *hs_types_cookies_get_by_name(struct HSCookies *cookies, char *name) 110 | { 111 | if (cookies == NULL || name == NULL) 112 | { 113 | return(NULL); 114 | } 115 | 116 | size_t count = vector_size(cookies->vector); 117 | for (size_t index = 0; index < count; index++) 118 | { 119 | struct HSCookie *cookie = (struct HSCookie *)vector_get(cookies->vector, index); 120 | if (stringfn_equal(cookie->name, name)) 121 | { 122 | return(cookie); 123 | } 124 | } 125 | 126 | return(NULL); 127 | } 128 | 129 | 130 | void hs_types_cookies_remove_by_name(struct HSCookies *cookies, char *name) 131 | { 132 | if (cookies == NULL || name == NULL) 133 | { 134 | return; 135 | } 136 | 137 | size_t count = vector_size(cookies->vector); 138 | if (!count) 139 | { 140 | return; 141 | } 142 | 143 | for (size_t index = count - 1; ; index--) 144 | { 145 | struct HSCookie *cookie = (struct HSCookie *)vector_get(cookies->vector, index); 146 | if (stringfn_equal(cookie->name, name)) 147 | { 148 | hs_types_cookie_release(cookie); 149 | vector_remove(cookies->vector, index); 150 | } 151 | 152 | if (!index) 153 | { 154 | break; 155 | } 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.7) 3 | 4 | project(hs C) 5 | 6 | # include shared utilities 7 | if(NOT EXISTS "target/cmake-modules/src/utils.cmake") 8 | execute_process(COMMAND git clone https://github.com/sagiegurari/cmake-modules.git) 9 | endif() 10 | include("target/cmake-modules/src/utils.cmake") 11 | 12 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 13 | 14 | set(CMAKE_BUILD_TYPE Release) 15 | if(NOT "$ENV{X_CMAKE_NO_THREADS}" STREQUAL "true") 16 | add_definitions(-DHS_THREADS_ENABLED) 17 | set(CMAKE_C_FLAGS -pthread) 18 | endif() 19 | if(NOT WIN32) 20 | set(X_CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wall -Wextra -Wcast-align -Wunused -Wshadow -Wpedantic") 21 | endif() 22 | 23 | set(X_CMAKE_PROJECT_ROOT_DIR ${CMAKE_BINARY_DIR}/..) 24 | 25 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 26 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 27 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 28 | 29 | macro(add_external_lib) 30 | utils_add_external_github_lib( 31 | REPO_USERNAME sagiegurari 32 | REPO_NAME c_${ARGV0} 33 | TAG_NAME ${ARGV1} 34 | LIBRARY_NAME ${ARGV0} 35 | LIBRARY_PARENT_DIRECTORY target 36 | ) 37 | endmacro(add_external_lib) 38 | add_external_lib("stringfn") 39 | add_external_lib("string_buffer") 40 | add_external_lib("vector") 41 | add_external_lib("hashtable") 42 | add_external_lib("fsio") 43 | add_external_lib("ini") 44 | if(NOT "$ENV{X_CMAKE_NO_THREADS}" STREQUAL "true") 45 | add_external_lib("thread_lock") 46 | add_external_lib("thread_pool") 47 | endif() 48 | 49 | include_directories(include "${STRINGFN_INCLUDE}" "${STRING_BUFFER_INCLUDE}" "${VECTOR_INCLUDE}" "${HASHTABLE_INCLUDE}" "${FSIO_INCLUDE}" "${INI_INCLUDE}" "${THREAD_LOCK_INCLUDE}" "${THREAD_POOL_INCLUDE}") 50 | 51 | # define all sources 52 | file(GLOB SOURCES "src/*.c") 53 | file(GLOB HEADER_SOURCES "include/*.h") 54 | file(GLOB TEST_SOURCES "tests/*") 55 | file(GLOB COMMON_TEST_SOURCES "tests/test.*") 56 | file(GLOB EXAMPLE_SOURCES "examples/*.c") 57 | file(GLOB EXAMPLE_PEM_FILES "examples/*.pem") 58 | 59 | # lint code 60 | utils_cppcheck(INCLUDE_DIRECTORY "./include/" SOURCES "./src/*.c" WORKING_DIRECTORY "${X_CMAKE_PROJECT_ROOT_DIR}") 61 | 62 | # format code 63 | utils_uncrustify( 64 | CONFIG_FILE "${X_CMAKE_PROJECT_ROOT_DIR}/uncrustify.cfg" 65 | SOURCES ${SOURCES} ${HEADER_SOURCES} ${TEST_SOURCES} ${EXAMPLE_SOURCES} 66 | ) 67 | 68 | # create static library 69 | add_library(${CMAKE_PROJECT_NAME} STATIC ${SOURCES} ${STRINGFN_SOURCES} ${STRING_BUFFER_SOURCES} ${VECTOR_SOURCES} ${HASHTABLE_SOURCES} ${FSIO_SOURCES} ${INI_SOURCES} ${THREAD_LOCK_SOURCES} ${THREAD_POOL_SOURCES}) 70 | if(NOT WIN32) 71 | set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES COMPILE_FLAGS "${X_CMAKE_C_FLAGS} -Wconversion") 72 | endif() 73 | 74 | # openssl support 75 | if("$ENV{X_CMAKE_OPEN_SSL}" STREQUAL "true") 76 | find_package(OpenSSL REQUIRED) 77 | 78 | message("OpenSSL Include: ${OPENSSL_INCLUDE_DIR}") 79 | message("OpenSSL Libs: ${OPENSSL_LIBRARIES}") 80 | 81 | add_definitions(-DHS_SSL_SUPPORTED) 82 | 83 | include_directories(${OPENSSL_INCLUDE_DIR}) 84 | target_link_libraries(${CMAKE_PROJECT_NAME} ${OPENSSL_LIBRARIES}) 85 | 86 | file(COPY ${EXAMPLE_PEM_FILES} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) 87 | endif() 88 | 89 | # example 90 | add_executable(example examples/example.c) 91 | target_link_libraries(example ${CMAKE_PROJECT_NAME}) 92 | if("$ENV{X_CMAKE_OPEN_SSL}" STREQUAL "true") 93 | target_link_libraries(example ${OPENSSL_LIBRARIES}) 94 | endif() 95 | set_target_properties(example PROPERTIES COMPILE_FLAGS "${X_CMAKE_C_FLAGS}") 96 | 97 | # tests 98 | include(CTest) 99 | 100 | utils_setup_test_lib( 101 | SOURCES "${COMMON_TEST_SOURCES}" 102 | COMPILATION_FLAGS "${X_CMAKE_C_FLAGS}" 103 | ) 104 | utils_setup_c_all_tests( 105 | COMPILATION_FLAGS "${X_CMAKE_C_FLAGS}" 106 | BINARY_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 107 | LIBRARIES "Test" 108 | ) 109 | 110 | if("$ENV{X_CMAKE_DOC_STEPS}" STREQUAL "true") 111 | # post build steps 112 | add_custom_command( 113 | TARGET example 114 | POST_BUILD 115 | COMMENT "Post Build Steps" 116 | COMMAND ${CMAKE_COMMAND} -P "../post-example-build.cmake" 117 | ) 118 | endif() 119 | 120 | -------------------------------------------------------------------------------- /tests/test_router_serve_text_file.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 11 | { 12 | assert_true(route != NULL); 13 | assert_true(params != NULL); 14 | 15 | params->response->code = HS_HTTP_RESPONSE_CODE_NOT_FOUND; 16 | hs_types_array_string_pair_add(params->response->headers, strdup("header1"), strdup("value1")); 17 | hs_types_array_string_pair_add(params->response->headers, strdup("header2"), strdup("value2")); 18 | hs_types_array_string_pair_add(params->response->headers, strdup("header3"), strdup("value3")); 19 | struct HSCookie *cookie = hs_types_cookie_new(); 20 | cookie->name = strdup("c1"); 21 | cookie->value = strdup("v1"); 22 | cookie->expires = strdup("1 1 1980"); 23 | cookie->max_age = 200; 24 | cookie->secure = true; 25 | cookie->http_only = true; 26 | cookie->domain = strdup("mydomain"); 27 | cookie->path = strdup("/somepath"); 28 | cookie->same_site = HS_COOKIE_SAME_SITE_NONE; 29 | hs_types_cookies_add(params->response->cookies, cookie); 30 | cookie = hs_types_cookie_new(); 31 | cookie->name = strdup("c2"); 32 | cookie->value = strdup("v2"); 33 | cookie->same_site = HS_COOKIE_SAME_SITE_STRICT; 34 | hs_types_cookies_add(params->response->cookies, cookie); 35 | cookie = hs_types_cookie_new(); 36 | cookie->name = strdup("c3"); 37 | cookie->value = strdup("v3"); 38 | hs_types_cookies_add(params->response->cookies, cookie); 39 | 40 | params->response->mime_type = HS_MIME_TYPE_TEXT_PLAIN; 41 | 42 | fsio_write_text_file("./test_router_serve_file.out.txt", "some file content\nsecond line."); 43 | params->response->content_file = strdup("./test_router_serve_file.out.txt"); 44 | 45 | return(HS_SERVE_FLOW_RESPONSE_DONE); 46 | } /* _test_serve */ 47 | 48 | 49 | void test_impl() 50 | { 51 | struct HSRouter *router = hs_router_new(); 52 | 53 | struct HSRoute *route = hs_route_new(); 54 | 55 | route->path = strdup("/test"); 56 | route->serve = _test_serve; 57 | route->is_get = true; 58 | hs_router_add_route(router, route); 59 | 60 | struct HSHttpRequest *request = hs_types_http_request_new(); 61 | request->resource = strdup("/test"); 62 | request->method = HS_HTTP_METHOD_GET; 63 | request->connection = HS_CONNECTION_TYPE_CLOSE; 64 | 65 | char *filename = "./test_router_serve_file.txt"; 66 | 67 | fsio_create_empty_file(filename); 68 | int socket = open(filename, O_WRONLY); 69 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 70 | 71 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 72 | connection_state->socket = hssocket; 73 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new_pre_populated(request); 74 | params->connection_state = connection_state; 75 | 76 | bool done = hs_router_serve(router, params); 77 | assert_true(done); 78 | 79 | hs_socket_close_and_release(hssocket); 80 | 81 | char *content = fsio_read_text_file(filename); 82 | 83 | assert_string_equal(content, "HTTP/1.1 404 404\r\n" 84 | "header1: value1\r\n" 85 | "header2: value2\r\n" 86 | "header3: value3\r\n" 87 | "Set-Cookie: c1=v1; Expires=1 1 1980; Max-Age=200; Secure; HttpOnly; Domain=mydomain; Path=/somepath; SameSite=None\r\n" 88 | "Set-Cookie: c2=v2; SameSite=Strict\r\n" 89 | "Set-Cookie: c3=v3; SameSite=Lax\r\n" 90 | "Connection: close\r\n" 91 | "Content-Type: text/plain\r\n" 92 | "Content-Length: 30\r\n" 93 | "\r\n" 94 | "some file content\n" 95 | "second line."); 96 | 97 | hs_io_free(content); 98 | fsio_remove(filename); 99 | fsio_remove("./test_router_serve_file.out.txt"); 100 | hs_types_serve_flow_params_release(params); 101 | hs_types_server_connection_state_release(connection_state); 102 | hs_router_release(router); 103 | } /* test_impl */ 104 | 105 | 106 | int main() 107 | { 108 | test_run(test_impl); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### v0.10.1 4 | 5 | * Maintenance: Adding void to input parameters where missing 6 | 7 | ### v0.10.0 (2023-04-21) 8 | 9 | * Enhancement: support multi threaded server via internal thread pool 10 | * Enhancement: make default new server as multi threaded 11 | * Fix: fix FS directory route relative paths 12 | 13 | ### v0.9.0 (2023-04-04) 14 | 15 | * Enhancement: new modify_cookie function for session route 16 | * Enhancement: migrated data array in session to hashtable 17 | 18 | ### v0.8.0 (2022-01-06) 19 | 20 | * Enhancement: migrated data array in state to hashtable 21 | 22 | ### v0.7.3 (2022-12-13) 23 | 24 | * Fix: fix FS directory route relative paths 25 | 26 | ### v0.7.2 (2022-12-13) 27 | 28 | * Fix: fix FS directory route relative paths 29 | * Maintenance: Update string_buffer dependency 30 | * Added static to internal functions 31 | * Adding lint checks as part of build 32 | * Updated header include guard macro name 33 | 34 | ### v0.7.1 (2021-12-09) 35 | 36 | * Update headers to enable access to socket struct 37 | 38 | ### v0.7.0 (2021-12-08) 39 | 40 | * Enhancement: Socket abstraction layer to enable TLS support 41 | * Enhancement: TLS support via OpenSSL 42 | 43 | ### v0.6.2 (2021-10-06) 44 | 45 | * Fix: routes inside multiple routers callbacks would be called multiple times 46 | 47 | ### v0.6.1 (2021-10-06) 48 | 49 | * Route callbacks are called in the reverse order of entry 50 | 51 | ### v0.6.0 (2021-10-02) 52 | 53 | * Enhancement: New security headers route. 54 | * Enhancement: Move auth routes to security namespace. 55 | * Fix: directory route now returns mime type. 56 | * Fix: memory leak in basic auth route. 57 | * Enhancement: Realm will be released with the basic auth route release 58 | * Enhancement: Make additional params of fs routes released with route 59 | * Enhancement: Refactor server to enable custom connection handling (preparation for multi threaded support) 60 | * Enhancement: Flow params now contains connection state which holds shared data (such as request counter) for all requests on same connection 61 | * Enhancement: New rate limiting routes 62 | 63 | ### v0.5.1 (2021-09-26) 64 | 65 | * Fix: Mime type detection by file name now handles multi case file names. 66 | 67 | ### v0.5.0 (2021-09-26) 68 | 69 | * Fix: Relative href values in FS directory route 70 | * Enhancement: Enable FS file route to close connections to enable multi connection support on single threaded servers 71 | * Enhancement: New redirection route 72 | * Enhancement: New static content routes 73 | 74 | ### v0.4.0 (2021-09-24) 75 | 76 | * Fix: Incorrect handling of binary files 77 | * Enhancement: New options for the hs_routes_fs_file_route_new_with_options function 78 | * Enhancement: New hs_routes_fs_directory_route_new_with_media_support function 79 | * Enhancement: Added additional mime types 80 | * Enhancement: New favicon route 81 | 82 | ### v0.3.0 (2021-09-09) 83 | 84 | * Routes support all HTTP methods. 85 | * 404 route supports all child paths by default. 86 | * New session route (HTTP session). 87 | * Refactor router flow invocation to enable non response routes to modify final HTTP response. 88 | * New simplified api for add/remove/get headers, cookies and state. 89 | * New powered-by route 90 | * Support multiple post response callbacks 91 | * New route flow state which can be used to store data as part of the request handling flow 92 | * Renaming common routes to enable internal refactoring 93 | * Close the socket if payload exists but not read after response is sent. 94 | * New 411 route. 95 | * New payload limit route. 96 | 97 | ### v0.2.3 (2021-08-24) 98 | 99 | * Fix response headers writing 100 | * New basic auth route 101 | * Authorization http request header support 102 | * Adding doctype to directory route html response 103 | 104 | ### v0.2.2 (2021-08-24) 105 | 106 | * Fix directory route - show only file name, not full path 107 | 108 | ### v0.2.1 (2021-08-24) 109 | 110 | * Enable directory route to accept additional head html 111 | * Added classes on directory view to enable customization 112 | 113 | ### v0.2.0 (2021-08-24) 114 | 115 | * Reading payload will limit based on content length header 116 | 117 | ### v0.1.1 (2021-08-23) 118 | 119 | * New post response callback to enable to invoke functionality after response is written 120 | * Enable file route to accept external mime type resolution 121 | 122 | ### v0.1.0 (2021-08-21) 123 | 124 | * Initial release 125 | -------------------------------------------------------------------------------- /tests/test_router_as_route.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum HSServeFlowResponse _test_redirect(struct HSRoute *route, struct HSServeFlowParams *params) 11 | { 12 | assert_true(route != NULL); 13 | assert_true(params != NULL); 14 | 15 | params->response->code = HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT; 16 | hs_types_array_string_pair_add(params->response->headers, strdup("Location"), strdup("/mylocation")); 17 | 18 | return(HS_SERVE_FLOW_RESPONSE_DONE); 19 | } 20 | 21 | 22 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 23 | { 24 | assert_true(route != NULL); 25 | assert_true(params != NULL); 26 | 27 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 28 | params->response->mime_type = HS_MIME_TYPE_TEXT_HTML; 29 | params->response->content_string = strdup("my html"); 30 | 31 | return(HS_SERVE_FLOW_RESPONSE_DONE); 32 | } 33 | 34 | 35 | void _test_with_values(struct HSRouter *router, char *request_path, char *expected_result) 36 | { 37 | char *filename = "./test_router_as_route.txt"; 38 | 39 | struct HSHttpRequest *request = hs_types_http_request_new(); 40 | 41 | request->resource = strdup(request_path); 42 | request->method = HS_HTTP_METHOD_GET; 43 | fsio_remove(filename); 44 | assert_true(!fsio_path_exists(filename)); 45 | fsio_create_empty_file(filename); 46 | int socket = open(filename, O_WRONLY); 47 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 48 | 49 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 50 | connection_state->socket = hssocket; 51 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new_pre_populated(request); 52 | params->connection_state = connection_state; 53 | 54 | bool done = hs_router_serve(router, params); 55 | assert_true(done); 56 | hs_socket_close_and_release(hssocket); 57 | assert_string_equal(request->resource, request_path); 58 | hs_types_serve_flow_params_release(params); 59 | hs_types_server_connection_state_release(connection_state); 60 | 61 | char *content = fsio_read_text_file(filename); 62 | fsio_remove(filename); 63 | assert_string_equal(content, expected_result); 64 | hs_io_free(content); 65 | } 66 | 67 | 68 | void test_impl() 69 | { 70 | struct HSRouter *router = hs_router_new(); 71 | 72 | struct HSRoute *route = hs_route_new(); 73 | 74 | route->is_get = true; 75 | route->path = strdup("/gohome"); 76 | route->serve = _test_redirect; 77 | hs_router_add_route(router, route); 78 | 79 | route = hs_route_new(); 80 | route->is_get = true; 81 | route->path = strdup("/index.html"); 82 | route->serve = _test_serve; 83 | hs_router_add_route(router, route); 84 | 85 | route = hs_router_as_route(router); 86 | route->path = strdup("/admin/"); 87 | 88 | router = hs_router_new(); 89 | hs_router_add_route(router, route); 90 | route = hs_routes_error_404_not_found_route_new(); 91 | hs_router_add_route(router, route); 92 | 93 | _test_with_values(router, "/test", "HTTP/1.1 404 404\r\n" 94 | "Connection: close\r\n" 95 | "Content-Length: 0\r\n" 96 | "\r\n"); 97 | 98 | _test_with_values(router, "/admin", "HTTP/1.1 404 404\r\n" 99 | "Connection: close\r\n" 100 | "Content-Length: 0\r\n" 101 | "\r\n"); 102 | 103 | _test_with_values(router, "/admin/", "HTTP/1.1 404 404\r\n" 104 | "Connection: close\r\n" 105 | "Content-Length: 0\r\n" 106 | "\r\n"); 107 | 108 | _test_with_values(router, "/admin/gohome", "HTTP/1.1 307 307\r\n" 109 | "Location: /mylocation\r\n" 110 | "Connection: close\r\n" 111 | "Content-Length: 0\r\n" 112 | "\r\n"); 113 | 114 | _test_with_values(router, "/admin/index.html", "HTTP/1.1 200 200\r\n" 115 | "Connection: close\r\n" 116 | "Content-Type: text/html\r\n" 117 | "Content-Length: 7\r\n" 118 | "\r\n" 119 | "my html"); 120 | 121 | hs_router_release(router); 122 | } /* test_impl */ 123 | 124 | 125 | int main() 126 | { 127 | test_run(test_impl); 128 | } 129 | 130 | -------------------------------------------------------------------------------- /tests/test_router_serve_text.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | bool callback_called = false; 11 | 12 | 13 | void _test_callback(struct HSPostResponseCallback *callback) 14 | { 15 | assert_true(callback != NULL); 16 | assert_true(callback->context != NULL); 17 | assert_true(stringfn_equal((char *)callback->context, "test callback")); 18 | callback_called = true; 19 | } 20 | 21 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 22 | { 23 | assert_true(route != NULL); 24 | assert_true(params != NULL); 25 | 26 | params->response->code = HS_HTTP_RESPONSE_CODE_NOT_FOUND; 27 | hs_types_array_string_pair_add(params->response->headers, strdup("header1"), strdup("value1")); 28 | hs_types_array_string_pair_add(params->response->headers, strdup("header2"), strdup("value2")); 29 | hs_types_array_string_pair_add(params->response->headers, strdup("header3"), strdup("value3")); 30 | struct HSCookie *cookie = hs_types_cookie_new(); 31 | cookie->name = strdup("c1"); 32 | cookie->value = strdup("v1"); 33 | cookie->expires = strdup("1 1 1980"); 34 | cookie->max_age = 200; 35 | cookie->secure = true; 36 | cookie->http_only = true; 37 | cookie->domain = strdup("mydomain"); 38 | cookie->path = strdup("/somepath"); 39 | cookie->same_site = HS_COOKIE_SAME_SITE_NONE; 40 | hs_types_cookies_add(params->response->cookies, cookie); 41 | cookie = hs_types_cookie_new(); 42 | cookie->name = strdup("c2"); 43 | cookie->value = strdup("v2"); 44 | cookie->same_site = HS_COOKIE_SAME_SITE_STRICT; 45 | hs_types_cookies_add(params->response->cookies, cookie); 46 | cookie = hs_types_cookie_new(); 47 | cookie->name = strdup("c3"); 48 | cookie->value = strdup("v3"); 49 | hs_types_cookies_add(params->response->cookies, cookie); 50 | 51 | params->response->mime_type = HS_MIME_TYPE_TEXT_PLAIN; 52 | 53 | params->response->content_string = strdup("some content\nsecond line."); 54 | 55 | struct HSPostResponseCallback *callback = hs_types_post_response_callback_new(); 56 | callback->context = "test callback"; 57 | callback->run = _test_callback; 58 | hs_types_post_response_callbacks_add(params->callbacks, callback); 59 | 60 | return(HS_SERVE_FLOW_RESPONSE_DONE); 61 | } /* _test_serve */ 62 | 63 | 64 | void test_impl() 65 | { 66 | struct HSRouter *router = hs_router_new(); 67 | 68 | struct HSRoute *route = hs_route_new(); 69 | 70 | route->path = strdup("/test"); 71 | route->serve = _test_serve; 72 | route->is_get = true; 73 | hs_router_add_route(router, route); 74 | 75 | char *filename = "./test_router_serve_text.txt"; 76 | 77 | fsio_create_empty_file(filename); 78 | int socket = open(filename, O_WRONLY); 79 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 80 | 81 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 82 | connection_state->socket = hssocket; 83 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 84 | params->connection_state = connection_state; 85 | params->request->resource = strdup("/test"); 86 | params->request->method = HS_HTTP_METHOD_GET; 87 | params->request->connection = HS_CONNECTION_TYPE_KEEP_ALIVE; 88 | 89 | assert_true(!callback_called); 90 | bool done = hs_router_serve(router, params); 91 | assert_true(done); 92 | assert_true(callback_called); 93 | 94 | hs_socket_close_and_release(hssocket); 95 | 96 | char *content = fsio_read_text_file(filename); 97 | 98 | assert_string_equal(content, "HTTP/1.1 404 404\r\n" 99 | "header1: value1\r\n" 100 | "header2: value2\r\n" 101 | "header3: value3\r\n" 102 | "Set-Cookie: c1=v1; Expires=1 1 1980; Max-Age=200; Secure; HttpOnly; Domain=mydomain; Path=/somepath; SameSite=None\r\n" 103 | "Set-Cookie: c2=v2; SameSite=Strict\r\n" 104 | "Set-Cookie: c3=v3; SameSite=Lax\r\n" 105 | "Connection: keep-alive\r\n" 106 | "Content-Type: text/plain\r\n" 107 | "Content-Length: 25\r\n" 108 | "\r\n" 109 | "some content\n" 110 | "second line."); 111 | 112 | hs_io_free(content); 113 | fsio_remove(filename); 114 | hs_types_serve_flow_params_release(params); 115 | hs_types_server_connection_state_release(connection_state); 116 | hs_router_release(router); 117 | } /* test_impl */ 118 | 119 | 120 | int main() 121 | { 122 | test_run(test_impl); 123 | } 124 | 125 | -------------------------------------------------------------------------------- /include/hs_server.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_SERVER_H 2 | #define HS_SERVER_H 3 | 4 | #include "hs_socket.h" 5 | #include 6 | #include 7 | 8 | #define HS_SERVER_DEFAULT_THREAD_POOL_SIZE 20 9 | 10 | struct HSServer; 11 | struct HSServerInternal; 12 | 13 | struct HSServerConnectionHandler 14 | { 15 | void (*init)(struct HSServerConnectionHandler *); 16 | void (*on_connection)(struct HSServerConnectionHandler *, struct HSServer *, struct HSSocket *, void * /* context */, bool (*should_stop_server)(struct HSServer *, void * /* context */), bool (*should_stop_for_connection)(struct HSRouter *, struct HSSocket *, size_t /* request counter */, void * /* context */)); 17 | void (*stop_connections)(struct HSServerConnectionHandler *); 18 | void (*release)(struct HSServerConnectionHandler *); 19 | void *extension; 20 | }; 21 | 22 | struct HSServerSSLInfo 23 | { 24 | // external files with the key and certificate (default NULL) 25 | // The strings will be released with the server. 26 | char *private_key_pem_file; 27 | char *certificate_pem_file; 28 | }; 29 | 30 | struct HSServer 31 | { 32 | struct HSRouter *router; 33 | time_t accept_recv_timeout_seconds; 34 | time_t request_recv_timeout_seconds; 35 | struct HSServerConnectionHandler *connection_handler; 36 | 37 | // If populated, it will enable TLS support. 38 | // However, if HS_SSL_SUPPORTED is undefined while this struct is populated, 39 | // it means the library was compiled without SSL support and the 40 | // hs_server_serve function will return false to avoid security issue. 41 | struct HSServerSSLInfo *ssl_info; 42 | 43 | // server functions should not be invoked directly, instead use the hs_server_xxx functions. 44 | struct HSSocket * (*create_socket_and_listen)(struct HSServer *, struct sockaddr_in *); 45 | struct HSSocket * (*accept)(struct HSServer *, struct HSSocket *, struct sockaddr *, int /* address size */); 46 | void (*listen_loop)(struct HSServer *, struct HSSocket *, struct sockaddr_in, void * /* context */, bool (*should_stop_server)(struct HSServer *, void * /* context */), bool (*should_stop_for_connection)(struct HSRouter *, struct HSSocket *, size_t /* request counter */, void * /* context */)); 47 | struct HSServerInternal *internal; 48 | }; 49 | 50 | /** 51 | * Creates a new server and returns it. 52 | * The server is by default setup as multi threaded implementation 53 | * but can be mutated after this call to allow custom connection handler 54 | * functions. 55 | */ 56 | struct HSServer *hs_server_new(void); 57 | 58 | /** 59 | * Creates a new fully initialized server and returns it. 60 | * The server will run all requests on the current thread. 61 | */ 62 | struct HSServer *hs_server_new_single_thread(void); 63 | 64 | /** 65 | * Creates a new fully initialized server and returns it. 66 | * The server will run all requests via thread pool. 67 | * In case threads are not supported during compilation, this function will 68 | * return a single thread server. 69 | */ 70 | struct HSServer *hs_server_new_multi_thread(size_t /* thread pool size */); 71 | 72 | /** 73 | * Frees all memory used by the server. 74 | */ 75 | void hs_server_release(struct HSServer *); 76 | 77 | /** 78 | * This is the main server function that initializes the socket and starts 79 | * listening. Any connection will be handled by the internal router. 80 | * After each timeout or accept, the callback will be called to check if 81 | * to continue handling or to stop (return true to stop). 82 | * Once stop is requested via callback, the socket will be closed and this 83 | * function can be invoked again. 84 | */ 85 | bool hs_server_serve(struct HSServer *, struct sockaddr_in, void * /* context */, bool (*should_stop_server)(struct HSServer *, void * /* context */), bool (*should_stop_for_connection)(struct HSRouter *, struct HSSocket *, size_t /* request counter */, void * /* context */)); 86 | 87 | /** 88 | * Returns new connection handler. 89 | */ 90 | struct HSServerConnectionHandler *hs_server_connection_handler_new(void); 91 | 92 | /** 93 | * Releases the connection handler by calling the release function (if exists) 94 | * and clears any memory. 95 | */ 96 | void hs_server_connection_handler_release(struct HSServerConnectionHandler *); 97 | 98 | /** 99 | * Basic implementation of creating the server socket, binding and listening 100 | * to new incoming connections. 101 | * This function can be set for the server->create_socket_and_listen and should 102 | * not be invoked directly. 103 | */ 104 | struct HSSocket *hs_server_create_socket_and_listen(struct HSServer *, struct sockaddr_in *); 105 | 106 | /** 107 | * Simple utility function to create address for the given port. 108 | */ 109 | struct sockaddr_in hs_server_init_ipv4_address(uint16_t /* port */); 110 | 111 | #ifdef HS_SSL_SUPPORTED 112 | 113 | #endif 114 | 115 | #endif 116 | 117 | -------------------------------------------------------------------------------- /tests/test_parser_create_request_from_url.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct HSHttpRequest *request = hs_parser_create_request_from_url(NULL); 7 | 8 | assert_true(request == NULL); 9 | 10 | request = hs_parser_create_request_from_url("abc://mydomain"); 11 | assert_true(request == NULL); 12 | 13 | request = hs_parser_create_request_from_url("http://mydomain"); 14 | assert_string_equal(request->domain, "mydomain"); 15 | assert_string_equal(request->resource, "/"); 16 | assert_true(!request->ssl); 17 | assert_num_equal(request->port, -1); 18 | assert_true(request->query_string == NULL); 19 | hs_types_http_request_release(request); 20 | 21 | request = hs_parser_create_request_from_url("http://mydomain/"); 22 | assert_string_equal(request->domain, "mydomain"); 23 | assert_string_equal(request->resource, "/"); 24 | assert_true(!request->ssl); 25 | assert_num_equal(request->port, -1); 26 | assert_true(request->query_string == NULL); 27 | hs_types_http_request_release(request); 28 | 29 | request = hs_parser_create_request_from_url("https://mydomain"); 30 | assert_string_equal(request->domain, "mydomain"); 31 | assert_string_equal(request->resource, "/"); 32 | assert_true(request->ssl); 33 | assert_num_equal(request->port, -1); 34 | assert_true(request->query_string == NULL); 35 | hs_types_http_request_release(request); 36 | 37 | request = hs_parser_create_request_from_url("https://mydomain:8080/"); 38 | assert_string_equal(request->domain, "mydomain"); 39 | assert_string_equal(request->resource, "/"); 40 | assert_true(request->ssl); 41 | assert_num_equal(request->port, 8080); 42 | assert_true(request->query_string == NULL); 43 | hs_types_http_request_release(request); 44 | 45 | request = hs_parser_create_request_from_url("https://mydomain:8080"); 46 | assert_string_equal(request->domain, "mydomain"); 47 | assert_string_equal(request->resource, "/"); 48 | assert_true(request->ssl); 49 | assert_num_equal(request->port, 8080); 50 | assert_true(request->query_string == NULL); 51 | hs_types_http_request_release(request); 52 | 53 | request = hs_parser_create_request_from_url("https://mydomain/"); 54 | assert_string_equal(request->domain, "mydomain"); 55 | assert_string_equal(request->resource, "/"); 56 | assert_true(request->ssl); 57 | assert_num_equal(request->port, -1); 58 | assert_true(request->query_string == NULL); 59 | hs_types_http_request_release(request); 60 | 61 | request = hs_parser_create_request_from_url("https://mydomain/resource1/Resource2/"); 62 | assert_string_equal(request->domain, "mydomain"); 63 | assert_string_equal(request->resource, "/resource1/Resource2/"); 64 | assert_true(request->ssl); 65 | assert_num_equal(request->port, -1); 66 | assert_true(request->query_string == NULL); 67 | hs_types_http_request_release(request); 68 | 69 | request = hs_parser_create_request_from_url("https://mydomain/resource1/resource2:3/"); 70 | assert_string_equal(request->domain, "mydomain"); 71 | assert_string_equal(request->resource, "/resource1/resource2:3/"); 72 | assert_true(request->ssl); 73 | assert_num_equal(request->port, -1); 74 | assert_true(request->query_string == NULL); 75 | hs_types_http_request_release(request); 76 | 77 | request = hs_parser_create_request_from_url("https://mydomain:8080/resource1/resource2/"); 78 | assert_string_equal(request->domain, "mydomain"); 79 | assert_string_equal(request->resource, "/resource1/resource2/"); 80 | assert_true(request->ssl); 81 | assert_num_equal(request->port, 8080); 82 | assert_true(request->query_string == NULL); 83 | hs_types_http_request_release(request); 84 | 85 | request = hs_parser_create_request_from_url("https://mydomain:8080/resource1/resource2/?V=1&t=2"); 86 | assert_string_equal(request->domain, "mydomain"); 87 | assert_string_equal(request->resource, "/resource1/resource2/"); 88 | assert_true(request->ssl); 89 | assert_num_equal(request->port, 8080); 90 | assert_string_equal(request->query_string, "V=1&t=2"); 91 | hs_types_http_request_release(request); 92 | 93 | request = hs_parser_create_request_from_url("https://mydomain:8080/resource1/resource2/?"); 94 | assert_string_equal(request->domain, "mydomain"); 95 | assert_string_equal(request->resource, "/resource1/resource2/"); 96 | assert_true(request->ssl); 97 | assert_num_equal(request->port, 8080); 98 | assert_true(request->query_string == NULL); 99 | hs_types_http_request_release(request); 100 | 101 | request = hs_parser_create_request_from_url("https://www.google.com"); 102 | assert_string_equal(request->domain, "www.google.com"); 103 | assert_string_equal(request->resource, "/"); 104 | assert_true(request->ssl); 105 | assert_num_equal(request->port, -1); 106 | assert_true(request->query_string == NULL); 107 | hs_types_http_request_release(request); 108 | 109 | request = hs_parser_create_request_from_url("https://mydomain/resource1/resource2/?V=1&t=2"); 110 | assert_string_equal(request->domain, "mydomain"); 111 | assert_string_equal(request->resource, "/resource1/resource2/"); 112 | assert_true(request->ssl); 113 | assert_num_equal(request->port, -1); 114 | assert_string_equal(request->query_string, "V=1&t=2"); 115 | hs_types_http_request_release(request); 116 | } /* test_impl */ 117 | 118 | 119 | int main() 120 | { 121 | test_run(test_impl); 122 | } 123 | 124 | -------------------------------------------------------------------------------- /tests/test_router_serve_multi_routes.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | char *global_session_id = NULL; 11 | int global_counter = 1; 12 | 13 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 14 | { 15 | assert_true(route != NULL); 16 | assert_true(params != NULL); 17 | 18 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 19 | 20 | struct HSSession *session = (struct HSSession *)hashtable_get(params->route_state->data, HS_DEFAULT_SESSION_STATE_NAME); 21 | assert_true(session != NULL); 22 | assert_true(session->id != NULL); 23 | if (global_session_id == NULL) 24 | { 25 | global_session_id = strdup(session->id); 26 | hashtable_insert(session->data, strdup("counter"), stringfn_format("%d", global_counter), hs_io_release_hashtable_key_and_value); 27 | } 28 | else 29 | { 30 | assert_string_equal(global_session_id, session->id); 31 | assert_num_equal(atoi(hashtable_get(session->data, "counter")), global_counter); 32 | global_counter++; 33 | hashtable_insert(session->data, strdup("counter"), stringfn_format("%d", global_counter), hs_io_release_hashtable_key_and_value); 34 | } 35 | 36 | return(HS_SERVE_FLOW_RESPONSE_DONE); 37 | } 38 | 39 | 40 | void _test_with_values(struct HSRouter *router, struct HSRoute *route, bool is_get, bool is_post, bool closed, char *expected_response) 41 | { 42 | route->is_get = is_get; 43 | route->is_post = is_post; 44 | 45 | char *filename = "./test_router_serve_multi_routes.txt"; 46 | fsio_create_empty_file(filename); 47 | int socket = open(filename, O_WRONLY); 48 | 49 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 50 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 51 | connection_state->socket = hssocket; 52 | struct HSServeFlowParams *params = hs_types_serve_flow_params_new(); 53 | params->connection_state = connection_state; 54 | params->request->resource = strdup("/test"); 55 | params->request->method = HS_HTTP_METHOD_GET; 56 | 57 | if (global_session_id != NULL) 58 | { 59 | struct HSCookie *cookie = hs_types_cookie_new(); 60 | cookie->name = strdup("sc"); 61 | cookie->value = strdup(global_session_id); 62 | hs_types_cookies_add(params->request->cookies, cookie); 63 | } 64 | 65 | params->router_state->closed_connection = closed; 66 | bool done = hs_router_serve(router, params); 67 | 68 | if (expected_response == NULL || closed) 69 | { 70 | assert_true(!done); 71 | } 72 | else 73 | { 74 | assert_true(done); 75 | } 76 | 77 | hs_socket_close_and_release(hssocket); 78 | 79 | if (expected_response != NULL) 80 | { 81 | char *content = fsio_read_text_file(filename); 82 | 83 | char *updated_response = stringfn_format(expected_response, global_session_id); 84 | assert_string_equal(content, updated_response); 85 | 86 | hs_io_free(updated_response); 87 | hs_io_free(content); 88 | } 89 | 90 | fsio_remove(filename); 91 | hs_types_serve_flow_params_release(params); 92 | hs_types_server_connection_state_release(connection_state); 93 | } /* _test_with_values */ 94 | 95 | 96 | void test_impl() 97 | { 98 | struct HSRouter *router = hs_router_new(); 99 | 100 | hs_router_add_route(router, hs_routes_error_411_length_required_route_new()); 101 | hs_router_add_route(router, hs_routes_payload_limit_route_new(1024)); 102 | hs_router_add_route(router, hs_routes_session_route_new_default()); 103 | hs_router_add_route(router, hs_routes_powered_by_route_new(NULL)); 104 | 105 | struct HSRoute *route = hs_route_new(); 106 | 107 | route->path = strdup("/test"); 108 | route->serve = _test_serve; 109 | route->is_get = true; 110 | hs_router_add_route(router, route); 111 | 112 | _test_with_values(router, route, true, false, false, "HTTP/1.1 200 200\r\n" 113 | "X-Powered-By: CHS\r\n" 114 | "Set-Cookie: sc=%s; Max-Age=63072000; Secure; HttpOnly; SameSite=Strict\r\n" 115 | "Connection: close\r\n" 116 | "Content-Length: 0\r\n" 117 | "\r\n"); 118 | 119 | _test_with_values(router, route, false, true, false, NULL); 120 | 121 | _test_with_values(router, route, true, false, false, "HTTP/1.1 200 200\r\n" 122 | "X-Powered-By: CHS\r\n" 123 | "Set-Cookie: sc=%s; Max-Age=63072000; Secure; HttpOnly; SameSite=Strict\r\n" 124 | "Connection: close\r\n" 125 | "Content-Length: 0\r\n" 126 | "\r\n"); 127 | 128 | _test_with_values(router, route, false, true, false, NULL); 129 | 130 | hs_router_add_route(router, hs_routes_error_404_not_found_route_new()); 131 | 132 | _test_with_values(router, route, false, false, false, "HTTP/1.1 404 404\r\n" 133 | "X-Powered-By: CHS\r\n" 134 | "Set-Cookie: sc=%s; Max-Age=63072000; Secure; HttpOnly; SameSite=Strict\r\n" 135 | "Connection: close\r\n" 136 | "Content-Length: 0\r\n" 137 | "\r\n"); 138 | 139 | _test_with_values(router, route, true, true, true, ""); 140 | 141 | hs_router_release(router); 142 | hs_io_free(global_session_id); 143 | } /* test_impl */ 144 | 145 | 146 | int main() 147 | { 148 | test_run(test_impl); 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/hs_types_array.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include "hs_types_array.h" 3 | #include "stringfn.h" 4 | #include "vector.h" 5 | #include 6 | 7 | struct HSArrayPair 8 | { 9 | char *key; 10 | void *value; 11 | }; 12 | 13 | struct HSArrayStringPair 14 | { 15 | struct Vector *vector; 16 | }; 17 | 18 | static void _hs_types_array_pair_release(struct HSArrayPair *, bool); 19 | static void _hs_types_array_release(struct Vector *, bool); 20 | static bool _hs_types_array_pair_add(struct Vector *, char *, void *); 21 | static char *_hs_types_array_pair_get_key(struct Vector *, size_t); 22 | static void *_hs_types_array_pair_get_value(struct Vector *, size_t); 23 | static void *_hs_types_array_pair_get_by_key(struct Vector *, char *); 24 | static void _hs_types_array_pair_remove_by_key(struct Vector *, char *, bool); 25 | 26 | struct HSArrayStringPair *hs_types_array_string_pair_new(void) 27 | { 28 | struct HSArrayStringPair *array = malloc(sizeof(struct HSArrayStringPair)); 29 | 30 | array->vector = vector_new(); 31 | 32 | return(array); 33 | } 34 | 35 | 36 | void hs_types_array_string_pair_release(struct HSArrayStringPair *array) 37 | { 38 | if (array == NULL) 39 | { 40 | return; 41 | } 42 | 43 | _hs_types_array_release(array->vector, true); 44 | 45 | hs_io_free(array); 46 | } 47 | 48 | 49 | size_t hs_types_array_string_pair_count(struct HSArrayStringPair *array) 50 | { 51 | if (array == NULL) 52 | { 53 | return(0); 54 | } 55 | 56 | return(vector_size(array->vector)); 57 | } 58 | 59 | 60 | bool hs_types_array_string_pair_add(struct HSArrayStringPair *array, char *key, char *value) 61 | { 62 | if (array == NULL) 63 | { 64 | return(false); 65 | } 66 | 67 | return(_hs_types_array_pair_add(array->vector, key, value)); 68 | } 69 | 70 | 71 | char *hs_types_array_string_pair_get_key(struct HSArrayStringPair *array, size_t index) 72 | { 73 | if (array == NULL) 74 | { 75 | return(NULL); 76 | } 77 | 78 | return(_hs_types_array_pair_get_key(array->vector, index)); 79 | } 80 | 81 | 82 | char *hs_types_array_string_pair_get_value(struct HSArrayStringPair *array, size_t index) 83 | { 84 | if (array == NULL) 85 | { 86 | return(NULL); 87 | } 88 | 89 | return(_hs_types_array_pair_get_value(array->vector, index)); 90 | } 91 | 92 | 93 | char *hs_types_array_string_pair_get_by_key(struct HSArrayStringPair *array, char *key) 94 | { 95 | if (array == NULL) 96 | { 97 | return(NULL); 98 | } 99 | 100 | return(_hs_types_array_pair_get_by_key(array->vector, key)); 101 | } 102 | 103 | 104 | void hs_types_array_string_pair_remove_by_key(struct HSArrayStringPair *array, char *key) 105 | { 106 | if (array == NULL) 107 | { 108 | return; 109 | } 110 | 111 | _hs_types_array_pair_remove_by_key(array->vector, key, true); 112 | } 113 | 114 | 115 | static void _hs_types_array_pair_release(struct HSArrayPair *pair, bool release_value) 116 | { 117 | if (pair == NULL) 118 | { 119 | return; 120 | } 121 | 122 | hs_io_free(pair->key); 123 | if (release_value) 124 | { 125 | hs_io_free(pair->value); 126 | } 127 | hs_io_free(pair); 128 | } 129 | 130 | 131 | static void _hs_types_array_release(struct Vector *vector, bool release_value) 132 | { 133 | if (vector == NULL) 134 | { 135 | return; 136 | } 137 | 138 | size_t count = vector_size(vector); 139 | for (size_t index = 0; index < count; index++) 140 | { 141 | struct HSArrayPair *pair = (struct HSArrayPair *)vector_get(vector, index); 142 | _hs_types_array_pair_release(pair, release_value); 143 | } 144 | 145 | vector_release(vector); 146 | } 147 | 148 | 149 | static bool _hs_types_array_pair_add(struct Vector *vector, char *key, void *value) 150 | { 151 | if (vector == NULL || key == NULL) 152 | { 153 | return(false); 154 | } 155 | 156 | struct HSArrayPair *pair = malloc(sizeof(struct HSArrayPair)); 157 | pair->key = key; 158 | pair->value = value; 159 | 160 | vector_push(vector, pair); 161 | 162 | return(true); 163 | } 164 | 165 | 166 | static char *_hs_types_array_pair_get_key(struct Vector *vector, size_t index) 167 | { 168 | if (vector == NULL) 169 | { 170 | return(NULL); 171 | } 172 | 173 | struct HSArrayPair *pair = (struct HSArrayPair *)vector_get(vector, index); 174 | if (pair == NULL) 175 | { 176 | return(NULL); 177 | } 178 | 179 | return(pair->key); 180 | } 181 | 182 | 183 | static void *_hs_types_array_pair_get_value(struct Vector *vector, size_t index) 184 | { 185 | if (vector == NULL) 186 | { 187 | return(NULL); 188 | } 189 | 190 | struct HSArrayPair *pair = (struct HSArrayPair *)vector_get(vector, index); 191 | if (pair == NULL) 192 | { 193 | return(NULL); 194 | } 195 | 196 | return(pair->value); 197 | } 198 | 199 | 200 | static void *_hs_types_array_pair_get_by_key(struct Vector *vector, char *key) 201 | { 202 | if (vector == NULL || key == NULL) 203 | { 204 | return(NULL); 205 | } 206 | 207 | size_t count = vector_size(vector); 208 | for (size_t index = 0; index < count; index++) 209 | { 210 | struct HSArrayPair *pair = (struct HSArrayPair *)vector_get(vector, index); 211 | if (pair != NULL && stringfn_equal(pair->key, key)) 212 | { 213 | return(pair->value); 214 | } 215 | } 216 | 217 | return(NULL); 218 | } 219 | 220 | 221 | static void _hs_types_array_pair_remove_by_key(struct Vector *vector, char *key, bool release_value) 222 | { 223 | if (vector == NULL || key == NULL) 224 | { 225 | return; 226 | } 227 | 228 | size_t count = vector_size(vector); 229 | if (!count) 230 | { 231 | return; 232 | } 233 | 234 | for (size_t index = count - 1; ; index--) 235 | { 236 | struct HSArrayPair *pair = (struct HSArrayPair *)vector_get(vector, index); 237 | if (pair != NULL && stringfn_equal(pair->key, key)) 238 | { 239 | _hs_types_array_pair_release(pair, release_value); 240 | vector_remove(vector, index); 241 | } 242 | 243 | if (!index) 244 | { 245 | break; 246 | } 247 | } 248 | } 249 | 250 | -------------------------------------------------------------------------------- /tests/test_router_serve_next.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum HSServeFlowResponse _test_redirect(struct HSRoute *route, struct HSServeFlowParams *params) 11 | { 12 | assert_true(route != NULL); 13 | assert_true(params != NULL); 14 | 15 | params->response->code = HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT; 16 | hs_types_array_string_pair_add(params->response->headers, strdup("Location"), strdup("/mylocation")); 17 | 18 | return(HS_SERVE_FLOW_RESPONSE_DONE); 19 | } 20 | 21 | 22 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 23 | { 24 | assert_true(route != NULL); 25 | assert_true(params != NULL); 26 | 27 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 28 | params->response->mime_type = HS_MIME_TYPE_TEXT_HTML; 29 | params->response->content_string = strdup("my html"); 30 | 31 | return(HS_SERVE_FLOW_RESPONSE_DONE); 32 | } 33 | 34 | 35 | void _test_with_values(struct HSRouter *router, struct HSServerConnectionState *state, int socket, char *request) 36 | { 37 | bool done = hs_io_write_string_to_socket(state->socket, request, strlen(request)); 38 | 39 | assert_true(done); 40 | lseek(socket, strlen(request) * -1, SEEK_END); 41 | 42 | done = hs_router_serve_next(router, state); 43 | assert_true(done); 44 | } 45 | 46 | 47 | void test_impl() 48 | { 49 | struct HSRouter *router = hs_router_new(); 50 | 51 | struct HSRoute *route = hs_route_new(); 52 | 53 | route->is_get = true; 54 | route->path = strdup("/gohome"); 55 | route->serve = _test_redirect; 56 | hs_router_add_route(router, route); 57 | 58 | route = hs_route_new(); 59 | route->is_get = true; 60 | route->path = strdup("/index.html"); 61 | route->serve = _test_serve; 62 | hs_router_add_route(router, route); 63 | 64 | route = hs_router_as_route(router); 65 | route->path = strdup("/admin/"); 66 | 67 | router = hs_router_new(); 68 | hs_router_add_route(router, route); 69 | route = hs_routes_error_404_not_found_route_new(); 70 | hs_router_add_route(router, route); 71 | 72 | char *filename = "./test_router_serve_next.txt"; 73 | fsio_create_empty_file(filename); 74 | int socket = open(filename, O_RDWR); 75 | 76 | struct HSServerConnectionState *connection_state = hs_types_server_connection_state_new(); 77 | connection_state->socket = hs_socket_plain_new(socket); 78 | 79 | _test_with_values(router, connection_state, socket, "GET /test HTTP/1.0\r\n" 80 | "Connection: keep-alive\r\n" 81 | "Content-Length: 0\r\n" 82 | "\r\n"); 83 | 84 | _test_with_values(router, connection_state, socket, "GET /admin HTTP/1.0\r\n" 85 | "Connection: keep-alive\r\n" 86 | "Content-Length: 0\r\n" 87 | "\r\n"); 88 | 89 | _test_with_values(router, connection_state, socket, "GET /admin/ HTTP/1.0\r\n" 90 | "Connection: keep-alive\r\n" 91 | "Content-Length: 0\r\n" 92 | "\r\n"); 93 | 94 | _test_with_values(router, connection_state, socket, "GET /admin/gohome HTTP/1.0\r\n" 95 | "Connection: keep-alive\r\n" 96 | "Content-Length: 0\r\n" 97 | "\r\n"); 98 | 99 | _test_with_values(router, connection_state, socket, "GET /admin/index.html HTTP/1.0\r\n" 100 | "Connection: keep-alive\r\n" 101 | "Content-Length: 0\r\n" 102 | "\r\n"); 103 | 104 | hs_socket_close_and_release(connection_state->socket); 105 | hs_types_server_connection_state_release(connection_state); 106 | 107 | char *content = fsio_read_text_file(filename); 108 | fsio_remove(filename); 109 | assert_string_equal(content, "GET /test HTTP/1.0\r\n" 110 | "Connection: keep-alive\r\n" 111 | "Content-Length: 0\r\n" 112 | "\r\n" 113 | "HTTP/1.1 404 404\r\n" 114 | "Connection: keep-alive\r\n" 115 | "Content-Length: 0\r\n" 116 | "\r\n" 117 | "GET /admin HTTP/1.0\r\n" 118 | "Connection: keep-alive\r\n" 119 | "Content-Length: 0\r\n" 120 | "\r\n" 121 | "HTTP/1.1 404 404\r\n" 122 | "Connection: keep-alive\r\n" 123 | "Content-Length: 0\r\n" 124 | "\r\n" 125 | "GET /admin/ HTTP/1.0\r\n" 126 | "Connection: keep-alive\r\n" 127 | "Content-Length: 0\r\n" 128 | "\r\n" 129 | "HTTP/1.1 404 404\r\n" 130 | "Connection: keep-alive\r\n" 131 | "Content-Length: 0\r\n" 132 | "\r\n" 133 | "GET /admin/gohome HTTP/1.0\r\n" 134 | "Connection: keep-alive\r\n" 135 | "Content-Length: 0\r\n" 136 | "\r\n" 137 | "HTTP/1.1 307 307\r\n" 138 | "Location: /mylocation\r\n" 139 | "Connection: keep-alive\r\n" 140 | "Content-Length: 0\r\n" 141 | "\r\n" 142 | "GET /admin/index.html HTTP/1.0\r\n" 143 | "Connection: keep-alive\r\n" 144 | "Content-Length: 0\r\n" 145 | "\r\n" 146 | "HTTP/1.1 200 200\r\n" 147 | "Connection: keep-alive\r\n" 148 | "Content-Type: text/html\r\n" 149 | "Content-Length: 7\r\n" 150 | "\r\n" 151 | "my html"); 152 | hs_io_free(content); 153 | 154 | hs_router_release(router); 155 | } /* test_impl */ 156 | 157 | 158 | int main() 159 | { 160 | test_run(test_impl); 161 | } 162 | 163 | -------------------------------------------------------------------------------- /uncrustify.cfg: -------------------------------------------------------------------------------- 1 | output_tab_size = 2 2 | tok_split_gte = true 3 | indent_columns = 2 4 | indent_with_tabs = 0 5 | indent_class = true 6 | indent_member = 2 7 | indent_bool_paren = true 8 | indent_first_bool_expr = true 9 | sp_arith = force 10 | sp_assign = force 11 | sp_assign_default = force 12 | sp_bool = force 13 | sp_compare = force 14 | sp_inside_paren = remove 15 | sp_paren_paren = remove 16 | sp_before_ptr_star = force 17 | sp_between_ptr_star = remove 18 | sp_after_ptr_star = remove 19 | sp_before_byref = force 20 | sp_after_byref = remove 21 | sp_before_angle = remove 22 | sp_inside_angle = remove 23 | sp_after_angle = force 24 | sp_angle_paren = remove 25 | sp_angle_paren_empty = remove 26 | sp_angle_word = force 27 | sp_before_sparen = force 28 | sp_inside_sparen = remove 29 | sp_after_sparen = force 30 | sp_before_semi_for = remove 31 | sp_before_semi_for_empty = force 32 | sp_before_squares = remove 33 | sp_inside_square = remove 34 | sp_after_comma = force 35 | sp_before_ellipsis = remove 36 | sp_after_cast = remove 37 | sp_sizeof_paren = remove 38 | sp_inside_braces_enum = force 39 | sp_inside_braces_struct = force 40 | sp_inside_braces = force 41 | sp_func_proto_paren = remove 42 | sp_func_def_paren = remove 43 | sp_inside_fparen = remove 44 | sp_after_tparen_close = remove 45 | sp_func_call_paren = remove 46 | sp_return_paren = remove 47 | sp_attribute_paren = remove 48 | sp_defined_paren = force 49 | sp_brace_typedef = force 50 | sp_before_dc = remove 51 | sp_after_dc = remove 52 | sp_enum_assign = force 53 | align_func_params = true 54 | align_var_def_span = 2 55 | align_var_def_star_style = 1 56 | align_var_def_amp_style = 1 57 | align_var_def_colon = true 58 | align_var_def_inline = true 59 | align_assign_span = 1 60 | align_enum_equ_span = 4 61 | align_var_class_span = 2 62 | align_var_struct_span = 3 63 | align_struct_init_span = 3 64 | align_typedef_gap = 3 65 | align_typedef_span = 5 66 | align_typedef_star_style = 1 67 | align_right_cmt_span = 3 68 | align_nl_cont = true 69 | align_pp_define_gap = 4 70 | align_pp_define_span = 3 71 | align_asm_colon = true 72 | align_enum_equ_thresh = 8 73 | nl_assign_leave_one_liners = true 74 | nl_class_leave_one_liners = true 75 | nl_enum_leave_one_liners = true 76 | nl_getset_leave_one_liners = true 77 | nl_assign_brace = add 78 | nl_func_var_def_blk = 1 79 | nl_fcall_brace = add 80 | nl_enum_brace = force 81 | nl_struct_brace = add 82 | nl_union_brace = add 83 | nl_if_brace = add 84 | nl_brace_else = add 85 | nl_elseif_brace = add 86 | nl_else_brace = add 87 | nl_finally_brace = add 88 | nl_try_brace = add 89 | nl_for_brace = add 90 | nl_catch_brace = add 91 | nl_while_brace = add 92 | nl_do_brace = add 93 | nl_brace_while = remove 94 | nl_switch_brace = add 95 | nl_before_case = true 96 | nl_after_case = true 97 | nl_case_colon_brace = force 98 | nl_namespace_brace = force 99 | nl_constr_init_args = force 100 | nl_func_type_name = remove 101 | nl_func_paren = remove 102 | nl_func_def_paren = remove 103 | nl_func_decl_start = remove 104 | nl_func_def_start = remove 105 | nl_func_decl_start_single = remove 106 | nl_func_def_start_single = remove 107 | nl_func_decl_args = remove 108 | nl_func_decl_end = remove 109 | nl_func_def_end = remove 110 | nl_func_decl_end_single = remove 111 | nl_func_def_end_single = remove 112 | nl_fdef_brace = force 113 | nl_return_expr = remove 114 | nl_after_semicolon = true 115 | nl_after_brace_open = true 116 | nl_after_vbrace_open = true 117 | nl_after_brace_close = true 118 | nl_squeeze_ifdef = true 119 | nl_constr_colon = force 120 | pos_constr_comma = lead_force 121 | pos_constr_colon = lead_break 122 | pos_bool = lead 123 | pos_enum_comma = trail_force 124 | nl_max = 3 125 | nl_after_func_proto = 1 126 | nl_after_func_proto_group = 2 127 | nl_before_func_body_def = 3 128 | nl_after_access_spec = 1 129 | eat_blanks_after_open_brace = true 130 | eat_blanks_before_close_brace = true 131 | nl_after_return = true 132 | mod_full_brace_do = add 133 | mod_full_brace_for = add 134 | mod_full_brace_if = add 135 | mod_full_brace_while = add 136 | mod_paren_on_return = add 137 | mod_remove_extra_semicolon = true 138 | mod_add_long_function_closebrace_comment = 40 139 | mod_add_long_namespace_closebrace_comment = 5 140 | mod_add_long_switch_closebrace_comment = 40 141 | mod_remove_empty_return = true 142 | cmt_star_cont = true 143 | pp_indent = remove 144 | pp_space = remove 145 | mod_sort_include = true 146 | -------------------------------------------------------------------------------- /tests/test_router_serve_forever.c: -------------------------------------------------------------------------------- 1 | #include "fsio.h" 2 | #include "stringfn.h" 3 | #include "test.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int global_counter = -2; 11 | int global_plain_socket = 0; 12 | 13 | enum HSServeFlowResponse _test_redirect(struct HSRoute *route, struct HSServeFlowParams *params) 14 | { 15 | assert_true(route != NULL); 16 | assert_true(params != NULL); 17 | 18 | assert_string_equal(params->router_state->base_path, "/admin/"); 19 | 20 | params->response->code = HS_HTTP_RESPONSE_CODE_TEMPORARY_REDIRECT; 21 | hs_types_array_string_pair_add(params->response->headers, strdup("Location"), strdup("/mylocation")); 22 | 23 | return(HS_SERVE_FLOW_RESPONSE_DONE); 24 | } 25 | 26 | 27 | enum HSServeFlowResponse _test_serve(struct HSRoute *route, struct HSServeFlowParams *params) 28 | { 29 | assert_true(route != NULL); 30 | assert_true(params != NULL); 31 | 32 | assert_string_equal(params->router_state->base_path, "/admin/"); 33 | 34 | params->response->code = HS_HTTP_RESPONSE_CODE_OK; 35 | params->response->mime_type = HS_MIME_TYPE_TEXT_HTML; 36 | params->response->content_string = strdup("my html"); 37 | 38 | return(HS_SERVE_FLOW_RESPONSE_DONE); 39 | } 40 | 41 | 42 | bool _test_should_stop(struct HSRouter *router, struct HSSocket *socket, size_t counter, void *context) 43 | { 44 | if (router == NULL || !socket || context == NULL) 45 | { 46 | test_fail(); 47 | return(true); 48 | } 49 | 50 | char *context_string = (char *)context; 51 | assert_string_equal(context_string, "test context"); 52 | 53 | global_counter = counter; 54 | 55 | char *request = NULL; 56 | switch (counter) 57 | { 58 | case 0: 59 | request = "GET /test HTTP/1.0\r\n" 60 | "Connection: keep-alive\r\n" 61 | "Content-Length: 0\r\n" 62 | "\r\n"; 63 | break; 64 | 65 | case 1: 66 | request = "GET /admin HTTP/1.0\r\n" 67 | "Connection: keep-alive\r\n" 68 | "Content-Length: 0\r\n" 69 | "\r\n"; 70 | break; 71 | 72 | case 2: 73 | request = "GET /admin/ HTTP/1.0\r\n" 74 | "Connection: keep-alive\r\n" 75 | "Content-Length: 0\r\n" 76 | "\r\n"; 77 | break; 78 | 79 | case 3: 80 | request = "GET /admin/gohome HTTP/1.0\r\n" 81 | "Connection: keep-alive\r\n" 82 | "Content-Length: 0\r\n" 83 | "\r\n"; 84 | break; 85 | 86 | case 4: 87 | request = "GET /admin/index.html HTTP/1.0\r\n" 88 | "Connection: close\r\n" 89 | "Content-Length: 0\r\n" 90 | "\r\n"; 91 | break; 92 | 93 | default: 94 | test_fail(); 95 | } 96 | 97 | bool done = hs_io_write_string_to_socket(socket, request, strlen(request)); 98 | 99 | assert_true(done); 100 | lseek(global_plain_socket, strlen(request) * -1, SEEK_END); 101 | 102 | return(false); 103 | } /* _test_should_stop */ 104 | 105 | 106 | void test_impl() 107 | { 108 | struct HSRouter *router = hs_router_new(); 109 | 110 | struct HSRoute *route = hs_route_new(); 111 | 112 | route->is_get = true; 113 | route->path = strdup("/gohome"); 114 | route->serve = _test_redirect; 115 | hs_router_add_route(router, route); 116 | 117 | route = hs_route_new(); 118 | route->is_get = true; 119 | route->path = strdup("/index.html"); 120 | route->serve = _test_serve; 121 | hs_router_add_route(router, route); 122 | 123 | route = hs_router_as_route(router); 124 | route->path = strdup("/admin/"); 125 | 126 | router = hs_router_new(); 127 | hs_router_add_route(router, route); 128 | route = hs_routes_error_404_not_found_route_new(); 129 | hs_router_add_route(router, route); 130 | 131 | char *filename = "./test_router_serve_forever.txt"; 132 | fsio_create_empty_file(filename); 133 | int socket = open(filename, O_RDWR); 134 | 135 | global_plain_socket = socket; 136 | struct HSSocket *hssocket = hs_socket_plain_new(socket); 137 | bool done = hs_router_serve_forever(router, hssocket, "test context", _test_should_stop); 138 | hs_socket_close_and_release(hssocket); 139 | assert_true(done); 140 | 141 | char *content = fsio_read_text_file(filename); 142 | fsio_remove(filename); 143 | assert_string_equal(content, "GET /test HTTP/1.0\r\n" 144 | "Connection: keep-alive\r\n" 145 | "Content-Length: 0\r\n" 146 | "\r\n" 147 | "HTTP/1.1 404 404\r\n" 148 | "Connection: keep-alive\r\n" 149 | "Content-Length: 0\r\n" 150 | "\r\n" 151 | "GET /admin HTTP/1.0\r\n" 152 | "Connection: keep-alive\r\n" 153 | "Content-Length: 0\r\n" 154 | "\r\n" 155 | "HTTP/1.1 404 404\r\n" 156 | "Connection: keep-alive\r\n" 157 | "Content-Length: 0\r\n" 158 | "\r\n" 159 | "GET /admin/ HTTP/1.0\r\n" 160 | "Connection: keep-alive\r\n" 161 | "Content-Length: 0\r\n" 162 | "\r\n" 163 | "HTTP/1.1 404 404\r\n" 164 | "Connection: keep-alive\r\n" 165 | "Content-Length: 0\r\n" 166 | "\r\n" 167 | "GET /admin/gohome HTTP/1.0\r\n" 168 | "Connection: keep-alive\r\n" 169 | "Content-Length: 0\r\n" 170 | "\r\n" 171 | "HTTP/1.1 307 307\r\n" 172 | "Location: /mylocation\r\n" 173 | "Connection: keep-alive\r\n" 174 | "Content-Length: 0\r\n" 175 | "\r\n" 176 | "GET /admin/index.html HTTP/1.0\r\n" 177 | "Connection: close\r\n" 178 | "Content-Length: 0\r\n" 179 | "\r\n" 180 | "HTTP/1.1 200 200\r\n" 181 | "Connection: close\r\n" 182 | "Content-Type: text/html\r\n" 183 | "Content-Length: 7\r\n" 184 | "\r\n" 185 | "my html"); 186 | hs_io_free(content); 187 | assert_num_equal(global_counter, 4); // the 5th closes connection so its not counted 188 | 189 | hs_router_release(router); 190 | } /* test_impl */ 191 | 192 | 193 | int main() 194 | { 195 | test_run(test_impl); 196 | } 197 | 198 | -------------------------------------------------------------------------------- /include/hs_types.h: -------------------------------------------------------------------------------- 1 | #ifndef HS_TYPES_H 2 | #define HS_TYPES_H 3 | 4 | #include "hs_constants.h" 5 | #include "hs_socket.h" 6 | #include "hs_types_array.h" 7 | #include "hs_types_cookie.h" 8 | #include 9 | #include 10 | #include 11 | 12 | struct HSHttpRequestPayload; 13 | 14 | struct HSRouterFlowState 15 | { 16 | bool done; 17 | bool closed_connection; 18 | char *base_path; 19 | }; 20 | 21 | struct HSRouteFlowState 22 | { 23 | // string key/value pairs (all values will be freed at end of request) 24 | struct HSArrayStringPair *string_pairs; 25 | // data table (values are freed at end of request based on provided release function) 26 | struct HashTable *data; 27 | }; 28 | 29 | struct HSHttpRequest 30 | { 31 | enum HSHttpMethod method; 32 | char *domain; 33 | int port; 34 | bool ssl; 35 | char *resource; 36 | char *query_string; 37 | // specific headers 38 | char *user_agent; 39 | char *authorization; 40 | size_t content_length; 41 | enum HSConnectionType connection; 42 | struct HSCookies *cookies; 43 | // all headers 44 | struct HSArrayStringPair *headers; 45 | struct HSHttpRequestPayload *payload; 46 | }; 47 | 48 | struct HSHttpResponse 49 | { 50 | // The status code 51 | enum HSHttpResponseCode code; 52 | // Any cookies we want to set/delete 53 | struct HSCookies *cookies; 54 | // Any extra headers to return 55 | struct HSArrayStringPair *headers; 56 | // The content type header value, if the enum doesn't contain a relevant value 57 | // use the HS_MIME_TYPE_NONE and add the actual value manually to the headers array. 58 | enum HSMimeType mime_type; 59 | // The content to return (one of the following) 60 | char *content_string; 61 | char *content_file; 62 | }; 63 | 64 | struct HSPostResponseCallback 65 | { 66 | void *context; 67 | void (*run)(struct HSPostResponseCallback *); 68 | void (*release)(struct HSPostResponseCallback *); 69 | }; 70 | 71 | struct HSPostResponseCallbacks 72 | { 73 | struct HSPostResponseCallback **callbacks; 74 | size_t count; 75 | size_t capacity; 76 | }; 77 | 78 | struct HSServerConnectionState 79 | { 80 | struct HSSocket *socket; 81 | size_t request_counter; 82 | time_t creation_time; 83 | }; 84 | 85 | struct HSServeFlowParams 86 | { 87 | struct HSHttpRequest *request; 88 | struct HSHttpResponse *response; 89 | // Optional callbacks after response is written 90 | struct HSPostResponseCallbacks *callbacks; 91 | // route state, can be used by the routes to store data in the context of the request 92 | struct HSRouteFlowState *route_state; 93 | // router state, used internally by the router 94 | struct HSRouterFlowState *router_state; 95 | // connection state, can be used to check connection tracking info 96 | struct HSServerConnectionState *connection_state; 97 | }; 98 | 99 | enum HSServeFlowResponse 100 | { 101 | HS_SERVE_FLOW_RESPONSE_CONTINUE = 1, 102 | HS_SERVE_FLOW_RESPONSE_DONE = 2, 103 | }; 104 | 105 | /** 106 | * Creates and returns a new struct. 107 | */ 108 | struct HSServeFlowParams *hs_types_serve_flow_params_new(void); 109 | 110 | /** 111 | * Creates and returns a new struct. 112 | */ 113 | struct HSServeFlowParams *hs_types_serve_flow_params_new_pre_populated(struct HSHttpRequest *); 114 | 115 | /** 116 | * Frees all internal memory and struct. 117 | */ 118 | void hs_types_serve_flow_params_release(struct HSServeFlowParams *); 119 | 120 | /** 121 | * Creates and returns a new http request struct. 122 | */ 123 | struct HSHttpRequest *hs_types_http_request_new(void); 124 | 125 | /** 126 | * Frees all memory used by the provided struct, including 127 | * any internal member/struct. 128 | */ 129 | void hs_types_http_request_release(struct HSHttpRequest *); 130 | 131 | /** 132 | * Creates and returns a new http response struct. 133 | */ 134 | struct HSHttpResponse *hs_types_http_response_new(void); 135 | 136 | /** 137 | * Frees all memory used by the provided struct, including 138 | * any internal member/struct. 139 | */ 140 | void hs_types_http_response_release(struct HSHttpResponse *); 141 | 142 | /** 143 | * Creates and returns the new struct. 144 | */ 145 | struct HSPostResponseCallback *hs_types_post_response_callback_new(void); 146 | 147 | /** 148 | * Releases the struct memory, not including the context. 149 | * Optional release function (if defined) will be invoked. 150 | */ 151 | void hs_types_post_response_callback_release(struct HSPostResponseCallback *); 152 | 153 | /** 154 | * Creates and returns the new struct. 155 | */ 156 | struct HSPostResponseCallbacks *hs_types_post_response_callbacks_new(size_t /* capacity */); 157 | 158 | /** 159 | * Releases the struct memory, including all sub callbacks. 160 | */ 161 | void hs_types_post_response_callbacks_release(struct HSPostResponseCallbacks *); 162 | 163 | /** 164 | * Adds additional callback. 165 | * If needed, a new internal array will be allocated with enough capacity. 166 | */ 167 | bool hs_types_post_response_callbacks_add(struct HSPostResponseCallbacks *, struct HSPostResponseCallback *); 168 | 169 | /** 170 | * Creates and returns a new state struct. 171 | */ 172 | struct HSRouterFlowState *hs_types_router_flow_state_new(void); 173 | 174 | /** 175 | * Frees all memory used by the provided struct, including 176 | * any internal member/struct. 177 | */ 178 | void hs_types_router_flow_state_release(struct HSRouterFlowState *); 179 | 180 | /** 181 | * Creates and returns a new state struct. 182 | */ 183 | struct HSRouteFlowState *hs_types_route_flow_state_new(void); 184 | 185 | /** 186 | * Frees all memory used by the provided struct, including 187 | * any internal member/struct. 188 | */ 189 | void hs_types_route_flow_state_release(struct HSRouteFlowState *); 190 | 191 | /** 192 | * Creates and returns a new state struct. 193 | */ 194 | struct HSServerConnectionState *hs_types_server_connection_state_new(void); 195 | 196 | /** 197 | * Frees all memory used by the provided struct, including 198 | * any internal member/struct. 199 | */ 200 | void hs_types_server_connection_state_release(struct HSServerConnectionState *); 201 | 202 | /** 203 | * Internal function. 204 | */ 205 | struct HSHttpRequestPayload *hs_types_http_request_new_payload(void *); 206 | 207 | /** 208 | * Returns true if we already loaded the payload content, making this struct 209 | * no longer usable. 210 | */ 211 | bool hs_types_http_request_payload_is_loaded(struct HSHttpRequest *); 212 | 213 | /** 214 | * Loads the entire payload to memory and returns it. 215 | * Once loaded, the payload struct is no longer usable. 216 | */ 217 | char *hs_types_http_request_payload_to_string(struct HSHttpRequest *); 218 | 219 | /** 220 | * Writes the entire payload to the requested file. 221 | * Once loaded, the payload struct is no longer usable. 222 | */ 223 | bool hs_types_http_request_payload_to_file(struct HSHttpRequest *, char * /* filename */); 224 | 225 | #endif 226 | 227 | -------------------------------------------------------------------------------- /src/hs_io.c: -------------------------------------------------------------------------------- 1 | #include "hs_io.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define HS_IO_READ_BUFFER_SIZE 256 8 | 9 | static char *_hs_io_read_line_from_buffer(struct StringBuffer *, bool *); 10 | 11 | 12 | void hs_io_free(void *ptr) 13 | { 14 | if (ptr == NULL) 15 | { 16 | return; 17 | } 18 | 19 | free(ptr); 20 | } 21 | 22 | 23 | char *hs_io_read_line(struct HSSocket *socket, struct StringBuffer *work_buffer) 24 | { 25 | if (!hs_socket_is_open(socket) || work_buffer == NULL) 26 | { 27 | return(NULL); 28 | } 29 | 30 | // it is possible that from previous run, we already have a line in the buffer 31 | bool eof = false; 32 | char *line = _hs_io_read_line_from_buffer(work_buffer, &eof); 33 | if (eof) 34 | { 35 | return(NULL); 36 | } 37 | if (line != NULL) 38 | { 39 | return(line); 40 | } 41 | 42 | ssize_t size = 0; 43 | char buffer[HS_IO_READ_BUFFER_SIZE] = { 0 }; 44 | do 45 | { 46 | // read next bulk 47 | size = hs_socket_read(socket, buffer, HS_IO_READ_BUFFER_SIZE); 48 | 49 | if (size) 50 | { 51 | buffer[size] = 0; 52 | stringbuffer_append_string(work_buffer, buffer); 53 | 54 | // attempt to get next line from buffer 55 | line = _hs_io_read_line_from_buffer(work_buffer, &eof); 56 | if (eof) 57 | { 58 | // reached end of header and start of payload 59 | return(NULL); 60 | } 61 | if (line != NULL) 62 | { 63 | return(line); 64 | } 65 | } 66 | } while (size > 0); 67 | 68 | return(NULL); 69 | } /* hs_io_read_line */ 70 | 71 | struct HSIOHttpRequestPayload *hs_io_new_http_request_payload(struct HSSocket *socket, struct StringBuffer *buffer) 72 | { 73 | if (!hs_socket_is_open(socket) || buffer == NULL) 74 | { 75 | return(NULL); 76 | } 77 | 78 | struct HSIOHttpRequestPayload *payload = malloc(sizeof(struct HSIOHttpRequestPayload)); 79 | payload->socket = socket; 80 | payload->partial = buffer; 81 | 82 | return(payload); 83 | } 84 | 85 | 86 | void hs_io_release_http_request_payload(struct HSIOHttpRequestPayload *payload) 87 | { 88 | if (payload == NULL) 89 | { 90 | return; 91 | } 92 | 93 | stringbuffer_release(payload->partial); 94 | hs_io_free(payload); 95 | } 96 | 97 | 98 | bool hs_io_read_fully(struct HSSocket *socket, struct StringBuffer *buffer, size_t length) 99 | { 100 | if (!hs_socket_is_open(socket) || buffer == NULL) 101 | { 102 | return(false); 103 | } 104 | 105 | if (!length) 106 | { 107 | return(true); 108 | } 109 | 110 | ssize_t size = 0; 111 | char io_buffer[HS_IO_READ_BUFFER_SIZE] = { 0 }; 112 | size_t left = length; 113 | do 114 | { 115 | // read next bulk 116 | size_t buffer_size = HS_IO_READ_BUFFER_SIZE; 117 | if (left < HS_IO_READ_BUFFER_SIZE) 118 | { 119 | buffer_size = left; 120 | } 121 | size = hs_socket_read(socket, io_buffer, buffer_size); 122 | 123 | if (size > 0) 124 | { 125 | left = left - (size_t)size; 126 | io_buffer[size] = 0; 127 | stringbuffer_append_string(buffer, io_buffer); 128 | } 129 | } while (size > 0 && left > 0); 130 | 131 | return(!left); 132 | } 133 | 134 | 135 | bool hs_io_read_and_write_to_file(struct HSSocket *socket, FILE *fp, size_t length) 136 | { 137 | if (!hs_socket_is_open(socket)) 138 | { 139 | return(false); 140 | } 141 | 142 | if (!length) 143 | { 144 | return(true); 145 | } 146 | 147 | ssize_t size = 0; 148 | char io_buffer[HS_IO_READ_BUFFER_SIZE] = { 0 }; 149 | size_t left = length; 150 | do 151 | { 152 | // read next bulk 153 | size_t buffer_size = HS_IO_READ_BUFFER_SIZE; 154 | if (left < HS_IO_READ_BUFFER_SIZE) 155 | { 156 | buffer_size = left; 157 | } 158 | size = hs_socket_read(socket, io_buffer, buffer_size); 159 | 160 | if (size > 0) 161 | { 162 | left = left - (size_t)size; 163 | size_t written = fwrite(io_buffer, 1, (size_t)size, fp); 164 | if (written < (size_t)size) 165 | { 166 | return(false); 167 | } 168 | } 169 | } while (size > 0 && left > 0); 170 | 171 | return(!left); 172 | } 173 | 174 | 175 | bool hs_io_write_string_to_socket(struct HSSocket *socket, char *content, size_t length) 176 | { 177 | if (!hs_socket_is_open(socket) || content == NULL) 178 | { 179 | return(false); 180 | } 181 | 182 | char *ptr = content; 183 | size_t left = length; 184 | if (!left) 185 | { 186 | return(true); 187 | } 188 | 189 | do 190 | { 191 | ssize_t written = hs_socket_write(socket, ptr, left); 192 | 193 | // in case of error 194 | if (written < 0) 195 | { 196 | return(false); 197 | } 198 | else if (written > 0) 199 | { 200 | left = left - (size_t)written; 201 | } 202 | } while (left > 0); 203 | 204 | return(true); 205 | } 206 | 207 | 208 | bool hs_io_write_file_to_socket(struct HSSocket *socket, char *filename) 209 | { 210 | if (!hs_socket_is_open(socket) || filename == NULL) 211 | { 212 | return(false); 213 | } 214 | 215 | FILE *fp = fopen(filename, "rb"); 216 | 217 | if (fp == NULL) 218 | { 219 | return(false); 220 | } 221 | 222 | char buffer[HS_IO_READ_BUFFER_SIZE] = { 0 }; 223 | bool done = true; 224 | do 225 | { 226 | if (feof(fp)) 227 | { 228 | break; 229 | } 230 | 231 | size_t read = fread(buffer, 1, HS_IO_READ_BUFFER_SIZE - 1, fp); 232 | if (!read) 233 | { 234 | done = false; 235 | break; 236 | } 237 | void hs_io_release_hashtable_key(char *, void *); 238 | 239 | buffer[read] = '\0'; 240 | 241 | done = hs_io_write_string_to_socket(socket, buffer, read); 242 | } while (done); 243 | 244 | if (done) 245 | { 246 | done = feof(fp); 247 | } 248 | 249 | fclose(fp); 250 | 251 | return(done); 252 | } /* hs_io_write_file_to_socket */ 253 | 254 | 255 | void hs_io_release_hashtable_key(char *key, void *data) 256 | { 257 | hs_io_free(key); 258 | 259 | if (data == NULL) 260 | { 261 | // for static check 262 | return; 263 | } 264 | } 265 | 266 | 267 | void hs_io_release_hashtable_key_and_value(char *key, void *data) 268 | { 269 | hs_io_free(key); 270 | hs_io_free(data); 271 | } 272 | 273 | 274 | static char *_hs_io_read_line_from_buffer(struct StringBuffer *buffer, bool *eof) 275 | { 276 | size_t length = stringbuffer_get_content_size(buffer); 277 | 278 | if (length < 2) 279 | { 280 | return(NULL); 281 | } 282 | 283 | char *content = stringbuffer_to_string(buffer); 284 | char *line = NULL; 285 | for (size_t index = 0; index < length - 1; index++) 286 | { 287 | if (content[index] == '\r' && content[index + 1] == '\n') 288 | { 289 | if (index) 290 | { 291 | line = stringfn_mut_substring(content, 0, index); 292 | } 293 | else 294 | { 295 | *eof = true; 296 | } 297 | stringbuffer_clear(buffer); 298 | 299 | if (length > (index + 1)) 300 | { 301 | stringbuffer_append_string(buffer, content + index + 2); 302 | } 303 | 304 | break; 305 | } 306 | } 307 | 308 | if (line == NULL) 309 | { 310 | hs_io_free(content); 311 | } 312 | 313 | return(line); 314 | } /* _hs_io_read_line_from_buffer */ 315 | 316 | 317 | void hs_io_noop(void *ptr) 318 | { 319 | // used to make compiler happy 320 | if (ptr == NULL) 321 | { 322 | return; 323 | } 324 | } 325 | 326 | --------------------------------------------------------------------------------