├── .clang-format ├── .github └── workflows │ ├── linux.yml │ └── macos.yml ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── amalgamate.py ├── benchmark ├── benchmark.md ├── drogon_hello.cpp ├── drogon_static.cpp ├── result.png ├── tiny_http_hello.c └── tiny_http_static.c ├── examples ├── custom_logger.c ├── echo.c ├── file_server.c ├── file_upload.c └── hello_world.c ├── include └── th.h ├── requirements.txt ├── src ├── th_acceptor.c ├── th_acceptor.h ├── th_align.h ├── th_allocator.c ├── th_allocator.h ├── th_allocator_test.c ├── th_config.h ├── th_conn.c ├── th_conn.h ├── th_conn_tracker.c ├── th_conn_tracker.h ├── th_context.c ├── th_context.h ├── th_date.c ├── th_dir.c ├── th_dir.h ├── th_dir_mgr.c ├── th_dir_mgr.h ├── th_dir_mgr_test.c ├── th_error.c ├── th_fcache.c ├── th_fcache.h ├── th_fcache_test.c ├── th_file.c ├── th_file.h ├── th_fmt.c ├── th_fmt.h ├── th_hash.h ├── th_hashmap.h ├── th_hashmap_test.c ├── th_header_id.gperf ├── th_header_id.h ├── th_heap_string.c ├── th_heap_string.h ├── th_heap_string_test.c ├── th_http.c ├── th_http.h ├── th_http_error.h ├── th_io_composite.c ├── th_io_composite.h ├── th_io_op.c ├── th_io_op.h ├── th_io_op_bsd.c ├── th_io_op_bsd.h ├── th_io_op_linux.c ├── th_io_op_linux.h ├── th_io_op_mock.c ├── th_io_op_mock.h ├── th_io_op_posix.c ├── th_io_op_posix.h ├── th_io_service.c ├── th_io_service.h ├── th_io_task.c ├── th_io_task.h ├── th_io_task_test.c ├── th_iov.h ├── th_kqueue_service.c ├── th_kqueue_service.h ├── th_list.h ├── th_list_test.c ├── th_listener.c ├── th_listener.h ├── th_log.c ├── th_log.h ├── th_method.gperf ├── th_method.h ├── th_mime.gperf ├── th_mime.h ├── th_mock_service.c ├── th_mock_service.h ├── th_mock_syscall.c ├── th_mock_syscall.h ├── th_path.c ├── th_path.h ├── th_poll_service.c ├── th_poll_service.h ├── th_queue.h ├── th_refcounted.h ├── th_request.c ├── th_request.h ├── th_request_parser.c ├── th_request_parser.h ├── th_request_parser_test.c ├── th_response.c ├── th_response.h ├── th_response_test.c ├── th_router.c ├── th_router.h ├── th_router_test.c ├── th_runner.c ├── th_runner.h ├── th_server.c ├── th_socket.c ├── th_socket.h ├── th_ssl_context.c ├── th_ssl_context.h ├── th_ssl_error.c ├── th_ssl_error.h ├── th_ssl_smem_bio.c ├── th_ssl_smem_bio.h ├── th_ssl_socket.c ├── th_ssl_socket.h ├── th_ssl_socket_test.c ├── th_string.c ├── th_string.h ├── th_string_test.c ├── th_system_error.h ├── th_task.c ├── th_task.h ├── th_task_test.c ├── th_tcp_socket.c ├── th_tcp_socket.h ├── th_tcp_socket_test.c ├── th_test.c ├── th_test.h ├── th_timer.c ├── th_timer.h ├── th_upload.c ├── th_upload.h ├── th_url_decode.c ├── th_url_decode.h ├── th_url_decode_test.c ├── th_utility.h ├── th_vec.h └── th_vec_test.c ├── th.c └── th.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Language: Cpp 3 | IndentWidth: 4 4 | AllowShortFunctionsOnASingleLine: true 5 | PointerBindsToType: true 6 | PointerAlignment: Left 7 | IncludeBlocks: Preserve 8 | ColumnLimit: 0 9 | MaxEmptyLinesToKeep: 1 10 | UseTab: Never 11 | SortIncludes: true 12 | BreakBeforeBraces: Custom 13 | BreakBeforeBinaryOperators: NonAssignment 14 | BraceWrapping: 15 | AfterCaseLabel: false 16 | AfterClass: false 17 | AfterControlStatement: false 18 | AfterEnum: false 19 | AfterFunction: true 20 | AfterNamespace: true 21 | AfterObjCDeclaration: false 22 | AfterStruct: false 23 | AfterUnion: false 24 | BeforeCatch: false 25 | BeforeElse: false 26 | IndentBraces: false 27 | StatementMacros: [ 'TH_TEST_BEGIN(name)', 'TH_TEST_END' , 'TH_TEST_CASE_BEGIN(name)', 'TH_TEST_CASE_END' , 'TH_EXPECT(x)', 'TH_STRING(x)'] 28 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Install dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install -y build-essential gperf 21 | - name: Set up Python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.x' 25 | - name: Install Python dependencies 26 | run: | 27 | pip install -r requirements.txt 28 | - name: Build 29 | run: | 30 | mkdir build; cd build 31 | cmake .. -DPython3_EXECUTABLE==$(which python) 32 | make -j$(nproc) 33 | - name: Test 34 | run: | 35 | cd build 36 | ctest 37 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: macos-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Install dependencies 18 | run: | 19 | brew install gperf 20 | - name: Set up Python 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: '3.x' 24 | - name: Install Python dependencies 25 | run: | 26 | pip install -r requirements.txt 27 | - name: Build 28 | run: | 29 | mkdir build; cd build 30 | cmake .. -DPython3_EXECUTABLE==$(which python) 31 | make -j$(nproc) 32 | - name: Test 33 | run: | 34 | cd build 35 | ctest 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphiaRa/tiny_http/f558425d448104ae751d2183aee0593075915c6b/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Raphael Schlarb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiny_http - Work in Progress... 2 |
3 | 4 | [![Linux](https://github.com/RaphiaRa/tiny_http/actions/workflows/linux.yml/badge.svg?branch=main)](https://github.com/RaphiaRa/tiny_http/actions/workflows/linux.yml) 5 | [![MacOS](https://github.com/RaphiaRa/tiny_http/actions/workflows/macos.yml/badge.svg?branch=main)](https://github.com/RaphiaRa/tiny_http/actions/workflows/macos.yml) 6 | 7 |
8 | tiny_http is my attempt at creating a lightweight, easy-to-use, and embeddable HTTP server library in C99. 9 | 10 | ##### Hello, World! Example: 11 | ```c 12 | #include "th.h" 13 | 14 | static th_err 15 | handler(void* userp, const th_request* req, th_response* resp) 16 | { 17 | th_set_body(resp, "Hello, World!"); 18 | th_add_header(resp, "Content-Type", "text/plain"); 19 | return TH_ERR_OK; 20 | } 21 | 22 | int main() 23 | { 24 | th_server* server; 25 | th_server_create(&server, NULL); 26 | th_bind(server, "0.0.0.0", "8080", NULL); 27 | th_route(server, TH_METHOD_GET, "/", handler, NULL); 28 | while (1) { 29 | th_poll(server, 1000); 30 | } 31 | return 0; 32 | } 33 | ``` 34 | Simply copy the code above to a file (e.g. `hello.c`) where you have the `th.h` and `th.c` files, and compile it with: 35 | ```sh 36 | $ gcc -o hello hello.c th.c 37 | ``` 38 | Then run the server with: 39 | ```sh 40 | $ ./hello 41 | ``` 42 | 43 | I wrote this library because I wanted a simple drop-in solution for the legacy C and C++ codebases I work with, hence the lack of dependencies and new language features. It is not designed to be a full-fledged web server, but rather a simple tool to build small web applications or to serve static files. No threading or forking is used. 44 | 45 | ## Features 46 | 47 | - Simple integration (just copy the `th.h` and `th.c` files to your project) 48 | - HTTPS support (via OpenSSL) (Works, but still needs to be optimized as it's quite slow) 49 | - Path capturing (e.g. `/user/{id}`) 50 | - Supports Linux and MacOS (Windows support is planned) 51 | - Fully customizable memory allocation and logging 52 | - File Uploads (Multipart form data) 53 | 54 | ## Planned features 55 | 56 | - Websockets 57 | 58 | ## Dependencies 59 | 60 | - The C standard library 61 | - OpenSSL (optional, for HTTPS support) 62 | - gperf (optinal, for binary builds and amalgamation) 63 | - python3 (optional, for running the amalgamation script) 64 | 65 | ## Enabling HTTPS 66 | 67 | To enable HTTPS support, you need to link against OpenSSL and set `TH_WITH_SSL=1` when compiling the library. 68 | ```sh 69 | $ gcc -o myserver myserver.c th.c -lssl -lcrypto -DTH_WITH_SSL=1 70 | ``` 71 | Pass the certificate and private key file paths to `th_bind`: 72 | ```c 73 | th_bind_opt opt = { 74 | .cert_file = "cert.pem", 75 | .key_file = "key.pem", 76 | }; 77 | th_bind(server, "0.0.0.0", "433", &opt); 78 | ``` 79 | 80 | ## Logging 81 | 82 | Logging can be configured via the `TH_LOG_LEVEL` macro. 83 | ```c 84 | $ gcc -o myserver myserver.c th.c -DTH_LOG_LEVEL=TH_LOG_LEVEL_TRACE 85 | ``` 86 | Possible values are: 87 | ```c 88 | TH_LOG_LEVEL_NONE 89 | TH_LOG_LEVEL_ERROR 90 | TH_LOG_LEVEL_WARN 91 | TH_LOG_LEVEL_INFO 92 | TH_LOG_LEVEL_DEBUG 93 | TH_LOG_LEVEL_TRACE 94 | ``` 95 | By default, tiny_http logs to `stderr`, 96 | but users can provide their own logging function (See [examples/custom_logger.c](examples/custom_logger.c)). 97 | 98 | ## Building binaries, examples, and tests 99 | 100 | Library builds, examples, and tests can be built using CMake (This requires gperf to be installed). 101 | ```sh 102 | $ mkdir build; cd build 103 | $ cmake .. 104 | $ make 105 | ``` 106 | 107 | ## More examples 108 | 109 | Example - Path capturing: 110 | ```c 111 | #include "th.h" 112 | 113 | static th_err 114 | handler(void* userp, const th_request* req, th_response* resp) 115 | { 116 | const char* msg = th_find_pathvar(req, "msg"); 117 | th_printf_body(resp, "Hello, %s!", msg); 118 | th_add_header(resp, "Content-Type", "text/plain"); 119 | return TH_ERR_OK; 120 | } 121 | 122 | int main(void) 123 | { 124 | th_server* server; 125 | th_server_create(&server, NULL); 126 | th_bind(server, "0.0.0.0", "8080", NULL); 127 | th_route(server, TH_METHOD_GET, "/{msg}", handler, NULL); 128 | while (1) { 129 | th_poll(server, 1000); 130 | } 131 | return 0; 132 | } 133 | ``` 134 | It's possible to specify a capture type by adding a colon before the parameter name: `{string:param}` (Default if nothing is specified), `{int:param}`, `{path:param}`. 135 | 136 | Example - File serving: 137 | ```c 138 | #include "th.h" 139 | 140 | static th_err 141 | handle_path(void* userp, const th_request* req, th_response* resp) 142 | { 143 | const char* path = th_find_pathvar(req, "path"); 144 | th_set_body_from_file(resp, "root", path); 145 | return TH_ERR_OK; 146 | } 147 | 148 | static th_err 149 | handle_index(void* userp, const th_request* req, th_response* resp) 150 | { 151 | th_set_body_from_file(resp, "root", "index.html"); 152 | return TH_ERR_OK; 153 | } 154 | 155 | int main(void) 156 | { 157 | th_server* server; 158 | th_server_create(&server, NULL); 159 | th_bind(server, "0.0.0.0", "8080", NULL); 160 | th_add_dir(server, "root", "/path/to/your/files"); 161 | th_route(server, TH_METHOD_GET, "/{path:path}", handle_path, NULL); 162 | th_route(server, TH_METHOD_GET, "/", handle_index, NULL); 163 | while (1) { 164 | th_poll(server, 1000); 165 | } 166 | return 0; 167 | } 168 | ``` 169 | 170 | More detailed examples can be found in the `examples` directory. 171 | 172 | ## Performance 173 | 174 | Although tiny_http is not designed to be high-performance, it's still quite fast. 175 | Here is a comparison with [Drogon](https://github.com/drogonframework/drogon) (One of my favorite C++ web frameworks) on my cloud server (2 dedicated AMD Epyc vCPUs). 176 | [![Benchmark](benchmark/result.png)](benchmark/benchmark.md) 177 | 178 | Notes: 179 | - Drogon will, of course scale much better with more threads. This is just to give a rough idea of tiny_http's performance. 180 | - The slower static file test is probably because of tiny_http not using `sendfile` on Linux yet. 181 | -------------------------------------------------------------------------------- /benchmark/benchmark.md: -------------------------------------------------------------------------------- 1 | ## Benchmark 2 | 3 | To reproduce the benchmark results, first create the testfile for 4 | the static file test (Only if you want to actually run that test): 5 | 6 | ```bash 7 | mkdir -p testfiles 8 | dd if=/dev/urandom of=testfiles/test_50000 bs=50000 count=1 9 | ``` 10 | 11 | Compile respective benchmarks (Compile commands are 12 | in the file header comments) and run them. 13 | Running the static file test: 14 | ```bash 15 | wrk -c 512 -d 1s http://localhost:8080/test_50000 16 | ``` 17 | Running the hello world test: 18 | ```bash 19 | wrk -c 512 -d 1s http://localhost:8080/ 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /benchmark/drogon_hello.cpp: -------------------------------------------------------------------------------- 1 | // g++ static.cpp -O3 -ldrogon -ljsoncpp -ltrantor -luuid -lz -o static 2 | 3 | #include 4 | 5 | int main() 6 | { 7 | drogon::app().setThreadNum(1).registerHandler( 8 | "/", 9 | [](const drogon::HttpRequestPtr& req, std::function&& callback) { 10 | auto resp = drogon::HttpResponse::newHttpResponse(); 11 | resp->setBody("Hello World!"); 12 | callback(resp); 13 | }, 14 | {drogon::Get}); 15 | drogon::app().addListener("0.0.0.0", 8080); 16 | drogon::app().enableGzip(false); 17 | drogon::app().run(); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /benchmark/drogon_static.cpp: -------------------------------------------------------------------------------- 1 | // g++ static.cpp -O3 -ldrogon -ljsoncpp -ltrantor -luuid -lz -o static 2 | 3 | #include 4 | 5 | int main() 6 | { 7 | drogon::app() 8 | .addListener("0.0.0.0", 8080) 9 | .setDocumentRoot("./testfiles") 10 | .run(); 11 | drogon::app().enableGzip(false); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphiaRa/tiny_http/f558425d448104ae751d2183aee0593075915c6b/benchmark/result.png -------------------------------------------------------------------------------- /benchmark/tiny_http_hello.c: -------------------------------------------------------------------------------- 1 | // gcc ../benchmark/tiny_http_hello.c th.c -O3 -DTH_CONFIG_MAX_CONNECTIONS=1024 -o tiny_http_hello 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static th_server* server = NULL; 10 | 11 | static th_err 12 | handler(void* userp, const th_request* req, th_response* resp) 13 | { 14 | (void)userp; 15 | (void)req; 16 | th_set_body(resp, "Hello, World!"); 17 | th_add_header(resp, "Content-Type", "text/plain"); 18 | return TH_ERR_OK; 19 | } 20 | 21 | int main() 22 | { 23 | th_err err = TH_ERR_OK; 24 | th_server* server = NULL; 25 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) 26 | goto cleanup; 27 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 28 | goto cleanup; 29 | if ((err = th_route(server, TH_METHOD_GET, "/", handler, NULL)) != TH_ERR_OK) 30 | goto cleanup; 31 | while (1) { 32 | th_poll(server, 1000); 33 | } 34 | fprintf(stderr, "Shutting down...\n"); 35 | cleanup: 36 | if (err != TH_ERR_OK) { 37 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 38 | return EXIT_FAILURE; 39 | } 40 | th_server_destroy(server); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /benchmark/tiny_http_static.c: -------------------------------------------------------------------------------- 1 | // gcc ../benchmark/tiny_http_static.c th.c -O3 -DTH_CONFIG_MAX_CONNECTIONS=1024 -o tiny_http_static 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static th_err 10 | handle_path(void* userp, const th_request* req, th_response* resp) 11 | { 12 | (void)userp; 13 | const char* path = th_try_get_path_param(req, "path"); 14 | return th_set_body_from_file(resp, "root", path); 15 | } 16 | 17 | static th_err 18 | handle_index(void* userp, const th_request* req, th_response* resp) 19 | { 20 | (void)userp; 21 | (void)req; 22 | return th_set_body_from_file(resp, "root", "index.html"); 23 | } 24 | 25 | int main() 26 | { 27 | th_err err = TH_ERR_OK; 28 | th_server* server = NULL; 29 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) 30 | goto cleanup; 31 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 32 | goto cleanup; 33 | if ((err = th_add_dir(server, "root", "testfiles")) != TH_ERR_OK) 34 | goto cleanup; 35 | if ((err = th_route(server, TH_METHOD_GET, "/{path:path}", handle_path, NULL)) != TH_ERR_OK) 36 | goto cleanup; 37 | if ((err = th_route(server, TH_METHOD_GET, "/", handle_index, NULL)) != TH_ERR_OK) 38 | goto cleanup; 39 | while (1) { 40 | th_poll(server, 1000); 41 | } 42 | fprintf(stderr, "Shutting down...\n"); 43 | cleanup: 44 | if (err != TH_ERR_OK) { 45 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 46 | return EXIT_FAILURE; 47 | } 48 | th_server_destroy(server); 49 | return EXIT_SUCCESS; 50 | } 51 | -------------------------------------------------------------------------------- /examples/custom_logger.c: -------------------------------------------------------------------------------- 1 | /** custom_logger example 2 | * @brief Demonstrates how to create a custom logger that writes log messages to a file. 3 | */ 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static sig_atomic_t stop = 0; 12 | 13 | static void 14 | sigint_handler(int signum) 15 | { 16 | stop = signum; 17 | } 18 | 19 | static th_err 20 | handler(void* userp, const th_request* req, th_response* resp) 21 | { 22 | (void)userp; 23 | (void)req; 24 | th_set_body(resp, "Hello, Loggers!"); 25 | th_add_header(resp, "Content-Type", "text/plain"); 26 | return TH_ERR_OK; 27 | } 28 | 29 | typedef struct my_logger { 30 | th_log log; 31 | FILE* file; 32 | } my_logger; 33 | 34 | static void 35 | my_log(void* self, int level, const char* msg) 36 | { 37 | (void)level; 38 | my_logger* logger = self; 39 | fprintf(logger->file, "%s\n", msg); 40 | } 41 | 42 | static bool 43 | my_logger_init(my_logger* log, const char* path) 44 | { 45 | log->log.print = my_log; 46 | log->file = fopen(path, "w"); 47 | return log->file != NULL; 48 | } 49 | 50 | static void 51 | my_logger_deinit(my_logger* log) 52 | { 53 | fclose(log->file); 54 | } 55 | 56 | int main(void) 57 | { 58 | signal(SIGINT, sigint_handler); 59 | 60 | // Initialize our custom logger 61 | my_logger logger = {0}; 62 | if (!my_logger_init(&logger, "custom_logger.log")) { 63 | fprintf(stderr, "Failed to open log file\n"); 64 | return EXIT_FAILURE; 65 | } 66 | th_log_set(&logger.log); 67 | 68 | // Startup the server as usual 69 | th_server* server = NULL; 70 | th_err err = TH_ERR_OK; 71 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) 72 | goto cleanup_logger; 73 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 74 | goto cleanup_server; 75 | if ((err = th_route(server, TH_METHOD_GET, "/", handler, NULL)) != TH_ERR_OK) 76 | goto cleanup_server; 77 | while (!stop) { 78 | th_poll(server, 1000); 79 | } 80 | fprintf(stderr, "Shutting down...\n"); 81 | cleanup_server: 82 | th_server_destroy(server); 83 | cleanup_logger: 84 | my_logger_deinit(&logger); 85 | if (err != TH_ERR_OK) { 86 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 87 | return EXIT_FAILURE; 88 | } 89 | return EXIT_SUCCESS; 90 | } 91 | -------------------------------------------------------------------------------- /examples/echo.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static sig_atomic_t stop = 0; 9 | 10 | static void 11 | sigint_handler(int signum) 12 | { 13 | stop = signum; 14 | } 15 | 16 | const char* method_strings[] = { 17 | "GET", 18 | "POST", 19 | "PUT", 20 | "DELETE", 21 | "PATCH", 22 | }; 23 | 24 | static th_err 25 | handler(void* userp, const th_request* req, th_response* resp) 26 | { 27 | (void)userp; 28 | size_t buf_len = 16 * 1024; 29 | char* buf = malloc(buf_len); 30 | size_t len = 0; 31 | len += (size_t)snprintf(buf, buf_len, "Method: %s\nPath: %s\nQuery: %s\nHeaders:\n", 32 | method_strings[th_get_method(req)], th_get_path(req), th_get_query(req)); 33 | for (th_iter it = th_header_iter(req); th_next(&it);) { 34 | len += (size_t)snprintf(buf + len, buf_len - len, " %s: %s\n", th_key(&it), th_cval(&it)); 35 | } 36 | for (th_iter it = th_cookie_iter(req); th_next(&it);) { 37 | len += (size_t)snprintf(buf + len, buf_len - len, " Cookie %s: %s\n", th_key(&it), th_cval(&it)); 38 | } 39 | for (th_iter it = th_queryvar_iter(req); th_next(&it);) { 40 | len += (size_t)snprintf(buf + len, buf_len - len, " Query var %s: %s\n", th_key(&it), th_cval(&it)); 41 | } 42 | for (th_iter it = th_formvar_iter(req); th_next(&it);) { 43 | len += (size_t)snprintf(buf + len, buf_len - len, " Form var %s: %s\n", th_key(&it), th_cval(&it)); 44 | } 45 | th_set_body(resp, buf); 46 | th_add_header(resp, "Content-Type", "text/plain; charset=utf-8"); 47 | free(buf); 48 | return TH_ERR_OK; 49 | } 50 | 51 | int main(void) 52 | { 53 | signal(SIGINT, sigint_handler); 54 | th_server* server = NULL; 55 | th_err err = TH_ERR_OK; 56 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) { 57 | fprintf(stderr, "Failed to create server: %s\n", th_strerror(err)); 58 | return EXIT_FAILURE; 59 | } 60 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 61 | goto cleanup; 62 | if ((err = th_route(server, TH_METHOD_ANY, "/{path:path}", handler, NULL)) != TH_ERR_OK) 63 | goto cleanup; 64 | while (!stop) { 65 | th_poll(server, 1000); 66 | } 67 | fprintf(stderr, "Shutting down...\n"); 68 | cleanup: 69 | th_server_destroy(server); 70 | if (err != TH_ERR_OK) { 71 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 72 | return EXIT_FAILURE; 73 | } 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /examples/file_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static sig_atomic_t stop = 0; 10 | 11 | static void 12 | sigint_handler(int signum) 13 | { 14 | stop = signum; 15 | } 16 | 17 | static th_err 18 | handle_path(void* userp, const th_request* req, th_response* resp) 19 | { 20 | (void)userp; 21 | return th_set_body_from_file(resp, "root", th_find_pathvar(req, "path")); 22 | } 23 | 24 | static th_err 25 | handle_index(void* userp, const th_request* req, th_response* resp) 26 | { 27 | (void)userp; 28 | (void)req; 29 | return th_set_body_from_file(resp, "root", "index.html"); 30 | } 31 | 32 | static void 33 | print_help(void) 34 | { 35 | fprintf(stdout, "Possible arguments:\n"); 36 | fprintf(stdout, "-r, --root Set the root directory\n"); 37 | fprintf(stdout, "-p, --port Set the port\n"); 38 | fprintf(stdout, "-k, --key Set the key file for SSL\n"); 39 | fprintf(stdout, "-c, --cert Set the cert file for SSL\n"); 40 | } 41 | 42 | int main(int argc, char** argv) 43 | { 44 | signal(SIGINT, sigint_handler); 45 | const char* root = NULL; 46 | const char* port = "8080"; 47 | const char* key = NULL; 48 | const char* cert = NULL; 49 | for (int i = 1; i < argc; i++) { 50 | if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 51 | print_help(); 52 | return EXIT_SUCCESS; 53 | } else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--root") == 0) { 54 | root = argv[i + 1]; 55 | ++i; 56 | } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) { 57 | port = argv[i + 1]; 58 | ++i; 59 | } else if (strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "--key") == 0) { 60 | key = argv[i + 1]; 61 | ++i; 62 | } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--cert") == 0) { 63 | cert = argv[i + 1]; 64 | ++i; 65 | } else { 66 | fprintf(stderr, "Error: Unknown argument %s\n", argv[i]); 67 | print_help(); 68 | return EXIT_FAILURE; 69 | } 70 | } 71 | if (root == NULL) { 72 | fprintf(stderr, "Error: Root directory not set\n"); 73 | print_help(); 74 | return EXIT_FAILURE; 75 | } 76 | th_server* server = NULL; 77 | th_err err = TH_ERR_OK; 78 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) { 79 | fprintf(stderr, "Failed to create server: %s\n", th_strerror(err)); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | th_bind_opt opt = {0}; 84 | opt.cert_file = cert; 85 | opt.key_file = key; 86 | if ((err = th_bind(server, "0.0.0.0", port, &opt)) != TH_ERR_OK) 87 | goto cleanup; 88 | if ((err = th_add_dir(server, "root", root)) != TH_ERR_OK) 89 | goto cleanup; 90 | if ((err = th_route(server, TH_METHOD_GET, "/{path:path}", handle_path, NULL)) != TH_ERR_OK) 91 | goto cleanup; 92 | if ((err = th_route(server, TH_METHOD_GET, "/", handle_index, NULL)) != TH_ERR_OK) 93 | goto cleanup; 94 | while (!stop) { 95 | th_poll(server, 1000); 96 | } 97 | fprintf(stderr, "Shutting down...\n"); 98 | cleanup: 99 | th_server_destroy(server); 100 | if (err != TH_ERR_OK) { 101 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 102 | return EXIT_FAILURE; 103 | } 104 | return EXIT_SUCCESS; 105 | } 106 | -------------------------------------------------------------------------------- /examples/file_upload.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static sig_atomic_t stop = 0; 9 | 10 | static void 11 | sigint_handler(int signum) 12 | { 13 | stop = signum; 14 | } 15 | 16 | static th_err 17 | handler(void* userp, const th_request* req, th_response* resp) 18 | { 19 | (void)userp; 20 | for (th_iter it = th_upload_iter(req); th_next(&it);) { 21 | const th_upload* upload = th_val(&it); 22 | th_upload_info info = th_upload_get_info(upload); 23 | th_err err = TH_ERR_OK; 24 | if ((err = th_upload_save(upload, "upload_dir", info.filename)) != TH_ERR_OK) { 25 | th_printf_body(resp, "Failed to save upload: %s, reason %s\n", info.filename, th_strerror(err)); 26 | th_add_header(resp, "Content-Type", "text/plain"); 27 | return TH_ERR_OK; 28 | } 29 | } 30 | th_set_body(resp, "Uploads saved successfully!\n"); 31 | th_add_header(resp, "Content-Type", "text/plain"); 32 | return TH_ERR_OK; 33 | } 34 | 35 | int main(void) 36 | { 37 | signal(SIGINT, sigint_handler); 38 | th_server* server = NULL; 39 | th_err err = TH_ERR_OK; 40 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) { 41 | fprintf(stderr, "Failed to create server: %s\n", th_strerror(err)); 42 | return EXIT_FAILURE; 43 | } 44 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 45 | goto cleanup; 46 | if ((err = th_add_dir(server, "upload_dir", "./uploads")) != TH_ERR_OK) 47 | goto cleanup; 48 | if ((err = th_route(server, TH_METHOD_POST, "/", handler, NULL)) != TH_ERR_OK) 49 | goto cleanup; 50 | while (!stop) { 51 | th_poll(server, 1000); 52 | } 53 | fprintf(stderr, "Shutting down...\n"); 54 | cleanup: 55 | th_server_destroy(server); 56 | if (err != TH_ERR_OK) { 57 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 58 | return EXIT_FAILURE; 59 | } 60 | return EXIT_SUCCESS; 61 | } 62 | -------------------------------------------------------------------------------- /examples/hello_world.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static sig_atomic_t stop = 0; 9 | 10 | static void 11 | sigint_handler(int signum) 12 | { 13 | stop = signum; 14 | } 15 | 16 | static th_err 17 | handler(void* userp, const th_request* req, th_response* resp) 18 | { 19 | (void)userp; 20 | (void)req; 21 | th_set_body(resp, "Hello, World!"); 22 | th_add_header(resp, "Content-Type", "text/plain"); 23 | return TH_ERR_OK; 24 | } 25 | 26 | 27 | int main(void) 28 | { 29 | signal(SIGINT, sigint_handler); 30 | th_server* server = NULL; 31 | th_err err = TH_ERR_OK; 32 | if ((err = th_server_create(&server, NULL)) != TH_ERR_OK) { 33 | fprintf(stderr, "Failed to create server: %s\n", th_strerror(err)); 34 | return EXIT_FAILURE; 35 | } 36 | if ((err = th_bind(server, "0.0.0.0", "8080", NULL)) != TH_ERR_OK) 37 | goto cleanup; 38 | if ((err = th_route(server, TH_METHOD_GET, "/", handler, NULL)) != TH_ERR_OK) 39 | goto cleanup; 40 | while (!stop) { 41 | th_poll(server, 1000); 42 | } 43 | fprintf(stderr, "Shutting down...\n"); 44 | cleanup: 45 | th_server_destroy(server); 46 | if (err != TH_ERR_OK) { 47 | fprintf(stderr, "Error: %s\n", th_strerror(err)); 48 | return EXIT_FAILURE; 49 | } 50 | return EXIT_SUCCESS; 51 | } 52 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx == 2.5.1 2 | -------------------------------------------------------------------------------- /src/th_acceptor.c: -------------------------------------------------------------------------------- 1 | #include "th_acceptor.h" 2 | 3 | #include "th_config.h" 4 | 5 | #if defined(TH_CONFIG_OS_POSIX) 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #elif defined(TH_CONFIG_OS_WIN) 15 | #include 16 | #include 17 | #elif defined(TH_CONFIG_OS_MOCK) 18 | #include "th_mock_syscall.h" 19 | #endif 20 | 21 | TH_PRIVATE(th_err) 22 | th_acceptor_init(th_acceptor* acceptor, th_context* context, 23 | th_allocator* allocator, 24 | const char* addr, const char* port) 25 | { 26 | acceptor->handle = NULL; 27 | acceptor->context = context; 28 | acceptor->allocator = allocator; 29 | #if defined(TH_CONFIG_OS_POSIX) 30 | th_err err = TH_ERR_OK; 31 | struct addrinfo hints = {0}; 32 | hints.ai_family = AF_UNSPEC; 33 | hints.ai_socktype = SOCK_STREAM; 34 | hints.ai_flags = AI_PASSIVE; 35 | struct addrinfo* res = NULL; 36 | if (getaddrinfo(addr, port, &hints, &res) != 0) { 37 | return TH_ERR_SYSTEM(errno); 38 | } 39 | int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 40 | if (fd < 0) { 41 | err = TH_ERR_SYSTEM(errno); 42 | goto cleanup_addrinfo; 43 | } 44 | #if TH_CONFIG_REUSE_ADDR 45 | { 46 | int optval = 1; 47 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { 48 | err = TH_ERR_SYSTEM(errno); 49 | goto cleanup_fd; 50 | } 51 | } 52 | #endif 53 | #if TH_CONFIG_REUSE_PORT 54 | { 55 | #if defined(SO_REUSEPORT) 56 | int optval = 1; 57 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0) { 58 | err = TH_ERR_SYSTEM(errno); 59 | goto cleanup_fd; 60 | } 61 | #else 62 | TH_LOG_FATAL("SO_REUSEPORT is not supported on this platform"); 63 | err = TH_ERR_NOSUPPORT; 64 | goto cleanup_fd; 65 | #endif 66 | } 67 | #endif 68 | // Set the socket to non-blocking mode 69 | if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) { 70 | err = TH_ERR_SYSTEM(errno); 71 | goto cleanup_fd; 72 | } 73 | if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) { 74 | err = TH_ERR_SYSTEM(errno); 75 | goto cleanup_fd; 76 | } 77 | if (listen(fd, 1024) < 0) { 78 | err = TH_ERR_SYSTEM(errno); 79 | goto cleanup_fd; 80 | } 81 | if ((err = th_context_create_handle(context, &acceptor->handle, fd)) != TH_ERR_OK) 82 | goto cleanup_fd; 83 | freeaddrinfo(res); 84 | return TH_ERR_OK; 85 | cleanup_fd: 86 | close(fd); 87 | cleanup_addrinfo: 88 | freeaddrinfo(res); 89 | return err; 90 | #elif defined(TH_CONFIG_OS_WIN) 91 | th_err err = TH_ERR_OK; 92 | const ADDRINFOA hints = {0}; 93 | hints.ai_family = AF_UNSPEC; 94 | hints.ai_socktype = SOCK_STREAM; 95 | hints.ai_flags = AI_PASSIVE; 96 | ADDRINFOA* res = NULL; 97 | if (getaddrinfo(addr, port, &hints, &res) != 0) { 98 | return TH_ERR_SYSTEM(WSAGetLastError()); 99 | } 100 | int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 101 | if (fd == INVALID_SOCKET) { 102 | err = TH_ERR_SYSTEM(WSAGetLastError()); 103 | goto cleanup_addrinfo; 104 | } 105 | #if TH_CONFIG_REUSE_ADDR 106 | { 107 | int optval = 1; 108 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval)) == SOCKET_ERROR) { 109 | err = TH_ERR_SYSTEM(WSAGetLastError()); 110 | goto cleanup_fd; 111 | } 112 | } 113 | #endif 114 | #if TH_CONFIG_REUSE_PORT 115 | { 116 | TH_LOG_FATAL("SO_REUSEPORT is not supported on this platform"); 117 | err = TH_ERR_NOSUPPORT; 118 | goto cleanup_fd; 119 | } 120 | #endif 121 | // Set the socket to non-blocking mode 122 | u_long mode = 1; 123 | if (ioctlsocket(fd, FIONBIO, &mode) == SOCKET_ERROR) { 124 | err = TH_ERR_SYSTEM(WSAGetLastError()); 125 | goto cleanup_fd; 126 | } 127 | if (bind(fd, res->ai_addr, (int)res->ai_addrlen) == SOCKET_ERROR) { 128 | err = TH_ERR_SYSTEM(WSAGetLastError()); 129 | goto cleanup_fd; 130 | } 131 | if (listen(fd, 1024) == SOCKET_ERROR) { 132 | err = TH_ERR_SYSTEM(WSAGetLastError()); 133 | goto cleanup_fd; 134 | } 135 | if ((err = th_context_create_handle(context, &acceptor->handle, fd)) != TH_ERR_OK) 136 | goto cleanup_fd; 137 | freeaddrinfo(res); 138 | return TH_ERR_OK; 139 | cleanup_fd: 140 | closesocket(fd); 141 | cleanup_addrinfo: 142 | freeaddrinfo(res); 143 | return err; 144 | #elif defined(TH_CONFIG_OS_MOCK) 145 | (void)addr; 146 | (void)port; 147 | int fd = th_mock_open(); 148 | if (fd < 0) 149 | return TH_ERR_SYSTEM(-fd); 150 | th_err err = TH_ERR_OK; 151 | if ((err = th_context_create_handle(context, &acceptor->handle, fd)) != TH_ERR_OK) 152 | th_mock_close(); 153 | return err; 154 | #endif 155 | } 156 | 157 | TH_PRIVATE(void) 158 | th_acceptor_async_accept(th_acceptor* acceptor, th_address* addr, th_io_handler* on_complete) 159 | { 160 | th_address_init(addr); 161 | th_io_task* iot = th_io_task_create(acceptor->allocator); 162 | if (!iot) { 163 | th_context_dispatch_handler(acceptor->context, on_complete, 0, TH_ERR_BAD_ALLOC); 164 | return; 165 | } 166 | th_io_task_prepare_accept(iot, th_io_handle_get_fd(acceptor->handle), &addr->addr, &addr->addrlen, on_complete); 167 | th_io_handle_submit(acceptor->handle, iot); 168 | } 169 | 170 | TH_PRIVATE(void) 171 | th_acceptor_cancel(th_acceptor* acceptor) 172 | { 173 | th_io_handle_cancel(acceptor->handle); 174 | } 175 | 176 | TH_PRIVATE(void) 177 | th_acceptor_deinit(th_acceptor* acceptor) 178 | { 179 | th_io_handle_destroy(acceptor->handle); 180 | } 181 | 182 | /* th_acceptor functions end */ 183 | -------------------------------------------------------------------------------- /src/th_acceptor.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_ACCEPTOR_H 2 | #define TH_ACCEPTOR_H 3 | 4 | #include 5 | 6 | #include "th_allocator.h" 7 | #include "th_config.h" 8 | #include "th_context.h" 9 | #include "th_socket.h" 10 | 11 | typedef struct th_acceptor { 12 | th_context* context; 13 | th_allocator* allocator; 14 | th_io_handle* handle; 15 | } th_acceptor; 16 | 17 | typedef struct th_acceptor_opt { 18 | bool reuse_addr; 19 | bool reuse_port; 20 | } th_acceptor_opt; 21 | 22 | TH_PRIVATE(th_err) 23 | th_acceptor_init(th_acceptor* acceptor, th_context* context, 24 | th_allocator* allocator, 25 | const char* addr, const char* port); 26 | 27 | /** th_acceptor_async_accept 28 | * @brief Asynchronously accept a new connection. And call the handler when the operation is complete. 29 | * Both addr and sock must point to valid memory locations until the handler is called. 30 | * @param acceptor The acceptor that will accept the new connection. 31 | * @param addr Pointer to the address that will be filled with the address of the new connection. 32 | * @param sock Pointer to the socket that will be filled with the new connection. 33 | */ 34 | TH_PRIVATE(void) 35 | th_acceptor_async_accept(th_acceptor* acceptor, th_address* addr, th_io_handler* handler); 36 | 37 | TH_PRIVATE(void) 38 | th_acceptor_cancel(th_acceptor* acceptor); 39 | 40 | TH_PRIVATE(void) 41 | th_acceptor_deinit(th_acceptor* acceptor); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/th_align.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_ALIGN_H 2 | #define TH_ALIGN_H 3 | 4 | #include 5 | 6 | #define TH_ALIGNOF(type) offsetof(struct { char c; type member; }, member) 7 | #define TH_ALIGNAS(align, ptr) ((void*)(((uintptr_t)(ptr) + ((align) - 1)) & ~((align) - 1))) 8 | #define TH_ALIGNUP(n, align) (((n) + (align) - 1) & ~((align) - 1)) 9 | #define TH_ALIGNDOWN(n, align) ((n) & ~((align) - 1)) 10 | 11 | typedef long double th_max_align; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/th_config.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_CONFIG_H 2 | #define TH_CONFIG_H 3 | 4 | #if defined(__GNUC__) 5 | // Buggy warning in GCC 6 | // TODO: Check the exact versions where these warnings are fixed 7 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 8 | #pragma GCC diagnostic ignored "-Wmissing-braces" 9 | #endif 10 | 11 | /* feature configuration begin */ 12 | 13 | #ifndef TH_WITH_SSL 14 | #define TH_WITH_SSL 0 15 | #endif 16 | 17 | #ifndef TH_WITH_SENDFILE 18 | #define TH_WITH_SENDFILE 1 19 | #endif 20 | 21 | #ifndef TH_WITH_MMAP 22 | #define TH_WITH_MMAP 1 23 | #endif 24 | 25 | #ifndef TH_LOG_LEVEL 26 | #define TH_LOG_LEVEL TH_LOG_LEVEL_INFO 27 | #endif 28 | 29 | #ifndef TH_MAX_BODY_LEN 30 | #define TH_MAX_BODY_LEN (4 * 1024 * 1024) 31 | #endif 32 | 33 | /* feature configuration end */ 34 | 35 | #ifndef TH_CONFIG_OS_MOCK 36 | #if defined(__APPLE__) 37 | #define TH_CONFIG_OS_OSX 1 38 | #define TH_CONFIG_OS_POSIX 1 39 | #define TH_CONFIG_OS_BSD 1 40 | #endif 41 | 42 | #if defined(__FreeBSD__) 43 | #define TH_CONFIG_OS_FreeBSD 1 44 | #define TH_CONFIG_OS_POSIX 1 45 | #define TH_CONFIG_OS_BSD 1 46 | #endif 47 | 48 | #if defined(__NetBSD__) 49 | #define TH_CONFIG_OS_NetBSD 1 50 | #define TH_CONFIG_OS_POSIX 1 51 | #define TH_CONFIG_OS_BSD 1 52 | #endif 53 | 54 | #if defined(__OpenBSD__) 55 | #define TH_CONFIG_OS_OpenBSD 1 56 | #define TH_CONFIG_OS_POSIX 1 57 | #define TH_CONFIG_OS_BSD 1 58 | #endif 59 | 60 | #if defined(__linux__) 61 | #define TH_CONFIG_OS_LINUX 1 62 | #define TH_CONFIG_OS_POSIX 1 63 | #endif 64 | 65 | #if defined(_WIN32) 66 | #define TH_CONFIG_OS_WIN 1 67 | #endif 68 | #endif 69 | 70 | /* IO service config begin */ 71 | 72 | #if defined(TH_CONFIG_OS_POSIX) 73 | #define TH_CONFIG_WITH_POLL 1 74 | #endif 75 | 76 | #if defined(TH_CONFIG_OS_OSX) 77 | #define TH_CONFIG_WITH_KQUEUE 1 78 | #endif 79 | 80 | #if defined(TH_CONFIG_OS_FreeBSD) 81 | #define TH_CONFIG_WITH_KQUEUE 1 82 | #endif 83 | 84 | /* IO service config end */ 85 | /* sendfile config begin */ 86 | 87 | #if TH_WITH_SENDFILE 88 | #if defined(TH_CONFIG_OS_LINUX) 89 | // #define TH_CONFIG_WITH_LINUX_SENDFILE 1 90 | #elif defined(TH_CONFIG_OS_OSX) || defined(TH_CONFIG_OS_FreeBSD) || defined(TH_CONFIG_OS_NetBSD) || defined(TH_CONFIG_OS_OpenBSD) 91 | #define TH_CONFIG_WITH_BSD_SENDFILE 1 92 | #endif 93 | #endif 94 | 95 | /* Unused attribute is platform dependent */ 96 | 97 | #define TH_MAYBE_UNUSED __attribute__((unused)) 98 | 99 | /* Helper macros for defining public and private functions */ 100 | 101 | #define TH_PUBLIC(type) type 102 | 103 | #ifdef TH_WITH_AMALGAMATION 104 | #define TH_PRIVATE(type) static type 105 | #else 106 | #define TH_PRIVATE(type) type 107 | #endif 108 | 109 | #define TH_LOCAL(type) static type 110 | 111 | #define TH_INLINE(type) static inline type 112 | 113 | /* Server related config begin */ 114 | 115 | #ifndef TH_CONFIG_MAX_HANDLES 116 | #define TH_CONFIG_MAX_HANDLES (8 * 1024) 117 | #endif 118 | 119 | #ifndef TH_CONFIG_MAX_CONNECTIONS 120 | #define TH_CONFIG_MAX_CONNECTIONS 512 121 | #endif 122 | 123 | #ifndef TH_CONFIG_SMALL_HEADER_LEN 124 | #define TH_CONFIG_SMALL_HEADER_LEN 1024 125 | #endif 126 | 127 | #ifndef TH_CONFIG_LARGE_HEADER_LEN 128 | #define TH_CONFIG_LARGE_HEADER_LEN (4 * 1024) 129 | #endif 130 | 131 | #ifndef TH_CONFIG_MAX_CONTENT_LEN 132 | #define TH_CONFIG_MAX_CONTENT_LEN (1024 * 1024) 133 | #endif 134 | 135 | #ifndef TH_CONFIG_MAX_HEADER_NUM 136 | #define TH_CONFIG_MAX_HEADER_NUM 64 137 | #endif 138 | 139 | #ifndef TH_CONFIG_MAX_PATH_LEN 140 | #define TH_CONFIG_MAX_PATH_LEN 512 141 | #endif 142 | 143 | #ifndef TH_CONFIG_MAX_CACHED_FDS 144 | #define TH_CONFIG_MAX_CACHED_FDS 64 145 | #endif 146 | 147 | #ifndef TH_CONFIG_SENDFILE_CHUNK_LEN 148 | #define TH_CONFIG_SENDFILE_CHUNK_LEN (4 * 1024 * 1024) 149 | #endif 150 | 151 | #ifndef TH_CONFIG_LARGE_BUF_LEN 152 | #define TH_CONFIG_LARGE_BUF_LEN (4 * 1024) 153 | #endif 154 | 155 | #ifndef TH_CONFIG_SMALL_SSL_BUF_LEN 156 | #define TH_CONFIG_SMALL_SSL_BUF_LEN (2 * 1024) 157 | #endif 158 | 159 | #ifndef TH_CONFIG_LARGE_SSL_BUF_LEN 160 | #define TH_CONFIG_LARGE_SSL_BUF_LEN (8 * 1024) 161 | #endif 162 | 163 | #ifndef TH_CONFIG_MAX_SSL_READ_BUF_LEN 164 | #define TH_CONFIG_MAX_SSL_READ_BUF_LEN (1024 * 1024) 165 | #endif 166 | 167 | #ifndef TH_CONFIG_MAX_SSL_WRITE_BUF_LEN 168 | #define TH_CONFIG_MAX_SSL_WRITE_BUF_LEN (4 * 1024 * 1024) 169 | #endif 170 | 171 | /* Socket related configuration */ 172 | 173 | #ifndef TH_CONFIG_REUSE_ADDR 174 | #define TH_CONFIG_REUSE_ADDR 1 175 | #endif 176 | 177 | #ifndef TH_CONFIG_REUSE_PORT 178 | #define TH_CONFIG_REUSE_PORT 0 179 | #endif 180 | 181 | #ifndef TH_CONFIG_TCP_NODELAY 182 | #define TH_CONFIG_TCP_NODELAY 0 183 | #endif 184 | 185 | // Socket timeout in seconds 186 | #ifndef TH_CONFIG_IO_TIMEOUT 187 | #define TH_CONFIG_IO_TIMEOUT 10 188 | #endif 189 | 190 | /* Server related config end */ 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /src/th_conn.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_CONN_H 2 | #define TH_CONN_H 3 | 4 | #include 5 | 6 | #include "th_allocator.h" 7 | #include "th_config.h" 8 | #include "th_request.h" 9 | #include "th_response.h" 10 | #include "th_router.h" 11 | #include "th_ssl_socket.h" 12 | #include "th_tcp_socket.h" 13 | 14 | /* th_prot interface begin */ 15 | 16 | /* th_prot interface end */ 17 | /* th_conn interface begin */ 18 | typedef struct th_conn th_conn; 19 | struct th_conn { 20 | th_socket* (*get_socket)(void* self); 21 | th_address* (*get_address)(void* self); 22 | void (*start)(void* self); 23 | void (*destroy)(void* self); 24 | }; 25 | 26 | /** th_conn_init 27 | * @brief Initialize the client interface, this function should be called 28 | * by the parent client implementation on initialization. 29 | */ 30 | TH_INLINE(void) 31 | th_conn_init(th_conn* client, 32 | th_socket* (*get_socket)(void* self), 33 | th_address* (*get_address)(void* self), 34 | void (*start)(void* self), 35 | void (*destroy)(void* self)) 36 | { 37 | client->get_socket = get_socket; 38 | client->get_address = get_address; 39 | client->start = start; 40 | client->destroy = destroy; 41 | } 42 | 43 | TH_INLINE(th_socket*) 44 | th_conn_get_socket(th_conn* client) 45 | { 46 | return client->get_socket(client); 47 | } 48 | 49 | TH_INLINE(th_address*) 50 | th_conn_get_address(th_conn* client) 51 | { 52 | return client->get_address(client); 53 | } 54 | 55 | TH_INLINE(void) 56 | th_conn_start(th_conn* client) 57 | { 58 | client->start(client); 59 | } 60 | 61 | TH_INLINE(void) 62 | th_conn_destroy(th_conn* client) 63 | { 64 | client->destroy(client); 65 | } 66 | 67 | /* th_conn interface end */ 68 | /* th_conn_upgrader interface begin */ 69 | 70 | /** th_conn_upgrader 71 | * @brief Implement this interface and pass it to `th_conn` to define 72 | * how a connection should be upgraded to a higher level protocol. 73 | */ 74 | typedef struct th_conn_upgrader { 75 | void (*upgrade)(void* self, th_conn* conn); 76 | } th_conn_upgrader; 77 | 78 | TH_INLINE(void) 79 | th_conn_upgrader_init(th_conn_upgrader* upgrader, void (*upgrade)(void* self, th_conn* conn)) 80 | { 81 | upgrader->upgrade = upgrade; 82 | } 83 | 84 | TH_INLINE(void) 85 | th_conn_upgrader_upgrade(th_conn_upgrader* upgrader, th_conn* conn) 86 | { 87 | upgrader->upgrade(upgrader, conn); 88 | } 89 | 90 | /* th_conn_upgrader interface end */ 91 | /* th_conn_observable interface begin */ 92 | 93 | /** th_conn_observer 94 | * @brief Implement this interface to observe when a client is 95 | * initialized and destroyed. 96 | */ 97 | typedef struct th_conn_observable th_conn_observable; 98 | 99 | typedef struct th_conn_observer th_conn_observer; 100 | struct th_conn_observer { 101 | void (*on_init)(th_conn_observer* self, th_conn_observable* observable); 102 | void (*on_deinit)(th_conn_observer* self, th_conn_observable* observable); 103 | }; 104 | 105 | TH_INLINE(void) 106 | th_conn_observer_on_init(th_conn_observer* observer, th_conn_observable* observable) 107 | { 108 | observer->on_init(observer, observable); 109 | } 110 | 111 | TH_INLINE(void) 112 | th_conn_observer_on_deinit(th_conn_observer* observer, th_conn_observable* observable) 113 | { 114 | observer->on_deinit(observer, observable); 115 | } 116 | 117 | struct th_conn_observable { 118 | th_conn base; 119 | void (*destroy)(void* self); 120 | th_conn_observer* observer; 121 | th_conn_observable *next, *prev; 122 | }; 123 | 124 | /* th_conn_observable interface end */ 125 | /* th_tcp_conn declaration begin */ 126 | 127 | typedef struct th_tcp_conn th_tcp_conn; 128 | 129 | struct th_tcp_conn { 130 | th_conn_observable base; 131 | th_tcp_socket socket; 132 | th_address addr; 133 | th_context* context; 134 | th_conn_upgrader* upgrader; 135 | th_allocator* allocator; 136 | }; 137 | 138 | TH_PRIVATE(th_err) 139 | th_tcp_conn_create(th_conn** out, th_context* context, 140 | th_conn_upgrader* upgrader, th_conn_observer* observer, 141 | th_allocator* allocator); 142 | 143 | /* th_tcp_conn declaration end */ 144 | /* th_ssl_conn declaration begin */ 145 | #if TH_WITH_SSL 146 | typedef struct th_ssl_conn th_ssl_conn; 147 | typedef struct th_ssl_conn_io_handler { 148 | th_io_handler base; 149 | th_ssl_conn* conn; 150 | } th_ssl_conn_io_handler; 151 | 152 | struct th_ssl_conn { 153 | th_conn_observable base; 154 | th_ssl_conn_io_handler handshake_handler; 155 | th_ssl_conn_io_handler shutdown_handler; 156 | th_ssl_socket socket; 157 | th_address addr; 158 | th_context* context; 159 | th_conn_upgrader* upgrader; 160 | th_allocator* allocator; 161 | }; 162 | 163 | TH_PRIVATE(th_err) 164 | th_ssl_conn_create(th_conn** out, th_context* context, th_ssl_context* ssl_context, 165 | th_conn_upgrader* upgrader, th_conn_observer* observer, 166 | th_allocator* allocator); 167 | #endif 168 | #endif 169 | -------------------------------------------------------------------------------- /src/th_conn_tracker.c: -------------------------------------------------------------------------------- 1 | #include "th_conn_tracker.h" 2 | #include "th_align.h" 3 | #include "th_conn.h" 4 | 5 | TH_LOCAL(void) 6 | th_conn_tracker_on_conn_init(th_conn_observer* observer, th_conn_observable* observable) 7 | { 8 | th_conn_tracker* tracker = (th_conn_tracker*)observer; 9 | th_conn_observable_list_push_back(&tracker->observables, observable); 10 | ++tracker->count; 11 | } 12 | 13 | TH_LOCAL(void) 14 | th_conn_tracker_on_conn_deinit(th_conn_observer* observer, th_conn_observable* observable) 15 | { 16 | th_conn_tracker* tracker = (th_conn_tracker*)observer; 17 | th_conn_observable_list_erase(&tracker->observables, observable); 18 | --tracker->count; 19 | if (tracker->task) { 20 | th_task* task = TH_MOVE_PTR(tracker->task); 21 | th_task_complete(task); 22 | th_task_destroy(task); 23 | } 24 | } 25 | 26 | TH_PRIVATE(void) 27 | th_conn_tracker_init(th_conn_tracker* tracker) 28 | { 29 | tracker->base.on_init = th_conn_tracker_on_conn_init; 30 | tracker->base.on_deinit = th_conn_tracker_on_conn_deinit; 31 | tracker->observables = (th_conn_observable_list){0}; 32 | tracker->task = NULL; 33 | tracker->count = 0; 34 | } 35 | 36 | TH_PRIVATE(void) 37 | th_conn_tracker_cancel_all(th_conn_tracker* conn_tracker) 38 | { 39 | th_conn_observable* observable = NULL; 40 | for (observable = th_conn_observable_list_front(&conn_tracker->observables); 41 | observable != NULL; 42 | observable = th_conn_observable_list_next(observable)) { 43 | th_conn* client = &observable->base; 44 | th_socket_cancel(th_conn_get_socket(client)); 45 | } 46 | } 47 | 48 | TH_PRIVATE(void) 49 | th_conn_tracker_async_wait(th_conn_tracker* conn_tracker, th_task* task) 50 | { 51 | TH_ASSERT(conn_tracker->task == NULL && "Task already set"); 52 | TH_ASSERT(th_conn_observable_list_front(&conn_tracker->observables) != NULL && "No clients to wait for"); 53 | conn_tracker->task = task; 54 | } 55 | 56 | TH_PRIVATE(size_t) 57 | th_conn_tracker_count(const th_conn_tracker* conn_tracker) 58 | { 59 | return conn_tracker->count; 60 | } 61 | 62 | TH_PRIVATE(void) 63 | th_conn_tracker_deinit(th_conn_tracker* tracker) 64 | { 65 | (void)tracker; 66 | TH_ASSERT(th_conn_observable_list_front(&tracker->observables) == NULL && "All clients must be destroyed before deinit"); 67 | } 68 | -------------------------------------------------------------------------------- /src/th_conn_tracker.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_CONN_TRACKER_H 2 | #define TH_CONN_TRACKER_H 3 | 4 | /** th_conn_tracker 5 | * @brief The client tracker keep track of all clients that are currently active. 6 | * It is used to cancel all clients when the server is shutting down. 7 | */ 8 | 9 | #include "th_allocator.h" 10 | #include "th_conn.h" 11 | #include "th_list.h" 12 | #include "th_task.h" 13 | 14 | TH_DEFINE_LIST(th_conn_observable_list, th_conn_observable, prev, next) 15 | 16 | typedef struct th_conn_tracker { 17 | th_conn_observer base; 18 | th_conn_observable_list observables; 19 | th_task* task; 20 | size_t count; 21 | } th_conn_tracker; 22 | 23 | TH_PRIVATE(void) 24 | th_conn_tracker_init(th_conn_tracker* conn_tracker); 25 | 26 | TH_PRIVATE(void) 27 | th_conn_tracker_cancel_all(th_conn_tracker* conn_tracker); 28 | 29 | TH_PRIVATE(void) 30 | th_conn_tracker_async_wait(th_conn_tracker* conn_tracker, th_task* task); 31 | 32 | TH_PRIVATE(size_t) 33 | th_conn_tracker_count(const th_conn_tracker* conn_tracker); 34 | 35 | TH_PRIVATE(void) 36 | th_conn_tracker_deinit(th_conn_tracker* conn_tracker); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/th_context.c: -------------------------------------------------------------------------------- 1 | #include "th_context.h" 2 | #include "th_io_service.h" 3 | #include "th_kqueue_service.h" 4 | #include "th_log.h" 5 | #include "th_mock_service.h" 6 | #include "th_poll_service.h" 7 | 8 | #undef TH_LOG_TAG 9 | #define TH_LOG_TAG "context" 10 | 11 | TH_LOCAL(th_err) 12 | th_io_service_create(th_io_service** out, th_runner* runner, th_allocator* allocator) 13 | { 14 | allocator = allocator ? allocator : th_default_allocator_get(); 15 | (void)out; 16 | #if defined(TH_CONFIG_OS_MOCK) 17 | (void)allocator; 18 | TH_LOG_TRACE("Using mock"); 19 | return th_mock_service_create(out, runner); 20 | #endif 21 | #if defined(TH_CONFIG_WITH_KQUEUE) 22 | TH_LOG_TRACE("Using kqueue"); 23 | return th_kqueue_service_create(out, runner, allocator); 24 | #endif 25 | #if defined(TH_CONFIG_WITH_POLL) 26 | TH_LOG_TRACE("Using poll"); 27 | return th_poll_service_create(out, runner, allocator); 28 | #endif 29 | TH_LOG_ERROR("No IO service implementation available"); 30 | return TH_ERR_NOSUPPORT; 31 | } 32 | 33 | TH_PRIVATE(th_err) 34 | th_context_init(th_context* context, th_allocator* allocator) 35 | { 36 | th_err err = TH_ERR_OK; 37 | context->allocator = allocator ? allocator : th_default_allocator_get(); 38 | th_runner_init(&context->runner); 39 | if ((th_io_service_create(&context->io_service, &context->runner, context->allocator)) != TH_ERR_OK) { 40 | return err; 41 | } 42 | th_runner_set_io_service(&context->runner, context->io_service); 43 | return TH_ERR_OK; 44 | } 45 | 46 | TH_PRIVATE(th_err) 47 | th_context_init_with_service(th_context* context, th_io_service* service) 48 | { 49 | context->io_service = service; 50 | th_runner_init(&context->runner); 51 | th_runner_set_io_service(&context->runner, context->io_service); 52 | return TH_ERR_OK; 53 | } 54 | 55 | TH_PRIVATE(void) 56 | th_context_push_task(th_context* context, th_task* task) 57 | { 58 | th_runner_push_task(&context->runner, task); 59 | } 60 | 61 | TH_PRIVATE(th_err) 62 | th_context_create_handle(th_context* context, th_io_handle** out, int fd) 63 | { 64 | return th_io_service_create_handle(context->io_service, out, fd); 65 | } 66 | 67 | TH_PRIVATE(th_err) 68 | th_context_poll(th_context* context, int timeout_ms) 69 | { 70 | return th_runner_poll(&context->runner, timeout_ms); 71 | } 72 | 73 | TH_PRIVATE(void) 74 | th_context_drain(th_context* context) 75 | { 76 | th_runner_drain(&context->runner); 77 | } 78 | 79 | TH_PRIVATE(void) 80 | th_context_deinit(th_context* context) 81 | { 82 | th_runner_deinit(&context->runner); 83 | th_io_service_destroy(context->io_service); 84 | } 85 | 86 | TH_PRIVATE(void) 87 | th_context_dispatch_handler(th_context* context, th_io_handler* handler, size_t result, th_err err) 88 | { 89 | th_io_handler_set_result(handler, result, err); 90 | th_context_push_task(context, &handler->base); 91 | } 92 | 93 | TH_PRIVATE(void) 94 | th_context_dispatch_composite_completion(th_context* context, th_io_composite* composite, size_t result, th_err err) 95 | { 96 | th_context_dispatch_handler(context, TH_MOVE_PTR(composite->on_complete), result, err); 97 | } 98 | -------------------------------------------------------------------------------- /src/th_context.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_CONTEXT_H 2 | #define TH_CONTEXT_H 3 | 4 | #include "th_allocator.h" 5 | #include "th_config.h" 6 | #include "th_io_composite.h" 7 | #include "th_io_service.h" 8 | #include "th_runner.h" 9 | 10 | typedef struct th_context { 11 | th_runner runner; 12 | th_allocator* allocator; 13 | th_io_service* io_service; 14 | } th_context; 15 | 16 | TH_PRIVATE(th_err) 17 | th_context_init(th_context* context, th_allocator* allocator); 18 | 19 | TH_PRIVATE(th_err) 20 | th_context_init_with_service(th_context* context, th_io_service* service) TH_MAYBE_UNUSED; 21 | 22 | TH_PRIVATE(void) 23 | th_context_push_task(th_context* context, th_task* task) TH_MAYBE_UNUSED; 24 | 25 | TH_PRIVATE(th_err) 26 | th_context_create_handle(th_context* context, th_io_handle** out, int fd); 27 | 28 | TH_PRIVATE(th_err) 29 | th_context_poll(th_context* context, int timeout_ms); 30 | 31 | TH_PRIVATE(void) 32 | th_context_drain(th_context* context); 33 | 34 | TH_PRIVATE(void) 35 | th_context_deinit(th_context* context); 36 | 37 | TH_PRIVATE(void) 38 | th_context_dispatch_handler(th_context* context, th_io_handler* handler, size_t result, th_err err); 39 | 40 | TH_PRIVATE(void) 41 | th_context_dispatch_composite_completion(th_context* context, th_io_composite* composite, size_t result, th_err err) TH_MAYBE_UNUSED; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/th_date.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "th_config.h" 6 | #include "th_string.h" 7 | 8 | TH_PUBLIC(th_duration) 9 | th_seconds(int seconds) 10 | { 11 | return (th_duration){.seconds = seconds}; 12 | } 13 | 14 | TH_PUBLIC(th_duration) 15 | th_minutes(int minutes) 16 | { 17 | return th_seconds(minutes * 60); 18 | } 19 | 20 | TH_PUBLIC(th_duration) 21 | th_hours(int hours) 22 | { 23 | return th_minutes(hours * 60); 24 | } 25 | 26 | TH_PUBLIC(th_duration) 27 | th_days(int days) 28 | { 29 | return th_hours(days * 24); 30 | } 31 | 32 | TH_PUBLIC(th_date) 33 | th_date_now(void) 34 | { 35 | time_t t = time(NULL); 36 | struct tm tm = {0}; 37 | gmtime_r(&t, &tm); 38 | th_date date = {0}; 39 | date.year = tm.tm_year; 40 | date.month = tm.tm_mon; 41 | date.day = tm.tm_mday; 42 | date.weekday = tm.tm_wday; 43 | date.hour = tm.tm_hour; 44 | date.minute = tm.tm_min; 45 | date.second = tm.tm_sec; 46 | return date; 47 | } 48 | 49 | TH_PUBLIC(th_date) 50 | th_date_add(th_date date, th_duration d) 51 | { 52 | struct tm tm = {0}; 53 | tm.tm_year = date.year; 54 | tm.tm_mon = date.month; 55 | tm.tm_mday = date.day; 56 | tm.tm_hour = date.hour; 57 | tm.tm_min = date.minute; 58 | tm.tm_sec = date.second; 59 | time_t t = mktime(&tm); 60 | t += d.seconds; 61 | gmtime_r(&t, &tm); 62 | th_date new_date = {0}; 63 | new_date.year = (unsigned int)tm.tm_year; 64 | new_date.month = (unsigned int)tm.tm_mon; 65 | new_date.day = (unsigned int)tm.tm_mday; 66 | new_date.weekday = (unsigned int)tm.tm_wday; 67 | new_date.hour = (unsigned int)tm.tm_hour; 68 | new_date.minute = (unsigned int)tm.tm_min; 69 | new_date.second = (unsigned int)tm.tm_sec; 70 | return new_date; 71 | } 72 | -------------------------------------------------------------------------------- /src/th_dir.c: -------------------------------------------------------------------------------- 1 | #include "th_dir.h" 2 | #include "th_config.h" 3 | #include "th_utility.h" 4 | #include "th_path.h" 5 | 6 | #if defined(TH_CONFIG_OS_POSIX) 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #elif defined(TH_CONFIG_OS_MOCK) 13 | #include "th_mock_syscall.h" 14 | #endif 15 | 16 | TH_PRIVATE(void) 17 | th_dir_init(th_dir* dir, th_allocator* allocator) 18 | { 19 | dir->allocator = allocator ? allocator : th_default_allocator_get(); 20 | dir->fd = -1; 21 | th_heap_string_init(&dir->path, dir->allocator); 22 | } 23 | 24 | TH_PRIVATE(th_err) 25 | th_dir_open(th_dir* dir, th_string path) 26 | { 27 | th_err err = TH_ERR_OK; 28 | if ((err = th_path_resolve(path, &dir->path)) != TH_ERR_OK) 29 | return err; 30 | #if defined(TH_CONFIG_OS_POSIX) 31 | if (path.len > TH_CONFIG_MAX_PATH_LEN) 32 | return TH_ERR_INVALID_ARG; 33 | char path_buf[TH_CONFIG_MAX_PATH_LEN + 1] = {0}; 34 | memcpy(path_buf, path.ptr, path.len); 35 | path_buf[path.len] = '\0'; 36 | int fd = open(path_buf, O_RDONLY | O_DIRECTORY); 37 | if (fd < 0) 38 | return TH_ERR_SYSTEM(errno); 39 | dir->fd = fd; 40 | return TH_ERR_OK; 41 | #elif defined(TH_CONFIG_OS_MOCK) 42 | (void)path; 43 | int fd = th_mock_open(); 44 | if (fd < 0) 45 | return TH_ERR_SYSTEM(-fd); 46 | dir->fd = fd; 47 | return TH_ERR_OK; 48 | #endif 49 | } 50 | 51 | TH_PRIVATE(th_string) 52 | th_dir_get_path(th_dir* dir) 53 | { 54 | return th_heap_string_view(&dir->path); 55 | } 56 | 57 | TH_PRIVATE(void) 58 | th_dir_deinit(th_dir* dir) 59 | { 60 | th_heap_string_deinit(&dir->path); 61 | #if defined(TH_CONFIG_OS_POSIX) 62 | if (dir->fd >= 0) { 63 | int ret = close(dir->fd); 64 | (void)ret; 65 | TH_ASSERT(ret == 0 && "This should not happen"); 66 | } 67 | #elif defined(TH_CONFIG_OS_MOCK) 68 | if (dir->fd >= 0) { 69 | int ret = th_mock_close(); 70 | (void)ret; 71 | TH_ASSERT(ret == 0 && "This should not happen"); 72 | } 73 | #endif 74 | } 75 | -------------------------------------------------------------------------------- /src/th_dir.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_DIR_H 2 | #define TH_DIR_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_heap_string.h" 8 | #include "th_string.h" 9 | 10 | typedef struct th_dir { 11 | th_allocator* allocator; 12 | th_heap_string path; 13 | int fd; 14 | } th_dir; 15 | 16 | TH_PRIVATE(void) 17 | th_dir_init(th_dir* dir, th_allocator* allocator); 18 | 19 | TH_PRIVATE(th_err) 20 | th_dir_open(th_dir* dir, th_string path); 21 | 22 | TH_PRIVATE(th_string) 23 | th_dir_get_path(th_dir* dir); 24 | 25 | TH_PRIVATE(void) 26 | th_dir_deinit(th_dir* dir); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/th_dir_mgr.c: -------------------------------------------------------------------------------- 1 | #include "th_dir_mgr.h" 2 | 3 | TH_PRIVATE(void) 4 | th_dir_mgr_init(th_dir_mgr* mgr, th_allocator* allocator) 5 | { 6 | mgr->allocator = allocator ? allocator : th_default_allocator_get(); 7 | th_dir_map_init(&mgr->map, allocator); 8 | th_heap_string_vec_init(&mgr->heap_strings, allocator); 9 | } 10 | 11 | TH_LOCAL(bool) 12 | th_dir_mgr_label_exists(th_dir_mgr* mgr, th_string label) 13 | { 14 | return th_dir_map_find(&mgr->map, label) != NULL; 15 | } 16 | 17 | TH_LOCAL(th_err) 18 | th_dir_mgr_store_string(th_dir_mgr* mgr, th_string str) 19 | { 20 | th_heap_string heap_str = {0}; 21 | th_heap_string_init(&heap_str, mgr->allocator); 22 | if (th_heap_string_set(&heap_str, str) != TH_ERR_OK) { 23 | return TH_ERR_BAD_ALLOC; 24 | } 25 | if (th_heap_string_vec_push_back(&mgr->heap_strings, heap_str) != TH_ERR_OK) { 26 | th_heap_string_deinit(&heap_str); 27 | return TH_ERR_BAD_ALLOC; 28 | } 29 | return TH_ERR_OK; 30 | } 31 | 32 | TH_LOCAL(th_string) 33 | th_dir_mgr_get_last_string(th_dir_mgr* mgr) 34 | { 35 | return th_heap_string_view(th_heap_string_vec_end(&mgr->heap_strings) - 1); 36 | } 37 | 38 | TH_LOCAL(void) 39 | th_dir_mgr_remove_last_string(th_dir_mgr* mgr) 40 | { 41 | th_heap_string_deinit(th_heap_string_vec_end(&mgr->heap_strings) - 1); 42 | th_heap_string_vec_resize(&mgr->heap_strings, th_heap_string_vec_size(&mgr->heap_strings) - 1); 43 | } 44 | 45 | TH_PRIVATE(th_err) 46 | th_dir_mgr_add(th_dir_mgr* mgr, th_string label, th_string path) 47 | { 48 | th_err err = TH_ERR_OK; 49 | if (th_dir_mgr_label_exists(mgr, label)) 50 | return TH_ERR_INVALID_ARG; 51 | th_dir dir = {0}; 52 | th_dir_init(&dir, mgr->allocator); 53 | if ((err = th_dir_open(&dir, path)) != TH_ERR_OK) 54 | goto cleanup_dir; 55 | if ((err = th_dir_mgr_store_string(mgr, label)) != TH_ERR_OK) 56 | goto cleanup_dir; 57 | if ((err = th_dir_map_set(&mgr->map, th_dir_mgr_get_last_string(mgr), dir)) != TH_ERR_OK) 58 | goto cleanup_string; 59 | return TH_ERR_OK; 60 | cleanup_string: 61 | th_dir_mgr_remove_last_string(mgr); 62 | cleanup_dir: 63 | th_dir_deinit(&dir); 64 | return err; 65 | } 66 | 67 | TH_PRIVATE(th_dir*) 68 | th_dir_mgr_get(th_dir_mgr* mgr, th_string label) 69 | { 70 | th_dir_map_iter it = th_dir_map_find(&mgr->map, label); 71 | if (it == NULL) 72 | return NULL; 73 | return &it->value; 74 | } 75 | 76 | TH_PRIVATE(void) 77 | th_dir_mgr_deinit(th_dir_mgr* mgr) 78 | { 79 | th_dir_map_iter it = th_dir_map_begin(&mgr->map); 80 | while (it != NULL) { 81 | th_dir_deinit(&it->value); 82 | it = th_dir_map_next(&mgr->map, it); 83 | } 84 | th_dir_map_deinit(&mgr->map); 85 | th_heap_string_vec_deinit(&mgr->heap_strings); 86 | } 87 | -------------------------------------------------------------------------------- /src/th_dir_mgr.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_DIR_MGR_H 2 | #define TH_DIR_MGR_H 3 | 4 | #include 5 | 6 | #include "th_allocator.h" 7 | #include "th_dir.h" 8 | #include "th_hashmap.h" 9 | #include "th_heap_string.h" 10 | #include "th_string.h" 11 | 12 | TH_DEFINE_HASHMAP(th_dir_map, th_string, th_dir, th_string_hash, th_string_eq, (th_string){0}) 13 | 14 | typedef struct th_dir_mgr { 15 | th_allocator* allocator; 16 | th_dir_map map; 17 | th_heap_string_vec heap_strings; 18 | } th_dir_mgr; 19 | 20 | TH_PRIVATE(void) 21 | th_dir_mgr_init(th_dir_mgr* mgr, th_allocator* allocator); 22 | 23 | TH_PRIVATE(th_err) 24 | th_dir_mgr_add(th_dir_mgr* mgr, th_string label, th_string path); 25 | 26 | TH_PRIVATE(th_dir*) 27 | th_dir_mgr_get(th_dir_mgr* mgr, th_string label); 28 | 29 | TH_PRIVATE(void) 30 | th_dir_mgr_deinit(th_dir_mgr* mgr); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/th_dir_mgr_test.c: -------------------------------------------------------------------------------- 1 | #include "th_dir_mgr.h" 2 | #include "th_mock_syscall.h" 3 | #include "th_test.h" 4 | 5 | static int 6 | bad_open(void) 7 | { 8 | return -TH_ENOENT; 9 | } 10 | 11 | TH_TEST_BEGIN(dir_mgr) 12 | { 13 | TH_TEST_CASE_BEGIN(dir_mgr_init) 14 | { 15 | th_dir_mgr mgr = {0}; 16 | th_dir_mgr_init(&mgr, NULL); 17 | th_dir_mgr_deinit(&mgr); 18 | } 19 | TH_TEST_CASE_END 20 | TH_TEST_CASE_BEGIN(dir_mgr_add) 21 | { 22 | th_dir_mgr mgr = {0}; 23 | th_dir_mgr_init(&mgr, NULL); 24 | TH_EXPECT(th_dir_mgr_add(&mgr, TH_STRING("test"), TH_STRING("/")) == TH_ERR_OK); 25 | TH_EXPECT(th_dir_mgr_get(&mgr, TH_STRING("test")) != NULL); 26 | th_dir_mgr_deinit(&mgr); 27 | } 28 | TH_TEST_CASE_END 29 | TH_TEST_CASE_BEGIN(dir_mgr_add_bad_open) 30 | { 31 | th_mock_syscall_get()->open = bad_open; 32 | th_dir_mgr mgr = {0}; 33 | th_dir_mgr_init(&mgr, NULL); 34 | TH_EXPECT(th_dir_mgr_add(&mgr, TH_STRING("test"), TH_STRING("/")) == TH_ERR_SYSTEM(TH_ENOENT)); 35 | TH_EXPECT(th_dir_mgr_get(&mgr, TH_STRING("test")) == NULL); 36 | th_dir_mgr_deinit(&mgr); 37 | } 38 | TH_TEST_END 39 | } 40 | TH_TEST_END 41 | -------------------------------------------------------------------------------- /src/th_error.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "th_http_error.h" 5 | #include "th_ssl_error.h" 6 | #include "th_utility.h" 7 | 8 | TH_PUBLIC(const char*) 9 | th_strerror(th_err err) 10 | { 11 | switch (TH_ERR_CATEGORY(err)) { 12 | case TH_ERR_CATEGORY_OTHER: 13 | switch (TH_ERR_CODE(err)) { 14 | case 0: 15 | return "success"; 16 | case TH_ERRC_BAD_ALLOC: 17 | return "out of memory"; 18 | case TH_ERRC_INVALID_ARG: 19 | return "invalid argument"; 20 | case TH_ERRC_EOF: 21 | return "end of file"; 22 | } 23 | break; 24 | case TH_ERR_CATEGORY_SYSTEM: 25 | return strerror(TH_ERR_CODE(err)); 26 | case TH_ERR_CATEGORY_HTTP: 27 | return th_http_strerror(TH_ERR_CODE(err)); 28 | case TH_ERR_CATEGORY_SSL: 29 | #if TH_WITH_SSL 30 | return th_ssl_strerror(TH_ERR_CODE(err)); 31 | #else 32 | TH_ASSERT(0 && "SSL not enabled"); 33 | return NULL; 34 | #endif 35 | } 36 | return "Unknown error category"; 37 | } 38 | -------------------------------------------------------------------------------- /src/th_fcache.c: -------------------------------------------------------------------------------- 1 | #include "th_fcache.h" 2 | #include "th_log.h" 3 | 4 | #undef TH_LOG_TAG 5 | #define TH_LOG_TAG "fcache" 6 | 7 | TH_LOCAL(th_fcache_id) 8 | th_fcache_entry_id(th_fcache_entry* entry) 9 | { 10 | return (th_fcache_id){th_heap_string_view(&entry->path), entry->dir}; 11 | } 12 | 13 | TH_LOCAL(void) 14 | th_fcache_entry_actual_destroy(void* self) 15 | { 16 | th_fcache_entry* entry = self; 17 | // Remove entry from cache 18 | th_fcache_map_iter it = th_fcache_map_find(&entry->cache->map, th_fcache_entry_id(entry)); 19 | if (it != NULL) { 20 | th_fcache_map_erase(&entry->cache->map, it); 21 | } 22 | th_file_deinit(&entry->stream); 23 | th_heap_string_deinit(&entry->path); 24 | th_allocator_free(entry->allocator, entry); 25 | } 26 | 27 | TH_LOCAL(void) 28 | th_fcache_entry_init(th_fcache_entry* entry, th_fcache* cache, th_allocator* allocator) 29 | { 30 | entry->allocator = allocator ? allocator : th_default_allocator_get(); 31 | th_refcounted_init(&entry->base, th_fcache_entry_actual_destroy); 32 | th_file_init(&entry->stream); 33 | th_heap_string_init(&entry->path, entry->allocator); 34 | entry->cache = cache; 35 | entry->next = NULL; 36 | entry->prev = NULL; 37 | } 38 | 39 | TH_LOCAL(th_err) 40 | th_fcache_entry_open(th_fcache_entry* entry, th_string root, th_string path) 41 | { 42 | th_err err = TH_ERR_OK; 43 | th_dir* dir = th_dir_mgr_get(&entry->cache->dir_mgr, root); 44 | if (!dir) 45 | return TH_ERR_INVALID_ARG; 46 | th_open_opt opt = {.read = true}; 47 | if ((err = th_file_openat(&entry->stream, dir, path, opt)) != TH_ERR_OK) { 48 | TH_LOG_INFO("Failed to open file at %.*s: %s", (int)path.len, path.ptr, th_strerror(err)); 49 | goto cleanup; 50 | } 51 | if ((err = th_heap_string_set(&entry->path, path)) != TH_ERR_OK) { 52 | TH_LOG_ERROR("Failed to set path: %s", th_strerror(err)); 53 | goto cleanup_fstream; 54 | } 55 | entry->stat_hash = th_file_stat_hash(&entry->stream); 56 | entry->dir = dir; 57 | return TH_ERR_OK; 58 | cleanup_fstream: 59 | th_file_deinit(&entry->stream); 60 | cleanup: 61 | return err; 62 | } 63 | 64 | TH_LOCAL(th_fcache_entry*) 65 | th_fcache_entry_ref(th_fcache_entry* entry) 66 | { 67 | th_refcounted_ref(&entry->base); 68 | return entry; 69 | } 70 | 71 | TH_PRIVATE(void) 72 | th_fcache_entry_unref(th_fcache_entry* entry) 73 | { 74 | th_refcounted_unref(&entry->base); 75 | } 76 | 77 | TH_PRIVATE(void) 78 | th_fcache_init(th_fcache* cache, th_allocator* allocator) 79 | { 80 | cache->allocator = allocator ? allocator : th_default_allocator_get(); 81 | th_dir_mgr_init(&cache->dir_mgr, cache->allocator); 82 | th_fcache_map_init(&cache->map, cache->allocator); 83 | cache->list = (th_fcache_list){NULL, NULL}; 84 | cache->num_cached = 0; 85 | cache->max_cached = TH_CONFIG_MAX_CACHED_FDS; 86 | } 87 | 88 | TH_LOCAL(void) 89 | th_fcache_erase(th_fcache* cache, th_fcache_entry* entry) 90 | { 91 | th_fcache_list_erase(&cache->list, entry); 92 | th_fcache_entry_unref(entry); 93 | --cache->num_cached; 94 | } 95 | 96 | TH_LOCAL(th_fcache_entry*) 97 | th_fcache_try_get(th_fcache* cache, th_string root, th_string path) 98 | { 99 | th_dir* dir = th_dir_mgr_get(&cache->dir_mgr, root); 100 | if (!dir) 101 | return NULL; 102 | th_fcache_entry** v = th_fcache_map_try_get(&cache->map, (th_fcache_id){path, dir}); 103 | if (!v) 104 | return NULL; 105 | th_fcache_entry* entry = *v; 106 | // Check if the file has been modified 107 | uint32_t hash = th_file_stat_hash(&entry->stream); 108 | if (hash != entry->stat_hash) { 109 | TH_LOG_TRACE("File has been modified, don't use cached entry"); 110 | th_fcache_erase(cache, entry); 111 | return NULL; 112 | } 113 | // Move entry to the back of the list 114 | th_fcache_list_erase(&cache->list, entry); 115 | th_fcache_list_push_back(&cache->list, entry); 116 | return th_fcache_entry_ref(entry); 117 | } 118 | 119 | TH_PRIVATE(th_err) 120 | th_fcache_add_dir(th_fcache* cache, th_string label, th_string path) 121 | { 122 | return th_dir_mgr_add(&cache->dir_mgr, label, path); 123 | } 124 | 125 | TH_PRIVATE(th_dir*) 126 | th_fcache_find_dir(th_fcache* cache, th_string label) 127 | { 128 | return th_dir_mgr_get(&cache->dir_mgr, label); 129 | } 130 | 131 | TH_LOCAL(th_err) 132 | th_fcache_insert(th_fcache* cache, th_fcache_entry* entry) 133 | { 134 | if (cache->num_cached == cache->max_cached) { 135 | // Evict the first entry 136 | th_fcache_entry* first = th_fcache_list_front(&cache->list); 137 | th_fcache_erase(cache, first); 138 | } 139 | th_err err = TH_ERR_OK; 140 | if ((err = th_fcache_map_set(&cache->map, th_fcache_entry_id(entry), entry)) != TH_ERR_OK) { 141 | TH_LOG_ERROR("Failed to insert entry into map: %s", th_strerror(err)); 142 | return err; 143 | } 144 | th_fcache_list_push_back(&cache->list, th_fcache_entry_ref(entry)); 145 | cache->num_cached++; 146 | return TH_ERR_OK; 147 | } 148 | 149 | TH_PRIVATE(th_err) 150 | th_fcache_get(th_fcache* cache, th_string root, th_string path, th_fcache_entry** out) 151 | { 152 | th_fcache_entry* entry = th_fcache_try_get(cache, root, path); 153 | if (entry) { 154 | *out = entry; 155 | return TH_ERR_OK; 156 | } 157 | entry = th_allocator_alloc(cache->allocator, sizeof(th_fcache_entry)); 158 | if (!entry) 159 | return TH_ERR_BAD_ALLOC; 160 | th_fcache_entry_init(entry, cache, cache->allocator); 161 | th_err err = TH_ERR_OK; 162 | if ((err = th_fcache_entry_open(entry, root, path)) != TH_ERR_OK) { 163 | th_allocator_free(cache->allocator, entry); 164 | return err; 165 | } 166 | if ((err = th_fcache_insert(cache, entry)) != TH_ERR_OK) { 167 | TH_LOG_ERROR("Failed to insert fcache entry"); 168 | th_fcache_entry_unref(entry); 169 | return err; 170 | } 171 | *out = entry; 172 | return TH_ERR_OK; 173 | } 174 | 175 | TH_PRIVATE(void) 176 | th_fcache_deinit(th_fcache* cache) 177 | { 178 | th_fcache_entry* entry = NULL; 179 | while ((entry = th_fcache_list_pop_front(&cache->list))) { 180 | th_fcache_entry_unref(entry); 181 | } 182 | th_fcache_map_deinit(&cache->map); 183 | th_dir_mgr_deinit(&cache->dir_mgr); 184 | } 185 | -------------------------------------------------------------------------------- /src/th_fcache.h: -------------------------------------------------------------------------------- 1 | #ifndef FCACHE_H 2 | #define FCACHE_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_dir.h" 8 | #include "th_dir_mgr.h" 9 | #include "th_file.h" 10 | #include "th_hashmap.h" 11 | #include "th_heap_string.h" 12 | #include "th_queue.h" 13 | #include "th_refcounted.h" 14 | #include "th_timer.h" 15 | 16 | typedef struct th_fcache th_fcache; 17 | typedef struct th_fcache_entry th_fcache_entry; 18 | struct th_fcache_entry { 19 | th_refcounted base; 20 | th_file stream; 21 | th_heap_string path; 22 | th_dir* dir; 23 | th_allocator* allocator; 24 | th_fcache* cache; 25 | th_fcache_entry* next; 26 | th_fcache_entry* prev; 27 | uint32_t stat_hash; 28 | }; 29 | 30 | typedef struct th_fcache_id { 31 | th_string path; 32 | th_dir* dir; 33 | } th_fcache_id; 34 | 35 | TH_INLINE(bool) 36 | th_fcache_id_eq(th_fcache_id a, th_fcache_id b) 37 | { 38 | return a.dir == b.dir && th_string_eq(a.path, b.path); 39 | } 40 | 41 | TH_INLINE(uint32_t) 42 | th_fcache_id_hash(th_fcache_id id) 43 | { 44 | return th_string_hash(id.path) + id.dir->fd; 45 | } 46 | 47 | TH_DEFINE_HASHMAP(th_fcache_map, th_fcache_id, th_fcache_entry*, th_fcache_id_hash, th_fcache_id_eq, (th_fcache_id){0}) 48 | TH_DEFINE_LIST(th_fcache_list, th_fcache_entry, prev, next) 49 | 50 | struct th_fcache { 51 | th_allocator* allocator; 52 | th_dir_mgr dir_mgr; 53 | th_fcache_map map; 54 | th_fcache_list list; 55 | size_t num_cached; 56 | size_t max_cached; 57 | }; 58 | 59 | // fcache entry functions 60 | 61 | TH_PRIVATE(void) 62 | th_fcache_entry_unref(th_fcache_entry* entry); 63 | 64 | // fcache functions 65 | 66 | TH_PRIVATE(void) 67 | th_fcache_init(th_fcache* cache, th_allocator* allocator); 68 | 69 | TH_PRIVATE(th_err) 70 | th_fcache_get(th_fcache* cache, th_string root, th_string path, th_fcache_entry** out); 71 | 72 | TH_PRIVATE(th_err) 73 | th_fcache_add_dir(th_fcache* cache, th_string label, th_string path); 74 | 75 | TH_PRIVATE(th_dir*) 76 | th_fcache_find_dir(th_fcache* cache, th_string label); 77 | 78 | TH_PRIVATE(void) 79 | th_fcache_deinit(th_fcache* cache); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/th_fcache_test.c: -------------------------------------------------------------------------------- 1 | #include "th_fcache.h" 2 | #include "th_mock_syscall.h" 3 | #include "th_test.h" 4 | 5 | #include 6 | 7 | static int th_mock_open_bad(void) 8 | { 9 | return -TH_ENOENT; 10 | } 11 | 12 | TH_TEST_BEGIN(fcache) 13 | { 14 | TH_TEST_CASE_BEGIN(fcache_init) 15 | { 16 | th_fcache cache = {0}; 17 | th_fcache_init(&cache, NULL); 18 | th_fcache_deinit(&cache); 19 | } 20 | TH_TEST_CASE_END 21 | TH_TEST_CASE_BEGIN(fcache_open) 22 | { 23 | th_fcache cache = {0}; 24 | th_fcache_init(&cache, NULL); 25 | th_fcache_entry* entry1 = NULL; 26 | th_fcache_entry* entry2 = NULL; 27 | th_fcache_entry* entry3 = NULL; 28 | TH_EXPECT(th_fcache_add_dir(&cache, TH_STRING("/"), TH_STRING("/")) == TH_ERR_OK); 29 | TH_EXPECT(th_fcache_get(&cache, TH_STRING("/"), TH_STRING("test"), &entry1) == TH_ERR_OK); 30 | TH_EXPECT(th_fcache_get(&cache, TH_STRING("/"), TH_STRING("test"), &entry2) == TH_ERR_OK); 31 | TH_EXPECT(th_fcache_get(&cache, TH_STRING("/"), TH_STRING("test"), &entry3) == TH_ERR_OK); 32 | TH_EXPECT(entry1->stream.fd == entry2->stream.fd); 33 | TH_EXPECT(entry2->stream.fd == entry3->stream.fd); 34 | th_fcache_entry_unref(entry1); 35 | th_fcache_entry_unref(entry2); 36 | th_fcache_entry_unref(entry3); 37 | th_fcache_deinit(&cache); 38 | } 39 | TH_TEST_CASE_END 40 | TH_TEST_CASE_BEGIN(fcache_open_bad) 41 | { 42 | th_fcache cache = {0}; 43 | th_fcache_init(&cache, NULL); 44 | TH_EXPECT(th_fcache_add_dir(&cache, TH_STRING("/"), TH_STRING("/")) == TH_ERR_OK); 45 | th_mock_syscall_get()->open = th_mock_open_bad; 46 | th_fcache_entry* entry = NULL; 47 | TH_EXPECT(th_fcache_get(&cache, TH_STRING("/"), TH_STRING("test"), &entry) != TH_ERR_OK); 48 | th_fcache_deinit(&cache); 49 | th_mock_syscall_reset(); 50 | } 51 | TH_TEST_CASE_END 52 | } 53 | TH_TEST_END 54 | -------------------------------------------------------------------------------- /src/th_file.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_FSTREAM_H 2 | #define TH_FSTREAM_H 3 | 4 | #include 5 | 6 | #include "th_dir.h" 7 | 8 | typedef struct th_file_mmap { 9 | void* addr; 10 | size_t offset; 11 | size_t len; 12 | } th_file_mmap; 13 | 14 | typedef struct th_file { 15 | int fd; 16 | size_t size; 17 | th_file_mmap view; 18 | } th_file; 19 | 20 | TH_PRIVATE(void) 21 | th_file_init(th_file* stream); 22 | 23 | typedef struct th_open_opt { 24 | bool read; 25 | bool write; 26 | bool create; 27 | bool truncate; 28 | } th_open_opt; 29 | 30 | TH_PRIVATE(th_err) 31 | th_file_openat(th_file* stream, th_dir* dir, th_string path, th_open_opt opt); 32 | 33 | TH_PRIVATE(th_err) 34 | th_file_read(th_file* stream, void* addr, size_t len, size_t offset, size_t* read) TH_MAYBE_UNUSED; 35 | 36 | TH_PRIVATE(th_err) 37 | th_file_write(th_file* stream, const void* addr, size_t len, size_t offset, size_t* written) TH_MAYBE_UNUSED; 38 | 39 | typedef struct th_fileview { 40 | void* ptr; 41 | size_t len; 42 | } th_fileview; 43 | 44 | TH_PRIVATE(th_err) 45 | th_file_get_view(th_file* stream, th_fileview* view, size_t offset, size_t len); 46 | 47 | TH_PRIVATE(uint32_t) 48 | th_file_stat_hash(th_file* stream); 49 | 50 | TH_PRIVATE(void) 51 | th_file_close(th_file* stream); 52 | 53 | TH_PRIVATE(void) 54 | th_file_deinit(th_file* stream); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/th_fmt.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_FMT_H 2 | #define TH_FMT_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "th_config.h" 11 | 12 | TH_PRIVATE(const char*) 13 | th_fmt_uint_to_str(char* buf, size_t len, unsigned int val); 14 | 15 | TH_PRIVATE(const char*) 16 | th_fmt_uint_to_str_ex(char* buf, size_t len, unsigned int val, size_t* out_len); 17 | 18 | /** th_fmt_str_append 19 | * @brief Append a string to a buffer. 20 | * @param buf The buffer to append to. 21 | * @param pos The current position in the buffer (where to append). 22 | * @param len The length of the buffer. 23 | * @param str The string to append. 24 | * @return The number of characters appended. 25 | */ 26 | TH_PRIVATE(size_t) 27 | th_fmt_str_append(char* buf, size_t pos, size_t len, const char* str); 28 | 29 | TH_PRIVATE(size_t) 30 | th_fmt_strn_append(char* buf, size_t pos, size_t len, const char* str, size_t n); 31 | 32 | TH_PRIVATE(size_t) 33 | th_fmt_strtime(char* buf, size_t len, th_date date); 34 | #endif 35 | -------------------------------------------------------------------------------- /src/th_hash.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_HASH_H 2 | #define TH_HASH_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "th_config.h" 9 | 10 | /** th_hash_bytes 11 | * @brief Fowler-Noll-Vo hash function (FNV-1a). 12 | * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function 13 | */ 14 | TH_INLINE(uint32_t) 15 | th_hash_bytes(const void* data, size_t len) 16 | { 17 | uint32_t hash = 2166136261u; 18 | const uint8_t* bytes = (const uint8_t*)data; 19 | for (size_t i = 0; i < len; ++i) { 20 | hash ^= bytes[i]; 21 | hash *= 16777619; 22 | } 23 | return hash; 24 | } 25 | 26 | TH_INLINE(uint32_t) 27 | th_hash_cstr(const char* str) 28 | { 29 | return th_hash_bytes(str, strlen(str)); 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/th_header_id.gperf: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include 4 | #include "th_header_id.h" 5 | #pragma GCC diagnostic push 6 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 7 | #pragma GCC diagnostic ignored "-Wunused-parameter" 8 | %} 9 | %define lookup-function-name th_header_id_mapping_find 10 | %define constants-prefix TH_HEADER_ID_ 11 | %define hash-function-name th_header_id_hash 12 | %struct-type 13 | %compare-strncmp 14 | struct th_header_id_mapping; 15 | %% 16 | connection, TH_HEADER_ID_CONNECTION 17 | content-type, TH_HEADER_ID_CONTENT_TYPE 18 | content-length, TH_HEADER_ID_CONTENT_LENGTH 19 | cookie, TH_HEADER_ID_COOKIE 20 | transfer-encoding, TH_HEADER_ID_TRANSFER_ENCODING 21 | range, TH_HEADER_ID_RANGE 22 | %% 23 | #pragma GCC diagnostic pop 24 | -------------------------------------------------------------------------------- /src/th_header_id.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_UNIQUE_HEADER_ID_H 2 | #define TH_UNIQUE_HEADER_ID_H 3 | 4 | #include "th_config.h" 5 | 6 | #include 7 | #include 8 | 9 | typedef enum th_header_id { 10 | TH_HEADER_ID_CONNECTION, 11 | TH_HEADER_ID_CONTENT_LENGTH, 12 | TH_HEADER_ID_CONTENT_TYPE, 13 | TH_HEADER_ID_DATE, 14 | TH_HEADER_ID_SERVER, 15 | TH_HEADER_ID_COOKIE, 16 | TH_HEADER_ID_TRANSFER_ENCODING, 17 | TH_HEADER_ID_RANGE, 18 | TH_HEADER_ID_MAX, 19 | TH_HEADER_ID_UNKNOWN = TH_HEADER_ID_MAX, 20 | } th_header_id; 21 | 22 | struct th_header_id_mapping { 23 | const char* name; 24 | th_header_id id; 25 | }; 26 | 27 | struct th_header_id_mapping* 28 | th_header_id_mapping_find(const char* name, size_t len); 29 | 30 | TH_INLINE(th_header_id) 31 | th_header_id_from_string(const char* name, size_t len) 32 | { 33 | struct th_header_id_mapping* mapping = th_header_id_mapping_find(name, (unsigned int)len); 34 | return mapping ? mapping->id : TH_HEADER_ID_UNKNOWN; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/th_heap_string.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_HEAP_STRING_H 2 | #define TH_HEAP_STRING_H 3 | 4 | #include "th_allocator.h" 5 | #include "th_string.h" 6 | #include "th_vec.h" 7 | 8 | typedef struct th_detail_large_string { 9 | size_t capacity; 10 | size_t len; 11 | char* ptr; 12 | th_allocator* allocator; 13 | } th_detail_large_string; 14 | 15 | #define TH_HEAP_STRING_SMALL_BUF_LEN (sizeof(char*) + sizeof(size_t) + sizeof(size_t) - 1) 16 | #define TH_HEAP_STRING_SMALL_MAX_LEN (TH_HEAP_STRING_SMALL_BUF_LEN - 1) 17 | typedef struct th_detail_small_string { 18 | unsigned char small : 1; 19 | unsigned char len : 7; 20 | char buf[TH_HEAP_STRING_SMALL_BUF_LEN]; 21 | th_allocator* allocator; 22 | } th_detail_small_string; 23 | 24 | typedef struct th_heap_string { 25 | union { 26 | th_detail_small_string small; 27 | th_detail_large_string large; 28 | } impl; 29 | } th_heap_string; 30 | 31 | TH_PRIVATE(void) 32 | th_heap_string_init(th_heap_string* self, th_allocator* allocator); 33 | 34 | TH_PRIVATE(th_err) 35 | th_heap_string_init_with(th_heap_string* self, th_string str, th_allocator* allocator); 36 | 37 | TH_PRIVATE(th_err) 38 | th_heap_string_set(th_heap_string* self, th_string str); 39 | 40 | TH_PRIVATE(th_err) 41 | th_heap_string_append(th_heap_string* self, th_string str); 42 | 43 | TH_PRIVATE(th_err) 44 | th_heap_string_append_cstr(th_heap_string* self, const char* str); 45 | 46 | TH_PRIVATE(th_err) 47 | th_heap_string_push_back(th_heap_string* self, char c); 48 | 49 | TH_PRIVATE(th_err) 50 | th_heap_string_resize(th_heap_string* self, size_t new_len, char fill); 51 | 52 | TH_PRIVATE(th_string) 53 | th_heap_string_view(const th_heap_string* self); 54 | 55 | TH_PRIVATE(char*) 56 | th_heap_string_at(th_heap_string* self, size_t index); 57 | 58 | TH_PRIVATE(const char*) 59 | th_heap_string_data(const th_heap_string* self); 60 | 61 | TH_PRIVATE(size_t) 62 | th_heap_string_len(const th_heap_string* self); 63 | 64 | TH_PRIVATE(void) 65 | th_heap_string_deinit(th_heap_string* self); 66 | 67 | TH_PRIVATE(void) 68 | th_heap_string_clear(th_heap_string* self); 69 | 70 | TH_PRIVATE(void) 71 | th_heap_string_to_lower(th_heap_string* self); 72 | 73 | TH_PRIVATE(bool) 74 | th_heap_string_eq(const th_heap_string* self, th_string other); 75 | 76 | //TH_PRIVATE(uint32_t) 77 | //th_heap_string_hash(const th_heap_string* self); 78 | 79 | TH_DEFINE_VEC(th_heap_string_vec, th_heap_string, th_heap_string_deinit) 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/th_heap_string_test.c: -------------------------------------------------------------------------------- 1 | #include "th_heap_string.h" 2 | #include "th_test.h" 3 | 4 | TH_TEST_BEGIN(heap_string) 5 | { 6 | TH_TEST_CASE_BEGIN(heap_string_init) 7 | { 8 | th_heap_string str; 9 | th_heap_string_init(&str, th_default_allocator_get()); 10 | TH_EXPECT(th_heap_string_len(&str) == 0); 11 | TH_EXPECT(th_heap_string_data(&str) != NULL); 12 | th_heap_string_deinit(&str); 13 | } 14 | TH_TEST_CASE_END 15 | TH_TEST_CASE_BEGIN(heap_string_set) 16 | { 17 | th_heap_string str; 18 | th_heap_string_init(&str, th_default_allocator_get()); 19 | th_string s = TH_STRING("hello"); 20 | TH_EXPECT(th_heap_string_set(&str, s) == TH_ERR_OK); 21 | TH_EXPECT(th_heap_string_len(&str) == s.len); 22 | TH_EXPECT(th_string_eq(th_heap_string_view(&str), s)); 23 | s = TH_STRING("Lorem ipsum dolor sit amet"); 24 | TH_EXPECT(th_heap_string_set(&str, s) == TH_ERR_OK); 25 | TH_EXPECT(th_heap_string_len(&str) == s.len); 26 | TH_EXPECT(th_string_eq(th_heap_string_view(&str), s)); 27 | s = TH_STRING(""); 28 | TH_EXPECT(th_heap_string_set(&str, s) == TH_ERR_OK); 29 | TH_EXPECT(th_heap_string_len(&str) == s.len); 30 | TH_EXPECT(th_string_eq(th_heap_string_view(&str), s)); 31 | s = TH_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ullamcorper mi ut felis pulvinar tincidunt."); 32 | TH_EXPECT(th_heap_string_set(&str, s) == TH_ERR_OK); 33 | TH_EXPECT(th_heap_string_len(&str) == s.len); 34 | TH_EXPECT(th_string_eq(th_heap_string_view(&str), s)); 35 | s = TH_STRING(""); 36 | TH_EXPECT(th_heap_string_set(&str, s) == TH_ERR_OK); 37 | TH_EXPECT(th_heap_string_len(&str) == s.len); 38 | TH_EXPECT(th_string_eq(th_heap_string_view(&str), s)); 39 | th_heap_string_deinit(&str); 40 | } 41 | TH_TEST_CASE_END 42 | TH_TEST_CASE_BEGIN(heap_string_append) 43 | { 44 | th_heap_string str; 45 | th_heap_string_init(&str, th_default_allocator_get()); 46 | for (int i = 0; i < 100; ++i) { 47 | TH_EXPECT(th_heap_string_append(&str, TH_STRING("A")) == TH_ERR_OK); 48 | TH_EXPECT(th_heap_string_len(&str) == (size_t)(i + 1)); 49 | } 50 | th_heap_string_deinit(&str); 51 | } 52 | TH_TEST_CASE_END 53 | } 54 | TH_TEST_END 55 | -------------------------------------------------------------------------------- /src/th_http.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_HTTP_H 2 | #define TH_HTTP_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_conn.h" 8 | #include "th_conn_tracker.h" 9 | #include "th_fcache.h" 10 | #include "th_request.h" 11 | #include "th_request_parser.h" 12 | #include "th_response.h" 13 | #include "th_router.h" 14 | 15 | typedef struct th_http th_http; 16 | 17 | typedef enum th_http_state { 18 | TH_HTTP_STATE_READ_REQUEST, 19 | TH_HTTP_STATE_WRITE_RESPONSE, 20 | } th_http_state; 21 | 22 | typedef struct th_http_io_handler { 23 | th_io_handler base; 24 | th_http* http; 25 | } th_http_io_handler; 26 | 27 | struct th_http { 28 | const th_conn_tracker* tracker; 29 | th_http_io_handler io_handler; 30 | th_request_parser parser; 31 | th_request request; 32 | th_response response; 33 | th_buf_vec buf; 34 | th_conn* conn; 35 | th_router* router; 36 | th_fcache* fcache; 37 | th_allocator* allocator; 38 | size_t read_bytes; 39 | size_t parsed_bytes; 40 | 41 | // the current state of the http connection 42 | th_http_state state; 43 | 44 | // true if the connection should be closed 45 | bool close; 46 | }; 47 | 48 | typedef struct th_http_upgrader { 49 | th_conn_upgrader base; 50 | const th_conn_tracker* tracker; 51 | th_router* router; 52 | th_fcache* fcache; 53 | th_allocator* allocator; 54 | } th_http_upgrader; 55 | 56 | TH_PRIVATE(void) 57 | th_http_upgrader_init(th_http_upgrader* upgrader, const th_conn_tracker* tracker, th_router* router, 58 | th_fcache* fcache, th_allocator* allocator); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/th_http_error.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_HTTP_ERR_H 2 | #define TH_HTTP_ERR_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_system_error.h" 8 | 9 | #include 10 | 11 | /** th_http_err 12 | * @brief Converts a error code to a equivalent HTTP error code. 13 | */ 14 | TH_INLINE(th_err) 15 | th_http_error(th_err err) 16 | { 17 | if (err == TH_ERR_OK) 18 | return TH_ERR_HTTP(TH_CODE_OK); 19 | switch (TH_ERR_CATEGORY(err)) { 20 | case TH_ERR_CATEGORY_SYSTEM: 21 | switch (TH_ERR_CODE(err)) { 22 | { 23 | case TH_ENOENT: 24 | return TH_ERR_HTTP(TH_CODE_NOT_FOUND); 25 | break; 26 | case TH_ETIMEDOUT: 27 | return TH_ERR_HTTP(TH_CODE_REQUEST_TIMEOUT); 28 | break; 29 | default: 30 | return TH_ERR_HTTP(TH_CODE_INTERNAL_SERVER_ERROR); 31 | break; 32 | } 33 | } 34 | break; 35 | case TH_ERR_CATEGORY_HTTP: 36 | return err; 37 | break; 38 | } 39 | return TH_ERR_HTTP(TH_CODE_INTERNAL_SERVER_ERROR); 40 | } 41 | 42 | TH_INLINE(const char*) 43 | th_http_strerror(int code) 44 | { 45 | switch (code) { 46 | case TH_CODE_OK: 47 | return "OK"; 48 | break; 49 | case TH_CODE_MOVED_PERMANENTLY: 50 | return "Moved Permanently"; 51 | break; 52 | case TH_CODE_BAD_REQUEST: 53 | return "Bad Request"; 54 | break; 55 | case TH_CODE_NOT_FOUND: 56 | return "Not Found"; 57 | break; 58 | case TH_CODE_METHOD_NOT_ALLOWED: 59 | return "Method Not Allowed"; 60 | break; 61 | case TH_CODE_PAYLOAD_TOO_LARGE: 62 | return "Payload Too Large"; 63 | break; 64 | case TH_CODE_INTERNAL_SERVER_ERROR: 65 | return "Internal Server Error"; 66 | break; 67 | case TH_CODE_SERVICE_UNAVAILABLE: 68 | return "Service Unavailable"; 69 | break; 70 | case TH_CODE_NOT_IMPLEMENTED: 71 | return "Method Not Implemented"; 72 | break; 73 | case TH_CODE_REQUEST_TIMEOUT: 74 | return "Request Timeout"; 75 | break; 76 | case TH_CODE_TOO_MANY_REQUESTS: 77 | return "Too Many Requests"; 78 | break; 79 | case TH_CODE_URI_TOO_LONG: 80 | return "URI Too Long"; 81 | break; 82 | case TH_CODE_UNSUPPORTED_MEDIA_TYPE: 83 | return "Unsupported Media Type"; 84 | break; 85 | case TH_CODE_RANGE_NOT_SATISFIABLE: 86 | return "Range Not Satisfiable"; 87 | break; 88 | case TH_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE: 89 | return "Request Header Fields Too Large"; 90 | break; 91 | case TH_CODE_UNAUTHORIZED: 92 | return "Unauthorized"; 93 | break; 94 | case TH_CODE_FORBIDDEN: 95 | return "Forbidden"; 96 | break; 97 | default: 98 | return "Unknown"; 99 | break; 100 | } 101 | } 102 | 103 | typedef enum th_http_code_type { 104 | TH_HTTP_CODE_TYPE_INFORMATIONAL, 105 | TH_HTTP_CODE_TYPE_SUCCESS, 106 | TH_HTTP_CODE_TYPE_REDIRECT, 107 | TH_HTTP_CODE_TYPE_CLIENT_ERROR, 108 | TH_HTTP_CODE_TYPE_SERVER_ERROR, 109 | } th_http_code_type; 110 | 111 | TH_INLINE(th_http_code_type) 112 | th_http_code_get_type(int code) 113 | { 114 | if (code >= 100 && code < 200) 115 | return TH_HTTP_CODE_TYPE_INFORMATIONAL; 116 | if (code >= 200 && code < 300) 117 | return TH_HTTP_CODE_TYPE_SUCCESS; 118 | if (code >= 300 && code < 400) 119 | return TH_HTTP_CODE_TYPE_REDIRECT; 120 | if (code >= 400 && code < 500) 121 | return TH_HTTP_CODE_TYPE_CLIENT_ERROR; 122 | if (code >= 500 && code < 600) 123 | return TH_HTTP_CODE_TYPE_SERVER_ERROR; 124 | return TH_HTTP_CODE_TYPE_SERVER_ERROR; 125 | } 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/th_io_composite.c: -------------------------------------------------------------------------------- 1 | #include "th_io_composite.h" 2 | 3 | TH_PRIVATE(void) 4 | th_io_composite_unref(void* self) 5 | { 6 | th_io_composite* composite = self; 7 | TH_ASSERT(composite->refcount > 0 && "Invalid refcount"); 8 | if (--composite->refcount == 0) { 9 | if (composite->on_complete) 10 | th_io_handler_destroy(TH_MOVE_PTR(composite->on_complete)); 11 | composite->destroy(composite); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/th_io_composite.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_COMPOSITE_H 2 | #define TH_IO_COMPOSITE_H 3 | 4 | #include "th_io_task.h" 5 | #include "th_utility.h" 6 | 7 | /** th_io_composite 8 | *@brief I/O composite task, inherits from th_io_handler. 9 | * and contains a pointer to another I/O handler that will be called 10 | * when the composite task is completed. 11 | */ 12 | typedef struct th_io_composite { 13 | th_io_handler base; 14 | th_io_handler* on_complete; 15 | void (*destroy)(void* self); 16 | unsigned int refcount; 17 | } th_io_composite; 18 | 19 | TH_PRIVATE(void) 20 | th_io_composite_unref(void* self); 21 | 22 | TH_INLINE(void) 23 | th_io_composite_init(th_io_composite* composite, void (*fn)(void* self, size_t result, th_err err), void (*destroy)(void* self), th_io_handler* on_complete) 24 | { 25 | th_io_handler_init(&composite->base, fn, th_io_composite_unref); 26 | composite->destroy = destroy; 27 | composite->on_complete = on_complete; 28 | composite->refcount = 1; 29 | } 30 | 31 | static inline void 32 | th_io_composite_complete(th_io_composite* composite, size_t result, th_err err) 33 | { 34 | th_io_handler_complete(composite->on_complete, result, err); 35 | } 36 | 37 | TH_INLINE(th_io_composite*) 38 | th_io_composite_ref(th_io_composite* composite) 39 | { 40 | ++composite->refcount; 41 | return composite; 42 | } 43 | 44 | typedef enum th_io_composite_forward_type { 45 | TH_IO_COMPOSITE_FORWARD_MOVE, 46 | TH_IO_COMPOSITE_FORWARD_COPY 47 | } th_io_composite_forward_type; 48 | 49 | TH_INLINE(th_io_composite*) 50 | th_io_composite_forward(th_io_composite* composite, th_io_composite_forward_type type) TH_MAYBE_UNUSED; 51 | 52 | TH_INLINE(th_io_composite*) 53 | th_io_composite_forward(th_io_composite* composite, th_io_composite_forward_type type) 54 | { 55 | switch (type) { 56 | case TH_IO_COMPOSITE_FORWARD_MOVE: 57 | return composite; 58 | case TH_IO_COMPOSITE_FORWARD_COPY: 59 | return th_io_composite_ref(composite); 60 | break; 61 | default: 62 | return NULL; 63 | break; 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/th_io_op.c: -------------------------------------------------------------------------------- 1 | #include "th_io_op.h" 2 | 3 | #include "th_io_op_bsd.h" 4 | #include "th_io_op_linux.h" 5 | #include "th_io_op_mock.h" 6 | #include "th_io_op_posix.h" 7 | #include "th_io_task.h" 8 | 9 | TH_PRIVATE(th_err) 10 | th_io_op_read(void* self, size_t* result) 11 | { 12 | #if defined(TH_CONFIG_OS_MOCK) 13 | return th_io_op_mock_read(self, result); 14 | #elif defined(TH_CONFIG_OS_POSIX) 15 | return th_io_op_posix_read(self, result); 16 | #endif 17 | } 18 | 19 | TH_PRIVATE(th_err) 20 | th_io_op_readv(void* self, size_t* result) 21 | { 22 | #if defined(TH_CONFIG_OS_MOCK) 23 | return th_io_op_mock_readv(self, result); 24 | #elif defined(TH_CONFIG_OS_POSIX) 25 | return th_io_op_posix_readv(self, result); 26 | #endif 27 | } 28 | 29 | TH_PRIVATE(th_err) 30 | th_io_op_write(void* self, size_t* result) 31 | { 32 | #if defined(TH_CONFIG_OS_MOCK) 33 | return th_io_op_mock_write(self, result); 34 | #elif defined(TH_CONFIG_OS_POSIX) 35 | return th_io_op_posix_write(self, result); 36 | #endif 37 | } 38 | 39 | TH_PRIVATE(th_err) 40 | th_io_op_writev(void* self, size_t* result) 41 | { 42 | #if defined(TH_CONFIG_OS_MOCK) 43 | return th_io_op_mock_writev(self, result); 44 | #elif defined(TH_CONFIG_OS_POSIX) 45 | return th_io_op_posix_writev(self, result); 46 | #endif 47 | } 48 | 49 | TH_PRIVATE(th_err) 50 | th_io_op_send(void* self, size_t* result) 51 | { 52 | #if defined(TH_CONFIG_OS_MOCK) 53 | return th_io_op_mock_send(self, result); 54 | #elif defined(TH_CONFIG_OS_POSIX) 55 | return th_io_op_posix_send(self, result); 56 | #endif 57 | } 58 | 59 | TH_PRIVATE(th_err) 60 | th_io_op_sendv(void* self, size_t* result) 61 | { 62 | #if defined(TH_CONFIG_OS_MOCK) 63 | return th_io_op_mock_sendv(self, result); 64 | #elif defined(TH_CONFIG_OS_POSIX) 65 | return th_io_op_posix_sendv(self, result); 66 | #endif 67 | } 68 | 69 | TH_PRIVATE(th_err) 70 | th_io_op_accept(void* self, size_t* result) 71 | { 72 | #if defined(TH_CONFIG_OS_MOCK) 73 | return th_io_op_mock_accept(self, result); 74 | #elif defined(TH_CONFIG_OS_POSIX) 75 | return th_io_op_posix_accept(self, result); 76 | #endif 77 | } 78 | 79 | TH_PRIVATE(th_err) 80 | th_io_op_sendfile(void* self, size_t* result) 81 | { 82 | #if defined(TH_CONFIG_OS_MOCK) 83 | return th_io_op_mock_sendfile(self, result); 84 | #elif defined(TH_CONFIG_OS_POSIX) 85 | th_io_task* iot = self; 86 | if (iot->len2 < (8 * 1024)) { 87 | return th_io_op_posix_sendfile_buffered(self, result); 88 | } 89 | #if defined(TH_CONFIG_WITH_BSD_SENDFILE) 90 | return th_io_op_bsd_sendfile(self, result); 91 | #endif 92 | #if defined(TH_CONFIG_WITH_LINUX_SENDFILE) 93 | return th_io_op_linux_sendfile(self, result); 94 | #endif 95 | return th_io_op_posix_sendfile_mmap(self, result); 96 | #endif // TH_CONFIG_HAVE_SENDFILE 97 | } 98 | -------------------------------------------------------------------------------- /src/th_io_op.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_OP_H 2 | #define TH_IO_OP_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #include 9 | 10 | TH_PRIVATE(th_err) 11 | th_io_op_read(void* self, size_t* result) TH_MAYBE_UNUSED; 12 | 13 | TH_PRIVATE(th_err) 14 | th_io_op_readv(void* self, size_t* result) TH_MAYBE_UNUSED; 15 | 16 | TH_PRIVATE(th_err) 17 | th_io_op_write(void* self, size_t* result) TH_MAYBE_UNUSED; 18 | 19 | TH_PRIVATE(th_err) 20 | th_io_op_writev(void* self, size_t* result) TH_MAYBE_UNUSED; 21 | 22 | TH_PRIVATE(th_err) 23 | th_io_op_send(void* self, size_t* result) TH_MAYBE_UNUSED; 24 | 25 | TH_PRIVATE(th_err) 26 | th_io_op_sendv(void* self, size_t* result) TH_MAYBE_UNUSED; 27 | 28 | TH_PRIVATE(th_err) 29 | th_io_op_accept(void* self, size_t* result) TH_MAYBE_UNUSED; 30 | 31 | TH_PRIVATE(th_err) 32 | th_io_op_sendfile(void* self, size_t* result) TH_MAYBE_UNUSED; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/th_io_op_bsd.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "th_io_op_bsd.h" 4 | 5 | #if defined(TH_CONFIG_WITH_BSD_SENDFILE) 6 | #include "th_file.h" 7 | #include "th_io_op_posix.h" 8 | #include "th_io_task.h" 9 | #include "th_system_error.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | TH_PRIVATE(th_err) 17 | th_io_op_bsd_sendfile(void* self, size_t* result) 18 | { 19 | th_io_task* iot = self; 20 | th_iov* iov = iot->addr; 21 | off_t len = (off_t)iot->len2; 22 | int ret = 0; 23 | if (iot->len == 0) { 24 | ret = sendfile(((th_file*)iot->addr2)->fd, iot->fd, iot->offset, &len, NULL, 0); 25 | } else { 26 | struct sf_hdtr hdtr = {.headers = (struct iovec*)iov, .hdr_cnt = iot->len, .trailers = NULL, .trl_cnt = 0}; 27 | ret = sendfile(((th_file*)iot->addr2)->fd, iot->fd, iot->offset, &len, &hdtr, 0); 28 | } 29 | th_err err = TH_ERR_OK; 30 | if (ret < 0 && len == 0) { 31 | int errc = errno; 32 | if (errc != TH_EAGAIN 33 | && errc != TH_EBUSY) { 34 | err = TH_ERR_SYSTEM(errc); 35 | } 36 | } 37 | *result = (size_t)len; 38 | return err; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/th_io_op_bsd.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_OP_BSD_H 2 | #define IO_OP_BSD_H 3 | 4 | #include "th_config.h" 5 | 6 | #if defined(TH_CONFIG_WITH_BSD_SENDFILE) 7 | TH_PRIVATE(th_err) 8 | th_io_op_bsd_sendfile(void* self, size_t* result) TH_MAYBE_UNUSED; 9 | #endif 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/th_io_op_linux.c: -------------------------------------------------------------------------------- 1 | #include "th_io_op_linux.h" 2 | 3 | #if defined(TH_CONFIG_WITH_LINUX_SENDFILE) 4 | TH_PRIVATE(th_err) 5 | th_io_op_linux_sendfile(void* self, size_t* result) 6 | { 7 | (void)self; 8 | (void)result; 9 | return TH_ERR_NOSUPPORT; 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /src/th_io_op_linux.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_OP_LINUX_H 2 | #define TH_IO_OP_LINUX_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #if defined(TH_CONFIG_WITH_LINUX_SENDFILE) 9 | TH_PRIVATE(th_err) 10 | th_io_op_linux_sendfile(void* self, size_t* result) TH_MAYBE_UNUSED; 11 | #endif 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/th_io_op_mock.c: -------------------------------------------------------------------------------- 1 | #include "th_io_op_mock.h" 2 | 3 | #if defined(TH_CONFIG_OS_MOCK) 4 | 5 | #include "th_io_task.h" 6 | #include "th_mock_syscall.h" 7 | 8 | TH_PRIVATE(th_err) 9 | th_io_op_mock_read(void* self, size_t* result) 10 | { 11 | th_io_task* iot = self; 12 | int r = th_mock_read(iot->addr, iot->len); 13 | if (r < 0) 14 | return TH_ERR_SYSTEM(-r); 15 | else if (r == 0) 16 | return TH_ERR_EOF; 17 | *result = r; 18 | return TH_ERR_OK; 19 | } 20 | 21 | TH_PRIVATE(th_err) 22 | th_io_op_mock_readv(void* self, size_t* result) 23 | { 24 | th_err err = TH_ERR_OK; 25 | th_io_task* iot = self; 26 | th_iov* iov = iot->addr; 27 | *result = 0; 28 | for (size_t i = 0; i < iot->len; ++i) { 29 | int r = th_mock_read(iov[i].base, iov[i].len); 30 | if (r < 0) { 31 | if (*result == 0) 32 | err = TH_ERR_SYSTEM(-r); 33 | break; 34 | } else if (r == 0 && *result == 0) { 35 | err = TH_ERR_EOF; 36 | break; 37 | } 38 | *result += r; 39 | } 40 | return err; 41 | } 42 | 43 | TH_PRIVATE(th_err) 44 | th_io_op_mock_write(void* self, size_t* result) 45 | { 46 | th_io_task* iot = self; 47 | int r = th_mock_write(iot->len); 48 | if (r < 0) 49 | return TH_ERR_SYSTEM(-r); 50 | *result = (size_t)r; 51 | return TH_ERR_OK; 52 | } 53 | 54 | TH_PRIVATE(th_err) 55 | th_io_op_mock_writev(void* self, size_t* result) 56 | { 57 | th_err err = TH_ERR_OK; 58 | th_io_task* iot = self; 59 | th_iov* iov = iot->addr; 60 | *result = 0; 61 | for (size_t i = 0; i < iot->len; ++i) { 62 | int r = th_mock_write(iov[i].len); 63 | if (r < 0) { 64 | if (*result == 0) 65 | err = TH_ERR_SYSTEM(-r); 66 | break; 67 | } 68 | *result += r; 69 | } 70 | return err; 71 | } 72 | 73 | TH_PRIVATE(th_err) 74 | th_io_op_mock_send(void* self, size_t* result) 75 | { 76 | return th_io_op_mock_write(self, result); 77 | } 78 | 79 | TH_PRIVATE(th_err) 80 | th_io_op_mock_sendv(void* self, size_t* result) 81 | { 82 | return th_io_op_mock_writev(self, result); 83 | } 84 | 85 | TH_PRIVATE(th_err) 86 | th_io_op_mock_accept(void* self, size_t* result) 87 | { 88 | (void)self; 89 | int r = th_mock_accept(); 90 | if (r < 0) 91 | return TH_ERR_SYSTEM(-r); 92 | *result = (size_t)r; 93 | return TH_ERR_OK; 94 | } 95 | 96 | TH_PRIVATE(th_err) 97 | th_io_op_mock_sendfile(void* self, size_t* result) 98 | { 99 | th_io_task* iot = self; 100 | size_t len = th_iov_bytes(iot->addr, iot->len); 101 | len += iot->len2; 102 | int r = th_mock_write(len); 103 | if (r < 0) 104 | return TH_ERR_SYSTEM(-r); 105 | *result = (size_t)r; 106 | return TH_ERR_OK; 107 | } 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/th_io_op_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_OP_MOCK_H 2 | #define TH_IO_OP_MOCK_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #if defined(TH_CONFIG_OS_MOCK) 9 | 10 | TH_PRIVATE(th_err) 11 | th_io_op_mock_read(void* self, size_t* result); 12 | 13 | TH_PRIVATE(th_err) 14 | th_io_op_mock_readv(void* self, size_t* result); 15 | 16 | TH_PRIVATE(th_err) 17 | th_io_op_mock_write(void* self, size_t* result); 18 | 19 | TH_PRIVATE(th_err) 20 | th_io_op_mock_writev(void* self, size_t* result); 21 | 22 | TH_PRIVATE(th_err) 23 | th_io_op_mock_send(void* self, size_t* result); 24 | 25 | TH_PRIVATE(th_err) 26 | th_io_op_mock_sendv(void* self, size_t* result); 27 | 28 | TH_PRIVATE(th_err) 29 | th_io_op_mock_accept(void* self, size_t* result); 30 | 31 | TH_PRIVATE(th_err) 32 | th_io_op_mock_sendfile(void* self, size_t* result); 33 | 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/th_io_op_posix.c: -------------------------------------------------------------------------------- 1 | 2 | #include "th_io_op_posix.h" 3 | 4 | #if defined(TH_CONFIG_OS_POSIX) 5 | 6 | #include "th_align.h" 7 | #include "th_io_task.h" 8 | #include "th_log.h" 9 | #include "th_utility.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | TH_PRIVATE(th_err) 21 | th_io_op_posix_read(void* self, size_t* result) 22 | { 23 | th_err err = TH_ERR_OK; 24 | th_io_task* iot = self; 25 | ssize_t ret = read(iot->fd, iot->addr, iot->len); 26 | if (ret < 0) 27 | err = TH_ERR_SYSTEM(errno); 28 | else if (ret == 0) 29 | err = TH_ERR_EOF; 30 | (*result) = (size_t)ret; 31 | return err; 32 | } 33 | 34 | TH_PRIVATE(th_err) 35 | th_io_op_posix_readv(void* self, size_t* result) 36 | { 37 | th_err err = TH_ERR_OK; 38 | th_io_task* iot = self; 39 | th_iov* iov = iot->addr; 40 | ssize_t ret = readv(iot->fd, (struct iovec*)iov, iot->len); 41 | if (ret < 0) 42 | err = TH_ERR_SYSTEM(errno); 43 | else if (ret == 0) 44 | err = TH_ERR_EOF; 45 | (*result) = (size_t)ret; 46 | return err; 47 | } 48 | 49 | TH_PRIVATE(th_err) 50 | th_io_op_posix_write(void* self, size_t* result) 51 | { 52 | th_err err = TH_ERR_OK; 53 | th_io_task* iot = self; 54 | ssize_t ret = write(iot->fd, iot->addr, iot->len); 55 | if (ret < 0) 56 | err = TH_ERR_SYSTEM(errno); 57 | (*result) = (size_t)ret; 58 | return err; 59 | } 60 | 61 | TH_PRIVATE(th_err) 62 | th_io_op_posix_writev(void* self, size_t* result) 63 | { 64 | th_err err = TH_ERR_OK; 65 | th_io_task* iot = self; 66 | th_iov* iov = iot->addr; 67 | ssize_t ret = writev(iot->fd, (struct iovec*)iov, iot->len); 68 | if (ret < 0) 69 | err = TH_ERR_SYSTEM(errno); 70 | (*result) = (size_t)ret; 71 | return err; 72 | } 73 | 74 | TH_PRIVATE(th_err) 75 | th_io_op_posix_send(void* self, size_t* result) 76 | { 77 | th_err err = TH_ERR_OK; 78 | th_io_task* iot = self; 79 | int flags = 0; 80 | #if defined(MSG_NOSIGNAL) 81 | flags |= MSG_NOSIGNAL; 82 | #endif 83 | ssize_t ret = send(iot->fd, iot->addr, iot->len, flags); 84 | if (ret < 0) 85 | err = TH_ERR_SYSTEM(errno); 86 | (*result) = (size_t)ret; 87 | return err; 88 | } 89 | 90 | TH_PRIVATE(th_err) 91 | th_io_op_posix_sendv(void* self, size_t* result) 92 | { 93 | th_err err = TH_ERR_OK; 94 | th_io_task* iot = self; 95 | int flags = 0; 96 | #if defined(MSG_NOSIGNAL) 97 | flags |= MSG_NOSIGNAL; 98 | #endif 99 | struct msghdr msg = {0}; 100 | msg.msg_iov = iot->addr; 101 | msg.msg_iovlen = iot->len; 102 | ssize_t ret = sendmsg(iot->fd, &msg, flags); 103 | if (ret < 0) 104 | err = TH_ERR_SYSTEM(errno); 105 | (*result) = (size_t)ret; 106 | return err; 107 | } 108 | 109 | TH_PRIVATE(th_err) 110 | th_io_op_posix_accept(void* self, size_t* result) 111 | { 112 | th_err err = TH_ERR_OK; 113 | th_io_task* iot = self; 114 | int ret = accept(iot->fd, iot->addr, iot->addr2); 115 | if (ret < 0) 116 | err = TH_ERR_SYSTEM(errno); 117 | (*result) = (size_t)ret; 118 | return err; 119 | } 120 | 121 | TH_PRIVATE(th_err) 122 | th_io_op_posix_sendfile_mmap(void* self, size_t* result) 123 | { 124 | th_err err = TH_ERR_OK; 125 | th_io_task* iot = self; 126 | th_file* file = iot->addr2; 127 | th_fileview view; 128 | if ((err = th_file_get_view(file, &view, iot->offset, iot->len2)) != TH_ERR_OK) 129 | return err; 130 | struct iovec vec[64]; 131 | size_t veclen = 0; 132 | if (iot->len > 0) { 133 | th_iov* iov = iot->addr; 134 | for (size_t i = 0; i < iot->len; i++) { 135 | vec[i].iov_base = iov[i].base; 136 | vec[i].iov_len = iov[i].len; 137 | veclen++; 138 | } 139 | } 140 | vec[veclen].iov_base = view.ptr; 141 | vec[veclen].iov_len = view.len; 142 | veclen++; 143 | int flags = 0; 144 | #if defined(MSG_NOSIGNAL) 145 | flags |= MSG_NOSIGNAL; 146 | #endif 147 | struct msghdr msg = {0}; 148 | msg.msg_iov = vec; 149 | msg.msg_iovlen = veclen; 150 | ssize_t ret = sendmsg(iot->fd, &msg, flags); 151 | if (ret < 0) 152 | err = TH_ERR_SYSTEM(errno); 153 | *result = ret; 154 | return err; 155 | } 156 | 157 | #define TH_IO_OP_POSIX_SENDFILE_BUFFERED_MAX 8 * 1024 158 | TH_PRIVATE(th_err) 159 | th_io_op_posix_sendfile_buffered(void* self, size_t* result) 160 | { 161 | uint8_t buffer[TH_IO_OP_POSIX_SENDFILE_BUFFERED_MAX]; 162 | th_io_task* iot = self; 163 | struct iovec vec[64]; 164 | size_t veclen = 0; 165 | if (iot->len > 0) { 166 | th_iov* iov = iot->addr; 167 | for (size_t i = 0; i < iot->len; i++) { 168 | vec[i].iov_base = iov[i].base; 169 | vec[i].iov_len = iov[i].len; 170 | veclen++; 171 | } 172 | } 173 | size_t toread = TH_MIN(sizeof(buffer), iot->len2); 174 | ssize_t readlen = pread(((th_file*)iot->addr2)->fd, buffer, toread, iot->offset); 175 | if (readlen < 0) 176 | return TH_ERR_SYSTEM(errno); 177 | int flags = 0; 178 | #if defined(MSG_NOSIGNAL) 179 | flags |= MSG_NOSIGNAL; 180 | #endif 181 | vec[veclen].iov_base = buffer; 182 | vec[veclen].iov_len = (size_t)readlen; 183 | veclen++; 184 | struct msghdr msg = {0}; 185 | msg.msg_iov = vec; 186 | msg.msg_iovlen = veclen; 187 | ssize_t writelen = sendmsg(iot->fd, &msg, flags); 188 | if (writelen < 0) 189 | return TH_ERR_SYSTEM(errno); 190 | *result = (size_t)writelen; 191 | return TH_ERR_OK; 192 | } 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /src/th_io_op_posix.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_OP_UNIX_H 2 | #define IO_OP_UNIX_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #if defined(TH_CONFIG_OS_POSIX) 9 | 10 | TH_PRIVATE(th_err) 11 | th_io_op_posix_read(void* self, size_t* result); 12 | 13 | TH_PRIVATE(th_err) 14 | th_io_op_posix_readv(void* self, size_t* result); 15 | 16 | TH_PRIVATE(th_err) 17 | th_io_op_posix_write(void* self, size_t* result); 18 | 19 | TH_PRIVATE(th_err) 20 | th_io_op_posix_writev(void* self, size_t* result); 21 | 22 | TH_PRIVATE(th_err) 23 | th_io_op_posix_send(void* self, size_t* result); 24 | 25 | TH_PRIVATE(th_err) 26 | th_io_op_posix_sendv(void* self, size_t* result); 27 | 28 | TH_PRIVATE(th_err) 29 | th_io_op_posix_accept(void* self, size_t* result); 30 | 31 | TH_PRIVATE(th_err) 32 | th_io_op_posix_sendfile_mmap(void* self, size_t* result) TH_MAYBE_UNUSED; 33 | 34 | TH_PRIVATE(th_err) 35 | th_io_op_posix_sendfile_buffered(void* self, size_t* result) TH_MAYBE_UNUSED; 36 | 37 | #endif 38 | #endif 39 | -------------------------------------------------------------------------------- /src/th_io_service.c: -------------------------------------------------------------------------------- 1 | #include "th_io_service.h" 2 | -------------------------------------------------------------------------------- /src/th_io_service.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_SERVICE_H 2 | #define TH_IO_SERVICE_H 3 | 4 | #include 5 | 6 | #include "th_io_task.h" 7 | #include "th_task.h" 8 | #include "th_utility.h" 9 | 10 | typedef struct th_io_handle { 11 | void (*cancel)(void* self); 12 | void (*submit)(void* self, th_io_task* task); 13 | void (*enable_timeout)(void* self, bool enabled); 14 | int (*get_fd)(void* self); 15 | void (*destroy)(void* self); 16 | } th_io_handle; 17 | 18 | TH_INLINE(void) 19 | th_io_handle_cancel(th_io_handle* io_handle) 20 | { 21 | io_handle->cancel(io_handle); 22 | } 23 | 24 | TH_INLINE(void) 25 | th_io_handle_submit(th_io_handle* io_handle, th_io_task* iot) 26 | { 27 | io_handle->submit(io_handle, iot); 28 | } 29 | 30 | TH_INLINE(int) 31 | th_io_handle_get_fd(th_io_handle* io_handle) 32 | { 33 | return io_handle->get_fd(io_handle); 34 | } 35 | 36 | TH_INLINE(void) 37 | th_io_handle_enable_timeout(th_io_handle* io_handle, bool enabled) 38 | { 39 | io_handle->enable_timeout(io_handle, enabled); 40 | } 41 | 42 | TH_INLINE(void) 43 | th_io_handle_destroy(th_io_handle* io_handle) 44 | { 45 | io_handle->destroy(io_handle); 46 | } 47 | 48 | typedef struct th_io_service { 49 | void (*run)(void* self, int timeout_ms); 50 | th_err (*create_handle)(void* self, th_io_handle** out, int fd); 51 | void (*destroy)(void* self); 52 | } th_io_service; 53 | 54 | TH_INLINE(void) 55 | th_io_service_run(th_io_service* io_service, int timeout_ms) 56 | { 57 | io_service->run(io_service, timeout_ms); 58 | } 59 | 60 | TH_INLINE(th_err) 61 | th_io_service_create_handle(th_io_service* io_service, th_io_handle** out, int fd) 62 | { 63 | return io_service->create_handle(io_service, out, fd); 64 | } 65 | 66 | TH_INLINE(void) 67 | th_io_service_destroy(th_io_service* io_service) 68 | { 69 | if (io_service->destroy) 70 | io_service->destroy(io_service); 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/th_io_task.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IO_TASK_H 2 | #define TH_IO_TASK_H 3 | 4 | #include 5 | 6 | #include "th_allocator.h" 7 | #include "th_file.h" 8 | #include "th_iov.h" 9 | #include "th_task.h" 10 | 11 | /** th_io_handler 12 | *@brief I/O operation completion handler, inherits from th_task. 13 | * and contains the result of the operation. 14 | */ 15 | typedef struct th_io_handler { 16 | th_task base; 17 | void (*fn)(void* self, size_t result, th_err err); 18 | size_t result; 19 | th_err err; 20 | } th_io_handler; 21 | 22 | TH_PRIVATE(void) 23 | th_io_handler_fn(void* self); 24 | 25 | TH_INLINE(void) 26 | th_io_handler_init(th_io_handler* handler, void (*fn)(void* self, size_t result, th_err err), void (*destroy)(void* self)) 27 | { 28 | th_task_init(&handler->base, th_io_handler_fn, destroy); 29 | handler->fn = fn; 30 | } 31 | 32 | TH_INLINE(void) 33 | th_io_handler_set_result(th_io_handler* handler, size_t result, th_err err) 34 | { 35 | handler->result = result; 36 | handler->err = err; 37 | } 38 | 39 | TH_INLINE(void) 40 | th_io_handler_complete(th_io_handler* handler, size_t result, th_err err) 41 | { 42 | th_io_handler_set_result(handler, result, err); 43 | th_task_complete(&handler->base); 44 | } 45 | 46 | TH_INLINE(void) 47 | th_io_handler_destroy(th_io_handler* handler) 48 | { 49 | th_task_destroy(&handler->base); 50 | } 51 | 52 | // some aliases 53 | 54 | typedef th_io_handler th_write_handler; 55 | typedef th_io_handler th_read_handler; 56 | #define th_write_handler_init th_io_handler_init 57 | #define th_read_handler_init th_io_handler_init 58 | #define th_write_handler_complete th_io_handler_complete 59 | #define th_read_handler_complete th_io_handler_complete 60 | 61 | typedef enum th_io_open_flag { 62 | TH_IO_OPEN_FLAG_RDONLY = 1 << 0, 63 | TH_IO_OPEN_FLAG_DIR = 1 << 1, 64 | } th_io_open_flag; 65 | 66 | /** th_io_op 67 | *@brief I/O operation type. 68 | */ 69 | typedef enum th_io_op_type { 70 | TH_IO_OP_TYPE_NONE = 0, 71 | TH_IO_OP_TYPE_READ = 1, 72 | TH_IO_OP_TYPE_WRITE = 2, 73 | TH_IO_OP_TYPE_MAX = TH_IO_OP_TYPE_WRITE 74 | } th_io_op_type; 75 | #define TH_IO_OP(opc, type) ((opc) | ((type) << 8)) 76 | #define TH_IO_OP_TYPE(op) ((op) >> 8) 77 | typedef enum th_io_op { 78 | TH_IO_OP_ACCEPT = TH_IO_OP(0, TH_IO_OP_TYPE_READ), 79 | TH_IO_OP_READ = TH_IO_OP(1, TH_IO_OP_TYPE_READ), 80 | TH_IO_OP_WRITE = TH_IO_OP(2, TH_IO_OP_TYPE_WRITE), 81 | TH_IO_OP_WRITEV = TH_IO_OP(3, TH_IO_OP_TYPE_WRITE), 82 | TH_IO_OP_SEND = TH_IO_OP(4, TH_IO_OP_TYPE_WRITE), 83 | TH_IO_OP_SENDV = TH_IO_OP(5, TH_IO_OP_TYPE_WRITE), 84 | TH_IO_OP_READV = TH_IO_OP(6, TH_IO_OP_TYPE_READ), 85 | TH_IO_OP_OPENAT = TH_IO_OP(7, TH_IO_OP_TYPE_NONE), 86 | TH_IO_OP_OPEN = TH_IO_OP(8, TH_IO_OP_TYPE_NONE), 87 | TH_IO_OP_CLOSE = TH_IO_OP(9, TH_IO_OP_TYPE_NONE), 88 | TH_IO_OP_SENDFILE = TH_IO_OP(10, TH_IO_OP_TYPE_WRITE), 89 | } th_io_op; 90 | 91 | /** th_io_task 92 | *@brief I/O task, inherits from th_task. 93 | * Contains the I/O operation type and the I/O operation arguments. 94 | */ 95 | typedef struct th_io_task { 96 | th_task base; 97 | th_allocator* allocator; 98 | th_err (*fn)(void* self, size_t* result); 99 | th_io_handler* on_complete; 100 | void* addr; 101 | void* addr2; 102 | size_t len; 103 | size_t len2; 104 | size_t offset; 105 | unsigned int flags; 106 | int fd; 107 | enum th_io_op op; 108 | } th_io_task; 109 | 110 | TH_PRIVATE(th_io_task*) 111 | th_io_task_create(th_allocator* allocator); 112 | 113 | /* 114 | TH_PRIVATE(void) 115 | th_io_task_to_string(char* buf, size_t len, th_io_task* iot); 116 | */ 117 | 118 | TH_PRIVATE(void) 119 | th_io_task_prepare_read(th_io_task* iot, int fd, void* addr, size_t len, th_io_handler* on_complete); 120 | 121 | /* 122 | TH_PRIVATE(void) 123 | th_io_task_prepare_write(th_io_task* iot, int fd, void* addr, size_t len, th_io_handler* on_complete); 124 | 125 | TH_PRIVATE(void) 126 | th_io_task_prepare_writev(th_io_task* iot, int fd, th_iov* iov, size_t len, th_io_handler* on_complete); 127 | */ 128 | 129 | TH_PRIVATE(void) 130 | th_io_task_prepare_send(th_io_task* iot, int fd, void* addr, size_t len, th_io_handler* on_complete); 131 | 132 | TH_PRIVATE(void) 133 | th_io_task_prepare_sendv(th_io_task* iot, int fd, th_iov* iov, size_t len, th_io_handler* on_complete); 134 | 135 | TH_PRIVATE(void) 136 | th_io_task_prepare_readv(th_io_task* iot, int fd, th_iov* iov, size_t len, th_io_handler* on_complete); 137 | 138 | TH_PRIVATE(void) 139 | th_io_task_prepare_sendfile(th_io_task* iot, th_file* file, int sfd, th_iov* header, size_t iovcnt, 140 | size_t offset, size_t len, th_io_handler* on_complete); 141 | 142 | TH_PRIVATE(void) 143 | th_io_task_prepare_accept(th_io_task* iot, int fd, void* addr, void* addrlen, th_io_handler* on_complete); 144 | 145 | /** th_io_task_execute 146 | * @brief Executes the I/O task and leaves the completion handler untouched. 147 | * @param iot I/O task. 148 | * @param result Result of the I/O operation. 149 | * @return Error code. 150 | */ 151 | TH_PRIVATE(th_err) 152 | th_io_task_execute(th_io_task* iot, size_t* result); 153 | 154 | /** th_io_task_try_execute 155 | * @brief Tries to execute the I/O task and returns the completion handler 156 | * if the I/O operation was completed. 157 | * @param iot I/O task. 158 | * @return Completion handler. 159 | */ 160 | TH_PRIVATE(th_io_handler*) 161 | th_io_task_try_execute(th_io_task* iot); 162 | 163 | TH_PRIVATE(void) 164 | th_io_task_destroy(th_io_task* iot); 165 | 166 | /** th_io_task_abort 167 | * @brief Aborts the I/O task. Sets the error code and returns the completion handler. 168 | * @param iot I/O task. 169 | * @param err Error code. 170 | */ 171 | TH_PRIVATE(th_io_handler*) 172 | th_io_task_abort(th_io_task* iot, th_err err); 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /src/th_io_task_test.c: -------------------------------------------------------------------------------- 1 | #include "th_io_task.h" 2 | #include "th_mock_service.h" 3 | #include "th_test.h" 4 | 5 | TH_TEST_BEGIN(io_task) 6 | { 7 | TH_TEST_CASE_BEGIN(io_task_create_and_destroy) 8 | { 9 | th_io_task* iot = th_io_task_create(th_default_allocator_get()); 10 | TH_EXPECT(iot != NULL); 11 | TH_EXPECT(iot->base.destroy != NULL); 12 | th_io_task_destroy(iot); 13 | } 14 | TH_TEST_CASE_END 15 | } 16 | TH_TEST_END 17 | -------------------------------------------------------------------------------- /src/th_iov.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_IOV_H 2 | #define TH_IOV_H 3 | 4 | #include 5 | #include 6 | 7 | #include "th_config.h" 8 | 9 | /** th_iov 10 | *@brief I/O vector. 11 | */ 12 | 13 | typedef struct th_iov { 14 | void* base; 15 | size_t len; 16 | } th_iov; 17 | 18 | /** th_iov_consume 19 | *@brief Consume the I/O vector and 20 | * return the number of bytes that were not consumed. 21 | */ 22 | TH_INLINE(size_t) 23 | th_iov_consume(th_iov** iov, size_t* iov_len, size_t consume) 24 | { 25 | size_t zeroed = 0; 26 | for (size_t i = 0; i < *iov_len; i++) { 27 | if (consume < (*iov)[i].len) { 28 | (*iov)[i].base = (char*)(*iov)[i].base + consume; 29 | (*iov)[i].len -= consume; 30 | consume = 0; 31 | break; 32 | } 33 | consume -= (*iov)[i].len; 34 | (*iov)[i].len = 0; 35 | zeroed++; 36 | } 37 | *iov_len -= zeroed; 38 | (*iov) += zeroed; 39 | return consume; 40 | } 41 | 42 | TH_INLINE(size_t) 43 | th_iov_bytes(th_iov* iov, size_t iov_len) 44 | { 45 | size_t bytes = 0; 46 | for (size_t i = 0; i < iov_len; i++) { 47 | bytes += iov[i].len; 48 | } 49 | return bytes; 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/th_kqueue_service.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_KQUEUE_SERVICE_H 2 | #define TH_KQUEUE_SERVICE_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #ifdef TH_CONFIG_WITH_KQUEUE 9 | #include "th_allocator.h" 10 | #include "th_hashmap.h" 11 | #include "th_io_service.h" 12 | #include "th_io_task.h" 13 | #include "th_list.h" 14 | #include "th_runner.h" 15 | #include "th_timer.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | /* Forward declarations begin */ 22 | 23 | typedef struct th_kqueue_service th_kqueue_service; 24 | typedef struct th_kqueue_handle th_kqueue_handle; 25 | typedef struct th_kqueue_handle_cleaner th_kqueue_handle_cleaner; 26 | 27 | /* Forward declarations end */ 28 | 29 | struct th_kqueue_handle { 30 | th_io_handle base; 31 | th_timer timer; 32 | th_allocator* allocator; 33 | th_kqueue_handle* pool_next; 34 | th_kqueue_handle* pool_prev; 35 | th_kqueue_handle* timer_next; 36 | th_kqueue_handle* timer_prev; 37 | th_kqueue_service* service; 38 | th_io_task* iot[TH_IO_OP_TYPE_MAX]; 39 | int fd; 40 | th_io_op_type active; 41 | bool timeout_enabled; 42 | }; 43 | 44 | #ifndef TH_KQUEUE_HANDLE_POOL 45 | #define TH_KQUEUE_HANDLE_POOL 46 | TH_DEFINE_OBJ_POOL_ALLOCATOR(th_kqueue_handle_pool, th_kqueue_handle, pool_prev, pool_next) 47 | #endif 48 | 49 | #ifndef TH_KQUEUE_HANDLE_TIMER_LIST 50 | #define TH_KQUEUE_HANDLE_TIMER_LIST 51 | TH_DEFINE_LIST(th_kqueue_timer_list, th_kqueue_handle, timer_prev, timer_next) 52 | #endif 53 | 54 | struct th_kqueue_service { 55 | th_io_service base; 56 | th_allocator* allocator; 57 | th_runner* runner; 58 | th_kqueue_handle_pool handle_allocator; 59 | th_kqueue_timer_list timer_list; 60 | int kq; 61 | }; 62 | 63 | TH_PRIVATE(th_err) 64 | th_kqueue_service_create(th_io_service** out, th_runner* runner, th_allocator* allocator); 65 | 66 | #endif /* TH_HAVE_KQUEUE */ 67 | #endif 68 | -------------------------------------------------------------------------------- /src/th_list_test.c: -------------------------------------------------------------------------------- 1 | #include "th_list.h" 2 | #include "th_test.h" 3 | 4 | #include 5 | 6 | typedef struct th_test_node { 7 | struct th_test_node* next; 8 | struct th_test_node* prev; 9 | } th_test_node; 10 | 11 | TH_DEFINE_LIST(th_test_list, th_test_node, prev, next) 12 | 13 | TH_TEST_BEGIN(list) 14 | { 15 | TH_TEST_CASE_BEGIN(hashmap_init) 16 | { 17 | th_test_list list = {0}; 18 | th_test_node* node = th_test_list_pop_front(&list); 19 | TH_EXPECT(node == NULL); 20 | } 21 | TH_TEST_CASE_END 22 | TH_TEST_CASE_BEGIN(list_push_back) 23 | { 24 | th_test_list list = {0}; 25 | th_test_node node1 = {0}; 26 | th_test_list_push_back(&list, &node1); 27 | th_test_node* node = th_test_list_pop_front(&list); 28 | TH_EXPECT(node == &node1); 29 | TH_EXPECT(list.head == NULL); 30 | TH_EXPECT(list.tail == NULL); 31 | } 32 | TH_TEST_CASE_END 33 | TH_TEST_CASE_BEGIN(list_push_back_multiple) 34 | { 35 | th_test_list list = {0}; 36 | th_test_node node1 = {0}; 37 | th_test_node node2 = {0}; 38 | th_test_list_push_back(&list, &node1); 39 | th_test_list_push_back(&list, &node2); 40 | th_test_node* node = th_test_list_pop_front(&list); 41 | TH_EXPECT(node == &node1); 42 | node = th_test_list_pop_front(&list); 43 | TH_EXPECT(node == &node2); 44 | TH_EXPECT(list.head == NULL); 45 | TH_EXPECT(list.tail == NULL); 46 | } 47 | TH_TEST_CASE_END 48 | TH_TEST_CASE_BEGIN(list_erase) 49 | { 50 | th_test_list list = {0}; 51 | th_test_node node1 = {0}; 52 | th_test_node node2 = {0}; 53 | th_test_list_push_back(&list, &node1); 54 | th_test_list_push_back(&list, &node2); 55 | th_test_list_erase(&list, &node1); 56 | th_test_node* node = th_test_list_pop_front(&list); 57 | TH_EXPECT(node == &node2); 58 | TH_EXPECT(list.head == NULL); 59 | TH_EXPECT(list.tail == NULL); 60 | } 61 | TH_TEST_CASE_END 62 | TH_TEST_CASE_BEGIN(list_erase_middle) 63 | { 64 | th_test_list list = {0}; 65 | th_test_node node1 = {0}; 66 | th_test_node node2 = {0}; 67 | th_test_node node3 = {0}; 68 | th_test_list_push_back(&list, &node1); 69 | th_test_list_push_back(&list, &node2); 70 | th_test_list_push_back(&list, &node3); 71 | th_test_list_erase(&list, &node2); 72 | th_test_node* node = th_test_list_pop_front(&list); 73 | TH_EXPECT(node == &node1); 74 | node = th_test_list_pop_front(&list); 75 | TH_EXPECT(node == &node3); 76 | TH_EXPECT(list.head == NULL); 77 | TH_EXPECT(list.tail == NULL); 78 | } 79 | TH_TEST_CASE_END 80 | TH_TEST_CASE_BEGIN(list_push_erase) 81 | { 82 | th_test_list list = {0}; 83 | th_test_node node1 = {0}; 84 | th_test_node node2 = {0}; 85 | th_test_node node3 = {0}; 86 | th_test_list_push_back(&list, &node1); 87 | th_test_list_push_back(&list, &node2); 88 | th_test_list_push_back(&list, &node3); 89 | th_test_list_erase(&list, &node3); 90 | th_test_list_erase(&list, &node1); 91 | th_test_list_erase(&list, &node2); 92 | th_test_node* node = th_test_list_pop_front(&list); 93 | TH_EXPECT(node == NULL); 94 | th_test_list_push_back(&list, &node1); 95 | node = th_test_list_pop_front(&list); 96 | TH_EXPECT(node == &node1); 97 | node = th_test_list_pop_front(&list); 98 | TH_EXPECT(node == NULL); 99 | } 100 | TH_TEST_CASE_END 101 | } 102 | TH_TEST_END 103 | -------------------------------------------------------------------------------- /src/th_listener.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_LISTENER_H 2 | #define TH_LISTENER_H 3 | 4 | #include "th_acceptor.h" 5 | #include "th_conn.h" 6 | #include "th_conn_tracker.h" 7 | #include "th_fcache.h" 8 | #include "th_heap_string.h" 9 | #include "th_http.h" 10 | #include "th_io_service.h" 11 | #include "th_router.h" 12 | #include "th_socket.h" 13 | 14 | typedef struct th_listener th_listener; 15 | 16 | typedef struct th_listener_accept_handler { 17 | th_io_handler base; 18 | th_listener* listener; 19 | } th_listener_accept_handler; 20 | 21 | typedef struct th_listener_conn_destroy_handler { 22 | th_task base; 23 | th_listener* listener; 24 | } th_listener_conn_destroy_handler; 25 | 26 | struct th_listener { 27 | th_acceptor acceptor; 28 | th_listener* next; 29 | th_context* context; 30 | 31 | /** The conn that will be used to handle the incoming connections. */ 32 | th_conn* conn; 33 | 34 | /** Used to keep track of all the clients that are currently active. */ 35 | th_conn_tracker conn_tracker; 36 | 37 | /** Used to react to the destruction of a client. */ 38 | th_listener_conn_destroy_handler client_destroy_handler; 39 | 40 | th_http_upgrader upgrader; 41 | 42 | #if TH_WITH_SSL 43 | /** Ssl context that will be used to create the ssl socket. */ 44 | th_ssl_context ssl_context; 45 | #endif /* TH_WITH_SSL */ 46 | 47 | /** Flag that indicates if ssl is enabled. */ 48 | bool ssl_enabled; 49 | 50 | /** The accept handler that will be used to handle the completion 51 | * of the accept operation. 52 | */ 53 | th_listener_accept_handler accept_handler; 54 | 55 | /** As long as the listener keeps accepting new connections, 56 | * this flag will be set to 1. 57 | */ 58 | bool running; 59 | th_allocator* allocator; 60 | }; 61 | 62 | TH_PRIVATE(th_err) 63 | th_listener_create(th_listener** out, th_context* context, 64 | const char* host, const char* port, 65 | th_router* router, th_fcache* fcache, 66 | th_bind_opt* opt, th_allocator* allocator); 67 | 68 | TH_PRIVATE(th_err) 69 | th_listener_start(th_listener* listener); 70 | 71 | TH_PRIVATE(void) 72 | th_listener_stop(th_listener* listener); 73 | 74 | TH_PRIVATE(void) 75 | th_listener_destroy(th_listener* listener); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/th_log.c: -------------------------------------------------------------------------------- 1 | #include "th_log.h" 2 | 3 | #include 4 | 5 | /* global log instance */ 6 | 7 | static th_log* th_user_log = NULL; 8 | 9 | TH_PUBLIC(void) 10 | th_log_set(th_log* log) 11 | { 12 | th_user_log = log; 13 | } 14 | 15 | /** th_log_get 16 | * @brief Get the current user log instance. 17 | * @return The current user log instance, or the default log instance if no user log is set. 18 | */ 19 | TH_LOCAL(th_log*) 20 | th_log_get(void) 21 | { 22 | return th_user_log ? th_user_log : th_default_log_get(); 23 | } 24 | 25 | /* th_default_log implementation begin */ 26 | 27 | /** th_default_log 28 | * @brief Default log implementation, simply prints log messages to stderr. 29 | */ 30 | typedef struct th_default_log { 31 | th_log base; 32 | } th_default_log; 33 | 34 | TH_LOCAL(void) 35 | th_default_log_print(void* self, int level, const char* msg) 36 | { 37 | (void)self; 38 | (void)level; 39 | fprintf(stderr, "%s\n", msg); 40 | } 41 | 42 | TH_PRIVATE(th_log*) 43 | th_default_log_get(void) 44 | { 45 | static th_default_log log = { 46 | .base = { 47 | .print = th_default_log_print, 48 | }}; 49 | return (th_log*)&log; 50 | } 51 | 52 | TH_PRIVATE(void) 53 | th_log_printf(int level, const char* fmt, ...) 54 | { 55 | th_log* log = th_log_get(); 56 | char buffer[1024]; 57 | va_list args; 58 | va_start(args, fmt); 59 | int ret = vsnprintf(buffer, sizeof(buffer), fmt, args); 60 | va_end(args); 61 | if (ret < 0 || (size_t)ret >= sizeof(buffer)) 62 | goto on_error; 63 | log->print(log, level, buffer); 64 | return; 65 | on_error: 66 | log->print(log, TH_LOG_LEVEL_ERROR, "ERROR: [th_log] Failed to format log message"); 67 | } 68 | 69 | /* th_default_log implementation end */ 70 | -------------------------------------------------------------------------------- /src/th_log.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_LOGGER_H 2 | #define TH_LOGGER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "th_config.h" 10 | 11 | #ifndef TH_LOG_LEVEL 12 | #define TH_LOG_LEVEL TH_LOG_LEVEL_INFO 13 | #endif 14 | 15 | #define TH_LOG_TAG "default" 16 | 17 | TH_PRIVATE(th_log*) 18 | th_default_log_get(void); 19 | 20 | TH_PRIVATE(void) 21 | th_log_printf(int level, const char* fmt, ...) TH_MAYBE_UNUSED; 22 | 23 | #if TH_LOG_LEVEL <= TH_LOG_LEVEL_TRACE 24 | #define TH_LOG_TRACE(...) th_log_printf(TH_LOG_LEVEL_TRACE, "TRACE: [" TH_LOG_TAG "] " __VA_ARGS__) 25 | #else 26 | #define TH_LOG_TRACE(...) ((void)0) 27 | #endif 28 | 29 | #if TH_LOG_LEVEL <= TH_LOG_LEVEL_DEBUG 30 | #define TH_LOG_DEBUG(...) th_log_printf(TH_LOG_LEVEL_DEBUG, "DEBUG: [" TH_LOG_TAG "] " __VA_ARGS__) 31 | #else 32 | #define TH_LOG_DEBUG(...) ((void)0) 33 | #endif 34 | 35 | #if (TH_LOG_LEVEL <= TH_LOG_LEVEL_INFO) 36 | #define TH_LOG_INFO(...) th_log_printf(TH_LOG_LEVEL_INFO, "INFO: [" TH_LOG_TAG "] " __VA_ARGS__) 37 | #else 38 | #define TH_LOG_INFO(...) ((void)0) 39 | #endif 40 | 41 | #if TH_LOG_LEVEL <= TH_LOG_LEVEL_WARN 42 | #define TH_LOG_WARN(...) th_log_printf(TH_LOG_LEVEL_WARN, "WARN: [" TH_LOG_TAG "] " __VA_ARGS__) 43 | #else 44 | #define TH_LOG_WARN(...) ((void)0) 45 | #endif 46 | 47 | #if TH_LOG_LEVEL <= TH_LOG_LEVEL_ERROR 48 | #define TH_LOG_ERROR(...) th_log_printf(TH_LOG_LEVEL_ERROR, "ERROR: [" TH_LOG_TAG "] " __VA_ARGS__) 49 | #else 50 | #define TH_LOG_ERROR(...) ((void)0) 51 | #endif 52 | 53 | #if TH_LOG_LEVEL <= TH_LOG_LEVEL_FATAL 54 | #define TH_LOG_FATAL(...) th_log_printf(TH_LOG_LEVEL_FATAL, "FATAL: [" TH_LOG_TAG "] " __VA_ARGS__) 55 | #else 56 | #define TH_LOG_FATAL(...) ((void)0) 57 | #endif 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/th_method.gperf: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include 4 | #include "th_method.h" 5 | #pragma GCC diagnostic push 6 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 7 | %} 8 | %define lookup-function-name th_method_mapping_find 9 | %define constants-prefix TH_METHOD_ 10 | %define hash-function-name th_method_hash 11 | %struct-type 12 | %compare-strncmp 13 | struct th_method_mapping; 14 | %% 15 | GET, TH_METHOD_GET 16 | POST, TH_METHOD_POST 17 | PUT, TH_METHOD_PUT 18 | DELETE, TH_METHOD_DELETE 19 | PATCH, TH_METHOD_PATCH 20 | HEAD, TH_METHOD_HEAD 21 | OPTIONS, TH_METHOD_OPTIONS 22 | TRACE, TH_METHOD_TRACE 23 | CONNECT, TH_METHOD_CONNECT 24 | %% 25 | #pragma GCC diagnostic pop 26 | -------------------------------------------------------------------------------- /src/th_method.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_METHOD_H 2 | #define TH_METHOD_H 3 | 4 | #include 5 | 6 | struct th_method_mapping { 7 | const char* name; 8 | th_method method; 9 | }; 10 | 11 | struct th_method_mapping* th_method_mapping_find(const char* str, size_t len); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/th_mime.gperf: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include 4 | #include "th_mime.h" 5 | #include "th_string.h" 6 | #pragma GCC diagnostic push 7 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 8 | %} 9 | %define lookup-function-name th_mime_mapping_find 10 | %define constants-prefix TH_MIME_ 11 | %define hash-function-name th_mime_hash 12 | %struct-type 13 | %compare-strncmp 14 | struct th_mime_mapping; 15 | %% 16 | aac, TH_STRING_INIT("audio/aac") 17 | mp3, TH_STRING_INIT("audio/mpeg") 18 | ogg, TH_STRING_INIT("audio/ogg") 19 | opus, TH_STRING_INIT("audio/opus") 20 | wav, TH_STRING_INIT("audio/wav") 21 | weba, TH_STRING_INIT("audio/webm") 22 | otf, TH_STRING_INIT("font/otf") 23 | ttf, TH_STRING_INIT("font/ttf") 24 | woff, TH_STRING_INIT("font/woff") 25 | woff2,TH_STRING_INIT("font/woff2") 26 | avif, TH_STRING_INIT("image/avif") 27 | gif, TH_STRING_INIT("image/gif") 28 | jpg, TH_STRING_INIT("image/jpeg") 29 | jpeg, TH_STRING_INIT("image/jpeg") 30 | png, TH_STRING_INIT("image/png") 31 | svg, TH_STRING_INIT("image/svg+xml") 32 | webp, TH_STRING_INIT("image/webp") 33 | ico, TH_STRING_INIT("image/x-icon") 34 | css, TH_STRING_INIT("text/css") 35 | csv, TH_STRING_INIT("text/csv") 36 | html, TH_STRING_INIT("text/html") 37 | js, TH_STRING_INIT("text/javascript") 38 | md, TH_STRING_INIT("text/markdown") 39 | txt, TH_STRING_INIT("text/plain") 40 | mp4, TH_STRING_INIT("video/mp4") 41 | mpeg, TH_STRING_INIT("video/mpeg") 42 | ogv, TH_STRING_INIT("video/ogg") 43 | webm, TH_STRING_INIT("video/webm") 44 | json, TH_STRING_INIT("application/json") 45 | pdf, TH_STRING_INIT("application/pdf") 46 | zip, TH_STRING_INIT("application/zip") 47 | xhtml,TH_STRING_INIT("application/xhtml+xml") 48 | xml, TH_STRING_INIT("application/xml") 49 | %% 50 | #pragma GCC diagnostic pop 51 | 52 | -------------------------------------------------------------------------------- /src/th_mime.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_MIME_H 2 | #define TH_MIME_H 3 | 4 | #include 5 | 6 | #include "th_string.h" 7 | 8 | struct th_mime_mapping { 9 | const char* name; 10 | th_string mime; 11 | }; 12 | 13 | struct th_mime_mapping* th_mime_mapping_find(const char* ext, size_t len); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/th_mock_service.c: -------------------------------------------------------------------------------- 1 | #include "th_mock_service.h" 2 | 3 | #if defined(TH_CONFIG_OS_MOCK) 4 | 5 | #include "th_allocator.h" 6 | #include "th_system_error.h" 7 | 8 | #include 9 | 10 | TH_LOCAL(void) 11 | th_mock_handle_cancel(void* self) 12 | { 13 | (void)self; 14 | } 15 | 16 | TH_LOCAL(void) 17 | th_mock_handle_submit(void* self, th_io_task* task) 18 | { 19 | th_mock_handle* handle = self; 20 | th_io_handler* on_complete = th_io_task_try_execute(task); 21 | if (!on_complete) 22 | on_complete = th_io_task_abort(task, TH_ERR_SYSTEM(TH_EAGAIN)); 23 | th_runner_push_task(handle->service->runner, (th_task*)on_complete); 24 | } 25 | 26 | TH_LOCAL(void) 27 | th_mock_handle_enable_timeout(void* self, bool enabled) 28 | { 29 | (void)self; 30 | (void)enabled; 31 | } 32 | 33 | TH_LOCAL(int) 34 | th_mock_handle_get_fd(void* self) 35 | { 36 | th_mock_handle* handle = self; 37 | return handle->fd; 38 | } 39 | 40 | TH_LOCAL(void) 41 | th_mock_handle_destroy(void* self) 42 | { 43 | th_mock_handle* handle = self; 44 | th_allocator_free(th_default_allocator_get(), handle); 45 | } 46 | 47 | TH_LOCAL(void) 48 | th_mock_handle_init(th_mock_handle* handle, th_mock_service* service, int fd) 49 | { 50 | handle->base.cancel = th_mock_handle_cancel; 51 | handle->base.submit = th_mock_handle_submit; 52 | handle->base.enable_timeout = th_mock_handle_enable_timeout; 53 | handle->base.get_fd = th_mock_handle_get_fd; 54 | handle->base.destroy = th_mock_handle_destroy; 55 | handle->service = service; 56 | handle->fd = fd; 57 | } 58 | 59 | TH_LOCAL(void) 60 | th_mock_service_deinit(th_mock_service* service) 61 | { 62 | (void)service; 63 | } 64 | 65 | TH_LOCAL(void) 66 | th_mock_service_destroy(void* self) 67 | { 68 | th_mock_service* service = self; 69 | th_mock_service_deinit(service); 70 | th_allocator_free(th_default_allocator_get(), service); 71 | } 72 | 73 | TH_LOCAL(th_err) 74 | th_mock_service_create_handle(void* self, th_io_handle** out, int fd) 75 | { 76 | th_mock_service* service = self; 77 | th_mock_handle* handle = th_allocator_alloc(th_default_allocator_get(), sizeof(th_mock_handle)); 78 | if (!handle) 79 | return TH_ERR_BAD_ALLOC; 80 | th_mock_handle_init(handle, service, fd); 81 | *out = (th_io_handle*)handle; 82 | return TH_ERR_OK; 83 | } 84 | 85 | TH_LOCAL(void) 86 | th_mock_service_run(void* self, int timeout_ms) 87 | { 88 | (void)self; 89 | (void)timeout_ms; 90 | } 91 | 92 | TH_LOCAL(th_err) 93 | th_mock_service_init(th_mock_service* service, th_runner* runner) 94 | { 95 | service->base.create_handle = th_mock_service_create_handle; 96 | service->base.run = th_mock_service_run; 97 | service->base.destroy = NULL; 98 | service->runner = runner; 99 | return TH_ERR_OK; 100 | } 101 | 102 | TH_PRIVATE(th_err) 103 | th_mock_service_create(th_io_service** out, th_runner* runner) 104 | { 105 | th_mock_service* service = th_allocator_alloc(th_default_allocator_get(), sizeof(th_mock_service)); 106 | if (!service) 107 | return TH_ERR_BAD_ALLOC; 108 | th_err err = th_mock_service_init(service, runner); 109 | if (err != TH_ERR_OK) { 110 | th_allocator_free(th_default_allocator_get(), service); 111 | return err; 112 | } 113 | service->base.destroy = th_mock_service_destroy; 114 | *out = (th_io_service*)service; 115 | return TH_ERR_OK; 116 | } 117 | #endif 118 | -------------------------------------------------------------------------------- /src/th_mock_service.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_MOCK_SERVICE_H 2 | #define TH_MOCK_SERVICE_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #if defined(TH_CONFIG_OS_MOCK) 9 | 10 | #include "th_io_service.h" 11 | #include "th_io_task.h" 12 | #include "th_runner.h" 13 | 14 | typedef struct th_mock_service th_mock_service; 15 | typedef struct th_mock_handle th_mock_handle; 16 | struct th_mock_handle { 17 | th_io_handle base; 18 | th_mock_service* service; 19 | int fd; 20 | }; 21 | 22 | struct th_mock_service { 23 | th_io_service base; 24 | th_runner* runner; 25 | }; 26 | 27 | TH_PRIVATE(th_err) 28 | th_mock_service_create(th_io_service** out, th_runner* runner); 29 | 30 | #endif 31 | #endif 32 | -------------------------------------------------------------------------------- /src/th_mock_syscall.c: -------------------------------------------------------------------------------- 1 | #include "th_mock_syscall.h" 2 | 3 | static int th_mock_accept_default(void) 4 | { 5 | return 0; 6 | } 7 | 8 | static int th_mock_open_default(void) 9 | { 10 | return 0; 11 | } 12 | 13 | static int th_mock_lseek_default(void) 14 | { 15 | return 0; 16 | } 17 | 18 | static int th_mock_close_default(void) 19 | { 20 | return 0; 21 | } 22 | 23 | static int th_mock_read_default(void* buf, size_t len) 24 | { 25 | (void)buf; 26 | return len; 27 | } 28 | 29 | static int th_mock_write_default(size_t len) 30 | { 31 | return (size_t)len; 32 | } 33 | 34 | static int th_mock_settime_default(void) 35 | { 36 | return 0; 37 | } 38 | 39 | th_mock_syscall* th_mock_syscall_get(void) 40 | { 41 | static th_mock_syscall syscall = { 42 | .accept = th_mock_accept_default, 43 | .open = th_mock_open_default, 44 | .lseek = th_mock_lseek_default, 45 | .close = th_mock_close_default, 46 | .read = th_mock_read_default, 47 | .write = th_mock_write_default, 48 | .settime = th_mock_settime_default, 49 | }; 50 | return &syscall; 51 | } 52 | 53 | void th_mock_syscall_reset(void) 54 | { 55 | th_mock_syscall* syscall = th_mock_syscall_get(); 56 | syscall->accept = th_mock_accept_default; 57 | syscall->open = th_mock_open_default; 58 | syscall->lseek = th_mock_lseek_default; 59 | syscall->close = th_mock_close_default; 60 | syscall->read = th_mock_read_default; 61 | syscall->write = th_mock_write_default; 62 | syscall->settime = th_mock_settime_default; 63 | } 64 | 65 | int th_mock_accept(void) 66 | { 67 | th_mock_syscall* syscall = th_mock_syscall_get(); 68 | return syscall->accept(); 69 | } 70 | 71 | int th_mock_open(void) 72 | { 73 | th_mock_syscall* syscall = th_mock_syscall_get(); 74 | return syscall->open(); 75 | } 76 | 77 | int th_mock_lseek(void) 78 | { 79 | th_mock_syscall* syscall = th_mock_syscall_get(); 80 | return syscall->lseek(); 81 | } 82 | 83 | int th_mock_close(void) 84 | { 85 | th_mock_syscall* syscall = th_mock_syscall_get(); 86 | return syscall->close(); 87 | } 88 | 89 | int th_mock_read(void* buf, size_t len) 90 | { 91 | th_mock_syscall* syscall = th_mock_syscall_get(); 92 | return syscall->read(buf, len); 93 | } 94 | 95 | int th_mock_write(size_t len) 96 | { 97 | th_mock_syscall* syscall = th_mock_syscall_get(); 98 | return syscall->write(len); 99 | } 100 | 101 | int th_mock_settime(void) 102 | { 103 | th_mock_syscall* syscall = th_mock_syscall_get(); 104 | return syscall->settime(); 105 | } 106 | -------------------------------------------------------------------------------- /src/th_mock_syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_MOCK_FD_H 2 | #define TH_MOCK_FD_H 3 | 4 | #include 5 | 6 | #include "th_iov.h" 7 | #include "th_system_error.h" 8 | 9 | typedef struct th_mock_syscall { 10 | int (*accept)(void); 11 | int (*open)(void); 12 | int (*lseek)(void); 13 | int (*close)(void); 14 | int (*read)(void* buf, size_t len); 15 | int (*write)(size_t len); 16 | int (*settime)(void); 17 | } th_mock_syscall; 18 | 19 | th_mock_syscall* th_mock_syscall_get(void); 20 | void th_mock_syscall_reset(void); 21 | 22 | int th_mock_accept(void); 23 | 24 | int th_mock_open(void); 25 | 26 | int th_mock_lseek(void); 27 | 28 | int th_mock_close(void); 29 | 30 | int th_mock_read(void* buf, size_t len); 31 | 32 | int th_mock_write(size_t len); 33 | 34 | int th_mock_settime(void); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/th_path.c: -------------------------------------------------------------------------------- 1 | #include "th_path.h" 2 | 3 | #include "th_config.h" 4 | #include "th_fmt.h" 5 | #include "th_string.h" 6 | 7 | #include 8 | 9 | #if defined(TH_CONFIG_OS_POSIX) 10 | #include 11 | #include 12 | 13 | TH_LOCAL(th_err) 14 | th_path_resolve_posix(th_string path, th_heap_string* out) 15 | { 16 | char in[TH_CONFIG_MAX_PATH_LEN + 1] = {0}; 17 | size_t pos = 0; 18 | pos += th_fmt_strn_append(in, pos, sizeof(in) - pos, path.ptr, path.len); 19 | in[pos] = '\0'; 20 | th_heap_string_resize(out, TH_CONFIG_MAX_PATH_LEN, 0); 21 | char* out_ptr = th_heap_string_at(out, 0); 22 | char* ret = realpath(in, out_ptr); 23 | if (ret == NULL) 24 | return TH_ERR_SYSTEM(errno); 25 | th_heap_string_resize(out, strlen(out_ptr), 0); 26 | return TH_ERR_OK; 27 | } 28 | /* 29 | TH_LOCAL(th_err) 30 | th_path_resolve_against_posix(th_dir* dir, th_string path, th_heap_string* out) 31 | { 32 | char in[TH_CONFIG_MAX_PATH_LEN + 1] = {0}; 33 | th_string root = th_dir_get_path(dir); 34 | size_t pos = 0; 35 | pos += th_fmt_strn_append(in, pos, sizeof(in) - pos, root.ptr, root.len); 36 | pos += th_fmt_str_append(in, pos, sizeof(in) - pos, "/"); 37 | pos += th_fmt_strn_append(in, pos, sizeof(in) - pos, path.ptr, path.len); 38 | th_heap_string_resize(out, TH_CONFIG_MAX_PATH_LEN, 0); 39 | char* out_ptr = th_heap_string_data(out); 40 | char* ret = realpath(in, out_ptr); 41 | if (ret == NULL) 42 | return TH_ERR_SYSTEM(errno); 43 | th_heap_string_resize(out, strlen(out_ptr), 0); 44 | 45 | return TH_ERR_OK; 46 | } 47 | */ 48 | #elif defined(TH_CONFIG_OS_MOCK) 49 | TH_LOCAL(th_err) 50 | th_path_resolve_mock(th_string path, th_heap_string* out) 51 | { 52 | (void)path; 53 | th_heap_string_clear(out); 54 | th_heap_string_set(out, path); 55 | return TH_ERR_OK; 56 | } 57 | #endif 58 | 59 | TH_PRIVATE(th_err) 60 | th_path_resolve(th_string path, th_heap_string* out) 61 | { 62 | #if defined(TH_CONFIG_OS_POSIX) 63 | return th_path_resolve_posix(path, out); 64 | #elif defined(TH_CONFIG_OS_MOCK) 65 | return th_path_resolve_mock(path, out); 66 | #else 67 | (void)path; 68 | (void)out; 69 | TH_ASSERT(0 && "Not implemented"); 70 | return TH_ERR_NOSUPPORT; 71 | #endif 72 | } 73 | 74 | TH_PRIVATE(th_err) 75 | th_path_resolve_against(th_string path, th_dir* dir, th_heap_string* out) 76 | { 77 | char in[TH_CONFIG_MAX_PATH_LEN + 1] = {0}; 78 | th_string root = th_dir_get_path(dir); 79 | size_t pos = 0; 80 | pos += th_fmt_strn_append(in, pos, sizeof(in) - pos, root.ptr, root.len); 81 | pos += th_fmt_str_append(in, pos, sizeof(in) - pos, "/"); 82 | pos += th_fmt_strn_append(in, pos, sizeof(in) - pos, path.ptr, path.len); 83 | return th_path_resolve(th_string_make(in, pos), out); 84 | } 85 | 86 | TH_PRIVATE(bool) 87 | th_path_is_within(th_string realpath, th_dir* dir) 88 | { 89 | th_string root = th_dir_get_path(dir); 90 | if (realpath.len < root.len) 91 | return false; 92 | return th_string_eq(th_string_make(realpath.ptr, root.len), root); 93 | } 94 | 95 | TH_PRIVATE(bool) 96 | th_path_is_hidden(th_string path) 97 | { 98 | size_t pos = 0; 99 | while ((pos = th_string_find_first(path, pos, '/')) != th_string_npos) { 100 | if (path.ptr[++pos] == '.') 101 | return true; 102 | } 103 | return false; 104 | } 105 | -------------------------------------------------------------------------------- /src/th_path.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_PATH_H 2 | #define TH_PATH_H 3 | 4 | #include "th_config.h" 5 | #include "th_dir.h" 6 | #include "th_string.h" 7 | 8 | /** 9 | * @brief th_path provides a bunch of helper functions to work with paths. 10 | */ 11 | 12 | /** 13 | * @brief th_path_resolve resolves a path to a absolute path. 14 | * @param dir The directory to resolve the path against. 15 | * @param path The path to resolve. 16 | * @param out The resolved path. 17 | * @return TH_ERR_OK on success, otherwise an error code. 18 | */ 19 | TH_PRIVATE(th_err) 20 | th_path_resolve_against(th_string path, th_dir* dir, th_heap_string* out); 21 | 22 | TH_PRIVATE(th_err) 23 | th_path_resolve(th_string path, th_heap_string* out); 24 | 25 | TH_PRIVATE(bool) 26 | th_path_is_within(th_string path, th_dir* dir); 27 | 28 | TH_PRIVATE(bool) 29 | th_path_is_hidden(th_string path); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/th_poll_service.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_POLL_SERVICE_H 2 | #define TH_POLL_SERVICE_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #ifdef TH_CONFIG_WITH_POLL 9 | #include "th_allocator.h" 10 | #include "th_hashmap.h" 11 | #include "th_io_service.h" 12 | #include "th_io_task.h" 13 | #include "th_runner.h" 14 | #include "th_timer.h" 15 | #include "th_vec.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /* Forward declarations begin */ 23 | 24 | typedef struct th_poll_service th_poll_service; 25 | typedef struct th_poll_handle th_poll_handle; 26 | typedef struct th_poll_handle_map th_poll_handle_map; 27 | 28 | /* Forward declarations end */ 29 | /* th_fd_to_idx_map implementation begin */ 30 | 31 | TH_INLINE(uint32_t) 32 | th_fd_hash(int fd) 33 | { 34 | return (uint32_t)fd; 35 | } 36 | 37 | TH_INLINE(bool) 38 | th_int_eq(int a, int b) 39 | { 40 | return a == b; 41 | } 42 | 43 | TH_DEFINE_HASHMAP(th_fd_to_idx_map, int, size_t, th_fd_hash, th_int_eq, -1) 44 | 45 | /* th_fd_to_idx_map implementation end */ 46 | /* th_poll_handle begin */ 47 | 48 | struct th_poll_handle { 49 | th_io_handle base; 50 | th_timer timer; 51 | th_poll_handle* next; 52 | th_poll_handle* prev; 53 | th_allocator* allocator; 54 | th_poll_service* service; 55 | th_io_task* iot[TH_IO_OP_TYPE_MAX]; 56 | int fd; 57 | bool timeout_enabled; 58 | }; 59 | 60 | #ifndef TH_POLL_HANDLE_POOL 61 | #define TH_POLL_HANDLE_POOL 62 | TH_DEFINE_OBJ_POOL_ALLOCATOR(th_poll_handle_pool, th_poll_handle, prev, next) 63 | #endif 64 | 65 | #ifndef TH_POLL_HANDLE_LIST 66 | #define TH_POLL_HANDLE_LIST 67 | TH_DEFINE_QUEUE(th_poll_handle_list, th_poll_handle) 68 | #endif 69 | 70 | #ifndef TH_POLLFD_VEC 71 | #define TH_POLLFD_VEC 72 | TH_DEFINE_VEC(th_pollfd_vec, struct pollfd, (void)) 73 | #endif 74 | 75 | /* th_poll_handle end */ 76 | /* th_poll_handle_map begin */ 77 | 78 | struct th_poll_handle_map { 79 | th_fd_to_idx_map fd_to_idx_map; 80 | th_allocator* allocator; 81 | th_poll_handle** handles; 82 | size_t size; 83 | size_t capacity; 84 | }; 85 | 86 | /* th_poll_handle_map end */ 87 | 88 | struct th_poll_service { 89 | th_io_service base; 90 | th_allocator* allocator; 91 | th_runner* runner; 92 | th_poll_handle_pool handle_allocator; 93 | th_poll_handle_map handles; 94 | th_pollfd_vec fds; 95 | }; 96 | 97 | TH_PRIVATE(th_err) 98 | th_poll_service_create(th_io_service** out, th_runner* runner, th_allocator* allocator); 99 | 100 | #endif /* TH_HAVE_POLL */ 101 | #endif 102 | -------------------------------------------------------------------------------- /src/th_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_QUEUE_H 2 | #define TH_QUEUE_H 3 | 4 | #include "th_config.h" 5 | 6 | #include 7 | 8 | /** Generic queue implementation. 9 | * that works with any struct that has a next pointer. 10 | */ 11 | #define TH_DEFINE_QUEUE(NAME, T) \ 12 | typedef struct NAME { \ 13 | T* head; \ 14 | T* tail; \ 15 | } NAME; \ 16 | \ 17 | TH_INLINE(NAME) \ 18 | NAME##_make(void) TH_MAYBE_UNUSED; \ 19 | \ 20 | TH_INLINE(void) \ 21 | NAME##_push(NAME* queue, T* item) TH_MAYBE_UNUSED; \ 22 | \ 23 | TH_INLINE(T*) \ 24 | NAME##_pop(NAME* queue) TH_MAYBE_UNUSED; \ 25 | \ 26 | TH_INLINE(bool) \ 27 | NAME##_empty(NAME* queue) TH_MAYBE_UNUSED; \ 28 | \ 29 | TH_INLINE(void) \ 30 | NAME##_push_queue(NAME* queue, NAME* other) TH_MAYBE_UNUSED; \ 31 | \ 32 | TH_INLINE(NAME) \ 33 | NAME##_make(void) \ 34 | { \ 35 | return (NAME){.head = NULL, .tail = NULL}; \ 36 | } \ 37 | \ 38 | TH_INLINE(bool) \ 39 | NAME##_empty(NAME* queue) \ 40 | { \ 41 | return queue->head == NULL; \ 42 | } \ 43 | \ 44 | TH_INLINE(void) \ 45 | NAME##_push(NAME* queue, T* item) \ 46 | { \ 47 | if (queue->head == NULL) { \ 48 | queue->head = item; \ 49 | } else { \ 50 | queue->tail->next = item; \ 51 | } \ 52 | queue->tail = item; \ 53 | item->next = NULL; \ 54 | } \ 55 | \ 56 | TH_INLINE(void) \ 57 | NAME##_push_queue(NAME* queue, NAME* other) \ 58 | { \ 59 | if (queue->head == NULL) { \ 60 | *queue = *other; \ 61 | } else if (other->head) { \ 62 | queue->tail->next = other->head; \ 63 | queue->tail = other->tail; \ 64 | } \ 65 | *other = NAME##_make(); \ 66 | } \ 67 | \ 68 | TH_INLINE(T*) \ 69 | NAME##_pop(NAME* queue) \ 70 | { \ 71 | T* item = queue->head; \ 72 | if (item) { \ 73 | queue->head = item->next; \ 74 | item->next = NULL; \ 75 | } \ 76 | return item; \ 77 | } 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/th_refcounted.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_REFCOUNTED_H 2 | #define TH_REFCOUNTED_H 3 | 4 | #include 5 | 6 | #include "th_utility.h" 7 | 8 | typedef struct th_refcounted { 9 | unsigned int refcount; 10 | void (*destroy)(void* self); 11 | } th_refcounted; 12 | 13 | TH_INLINE(void) 14 | th_refcounted_init(th_refcounted* refcounted, void (*destroy)(void* self)) 15 | { 16 | refcounted->refcount = 1; 17 | refcounted->destroy = destroy; 18 | } 19 | 20 | TH_INLINE(th_refcounted*) 21 | th_refcounted_ref(th_refcounted* refcounted) 22 | { 23 | ++refcounted->refcount; 24 | return refcounted; 25 | } 26 | 27 | TH_INLINE(void) 28 | th_refcounted_unref(th_refcounted* refcounted) 29 | { 30 | TH_ASSERT(refcounted->refcount > 0 && "Invalid refcount"); 31 | if (--refcounted->refcount == 0) { 32 | refcounted->destroy(refcounted); 33 | } 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/th_request.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_REQUEST_H 2 | #define TH_REQUEST_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_fcache.h" 8 | #include "th_heap_string.h" 9 | #include "th_method.h" 10 | #include "th_upload.h" 11 | #include "th_vec.h" 12 | 13 | struct th_iter_methods { 14 | bool (*next)(th_iter* it); 15 | const char* (*key)(const th_iter* it); 16 | const void* (*val)(const th_iter* it); 17 | }; 18 | 19 | typedef struct th_hstr_pair { 20 | th_heap_string key; 21 | th_heap_string value; 22 | } th_hstr_pair; 23 | 24 | TH_INLINE(void) 25 | th_hstr_pair_deinit(th_hstr_pair* pair) 26 | { 27 | th_heap_string_deinit(&pair->key); 28 | th_heap_string_deinit(&pair->value); 29 | } 30 | 31 | TH_DEFINE_VEC(th_hstr_vec, th_hstr_pair, th_hstr_pair_deinit) 32 | 33 | TH_DEFINE_VEC(th_upload_vec, th_upload, th_upload_deinit) 34 | 35 | struct th_request { 36 | th_allocator* allocator; 37 | th_fcache* fcache; 38 | th_heap_string uri_path; 39 | th_heap_string uri_query; 40 | th_upload_vec uploads; 41 | th_hstr_vec cookies; 42 | th_hstr_vec headers; 43 | th_hstr_vec queryvars; 44 | th_hstr_vec formvars; 45 | th_hstr_vec pathvars; 46 | th_string body; 47 | th_method method; 48 | int version; 49 | bool close; 50 | }; 51 | 52 | TH_PRIVATE(void) 53 | th_request_init(th_request* request, th_fcache* fcache, th_allocator* allocator); 54 | 55 | TH_PRIVATE(void) 56 | th_request_deinit(th_request* request); 57 | 58 | TH_PRIVATE(void) 59 | th_request_reset(th_request* request); 60 | 61 | TH_PRIVATE(void) 62 | th_request_set_version(th_request* request, int version); 63 | 64 | TH_PRIVATE(void) 65 | th_request_set_method(th_request* request, th_method method); 66 | 67 | TH_PRIVATE(th_err) 68 | th_request_set_uri_path(th_request* request, th_string path); 69 | 70 | TH_PRIVATE(th_err) 71 | th_request_set_uri_query(th_request* request, th_string query); 72 | 73 | TH_PRIVATE(th_err) 74 | th_request_add_queryvar(th_request* request, th_string key, th_string value); 75 | 76 | TH_PRIVATE(th_err) 77 | th_request_add_formvar(th_request* request, th_string key, th_string value); 78 | 79 | TH_PRIVATE(th_err) 80 | th_request_add_pathvar(th_request* request, th_string key, th_string value); 81 | 82 | TH_PRIVATE(th_err) 83 | th_request_add_cookie(th_request* request, th_string key, th_string value); 84 | 85 | TH_PRIVATE(th_err) 86 | th_request_add_header(th_request* request, th_string key, th_string value); 87 | 88 | TH_PRIVATE(th_err) 89 | th_request_add_upload(th_request* request, th_string data, th_string name, th_string filename, th_string content_type); 90 | 91 | TH_PRIVATE(void) 92 | th_request_clear_queryvars(th_request* request); 93 | 94 | TH_PRIVATE(void) 95 | th_request_set_body(th_request* request, th_string body); 96 | 97 | TH_PRIVATE(th_string) 98 | th_request_get_header(th_request* request, th_string key) TH_MAYBE_UNUSED; 99 | 100 | TH_PRIVATE(th_string) 101 | th_request_get_pathvar(th_request* request, th_string key) TH_MAYBE_UNUSED; 102 | 103 | TH_PRIVATE(th_string) 104 | th_request_get_queryvar(th_request* request, th_string key) TH_MAYBE_UNUSED; 105 | 106 | TH_PRIVATE(th_string) 107 | th_request_get_formvar(th_request* request, th_string key) TH_MAYBE_UNUSED; 108 | 109 | TH_PRIVATE(th_upload*) 110 | th_request_get_upload(th_request* request, th_string key) TH_MAYBE_UNUSED; 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /src/th_request_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_REQUEST_PARSER_H 2 | #define TH_REQUEST_PARSER_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_request.h" 8 | 9 | #include 10 | 11 | typedef enum th_request_parser_state { 12 | TH_REQUEST_PARSER_STATE_METHOD, 13 | TH_REQUEST_PARSER_STATE_PATH, 14 | TH_REQUEST_PARSER_STATE_VERSION, 15 | TH_REQUEST_PARSER_STATE_HEADERS, 16 | TH_REQUEST_PARSER_STATE_BODY, 17 | TH_REQUEST_PARSER_STATE_DONE 18 | } th_request_parser_state; 19 | 20 | typedef enum th_request_body_encoding { 21 | TH_REQUEST_BODY_ENCODING_NONE, 22 | TH_REQUEST_BODY_ENCODING_FORM_URL_ENCODED, 23 | TH_REQUEST_BODY_ENCODING_MULTIPART_FORM_DATA 24 | } th_request_body_encoding; 25 | 26 | typedef struct th_request_parser { 27 | size_t content_len; 28 | th_request_parser_state state; 29 | th_request_body_encoding body_encoding; 30 | } th_request_parser; 31 | 32 | TH_PRIVATE(void) 33 | th_request_parser_init(th_request_parser* parser); 34 | 35 | TH_PRIVATE(void) 36 | th_request_parser_reset(th_request_parser* parser); 37 | 38 | TH_PRIVATE(size_t) 39 | th_request_parser_content_len(th_request_parser* parser); 40 | 41 | TH_PRIVATE(th_err) 42 | th_request_parser_parse(th_request_parser* parser, th_request* request, th_string data, size_t* parsed); 43 | 44 | TH_PRIVATE(bool) 45 | th_request_parser_header_done(th_request_parser* parser); 46 | 47 | TH_PRIVATE(bool) 48 | th_request_parser_done(th_request_parser* parser); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/th_response.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_RESPONSE_H 2 | #define TH_RESPONSE_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "th_allocator.h" 9 | #include "th_config.h" 10 | #include "th_fcache.h" 11 | #include "th_header_id.h" 12 | #include "th_heap_string.h" 13 | #include "th_socket.h" 14 | /* th_response begin */ 15 | 16 | // 3 = start line + headers + body 17 | #define TH_RESPONSE_MAX_CHUNK_NUM 3 18 | 19 | struct th_response { 20 | th_heap_string headers; 21 | th_heap_string body; 22 | th_iov iov[TH_RESPONSE_MAX_CHUNK_NUM]; 23 | th_allocator* allocator; 24 | th_fcache* fcache; 25 | th_fcache_entry* fcache_entry; 26 | size_t file_len; 27 | th_code code; 28 | bool header_is_set[TH_HEADER_ID_MAX]; 29 | bool is_file; 30 | // Set this to true if we have a HEAD request, so that we only write headers. 31 | bool only_headers; 32 | }; 33 | 34 | TH_PRIVATE(void) 35 | th_response_init(th_response* response, th_fcache* fcache, th_allocator* allocator); 36 | 37 | TH_PRIVATE(void) 38 | th_response_reset(th_response* response); 39 | 40 | TH_PRIVATE(void) 41 | th_response_set_code(th_response* response, th_code code); 42 | 43 | TH_PRIVATE(th_err) 44 | th_response_add_header(th_response* response, th_string key, th_string value); 45 | 46 | TH_PRIVATE(th_err) 47 | th_response_set_body(th_response* response, th_string body); 48 | 49 | TH_PRIVATE(void) 50 | th_response_deinit(th_response* response); 51 | 52 | /* th_response end */ 53 | 54 | TH_PRIVATE(void) 55 | th_response_async_write(th_response* response, th_socket* socket, th_io_handler* handler); 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/th_response_test.c: -------------------------------------------------------------------------------- 1 | #include "th_context.h" 2 | #include "th_mock_service.h" 3 | #include "th_mock_syscall.h" 4 | #include "th_response.h" 5 | #include "th_tcp_socket.h" 6 | #include "th_test.h" 7 | 8 | #include 9 | 10 | static th_err last_err = TH_ERR_OK; 11 | static void write_handler(void* data, size_t len, th_err err) 12 | { 13 | (void)data; 14 | (void)len; 15 | last_err = err; 16 | } 17 | 18 | #define TH_SETUP_BASIC(context, response, socket) \ 19 | th_context context = {0}; \ 20 | th_context_init(&context, NULL); \ 21 | th_fcache fcache = {0}; \ 22 | th_fcache_init(&fcache, th_default_allocator_get()); \ 23 | th_response response = {0}; \ 24 | th_response_init(&response, &fcache, th_default_allocator_get()); \ 25 | th_tcp_socket socket = {0}; \ 26 | th_tcp_socket_init(&socket, &context, th_default_allocator_get()); \ 27 | th_tcp_socket_set_fd(&socket, 0); \ 28 | th_io_handler handler = {0}; \ 29 | th_io_handler_init(&handler, write_handler, NULL); 30 | 31 | #define TH_SHUTDOWN_BASIC(context, response, socket) \ 32 | th_tcp_socket_set_fd(&socket, -1); \ 33 | th_tcp_socket_close(&socket); \ 34 | th_response_deinit(&response); \ 35 | th_fcache_deinit(&fcache); \ 36 | th_context_deinit(&context); 37 | 38 | TH_TEST_BEGIN(response) 39 | { 40 | TH_TEST_CASE_BEGIN(response_create_and_destroy) 41 | { 42 | TH_SETUP_BASIC(context, response, socket); 43 | TH_SHUTDOWN_BASIC(context, response, socket); 44 | } 45 | TH_TEST_CASE_END 46 | TH_TEST_CASE_BEGIN(response_write_without_content) 47 | { 48 | TH_SETUP_BASIC(context, response, socket); 49 | th_response_async_write(&response, &socket.base, &handler); 50 | while (1) { 51 | if (th_context_poll(&context, -1) != TH_ERR_OK) 52 | break; 53 | } 54 | TH_EXPECT(last_err == TH_ERR_OK); 55 | TH_SHUTDOWN_BASIC(context, response, socket); 56 | } 57 | TH_TEST_CASE_END 58 | TH_TEST_CASE_BEGIN(response_write_with_content) 59 | { 60 | TH_SETUP_BASIC(context, response, socket); 61 | th_set_body(&response, "Hello, World!"); 62 | th_response_async_write(&response, &socket.base, &handler); 63 | while (1) { 64 | if (th_context_poll(&context, -1) != TH_ERR_OK) 65 | break; 66 | } 67 | TH_EXPECT(last_err == TH_ERR_OK); 68 | TH_SHUTDOWN_BASIC(context, response, socket); 69 | } 70 | TH_TEST_CASE_END 71 | TH_TEST_CASE_BEGIN(response_write_with_content_and_header) 72 | { 73 | TH_SETUP_BASIC(context, response, socket); 74 | th_set_body(&response, "Hello, World!"); 75 | th_add_header(&response, "Connection", "close"); 76 | th_add_header(&response, "Content-Type", "text/plain"); 77 | th_response_async_write(&response, &socket.base, &handler); 78 | while (1) { 79 | if (th_context_poll(&context, -1) != TH_ERR_OK) 80 | break; 81 | } 82 | TH_EXPECT(last_err == TH_ERR_OK); 83 | TH_SHUTDOWN_BASIC(context, response, socket); 84 | } 85 | TH_TEST_CASE_END 86 | } 87 | TH_TEST_END 88 | -------------------------------------------------------------------------------- /src/th_router.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_ROUTER_H 2 | #define TH_ROUTER_H 3 | 4 | #include 5 | 6 | #include "th_allocator.h" 7 | #include "th_heap_string.h" 8 | #include "th_request.h" 9 | #include "th_response.h" 10 | #include "th_string.h" 11 | 12 | typedef struct th_route_handler { 13 | th_handler handler; 14 | void* user_data; 15 | } th_route_handler; 16 | 17 | typedef struct th_capture { 18 | th_string key; 19 | th_string value; 20 | } th_capture; 21 | 22 | typedef enum th_capture_type { 23 | TH_CAPTURE_TYPE_NONE = 0, 24 | TH_CAPTURE_TYPE_INT, 25 | TH_CAPTURE_TYPE_STRING, 26 | TH_CAPTURE_TYPE_PATH, 27 | } th_capture_type; 28 | 29 | typedef struct th_route_segment th_route_segment; 30 | struct th_route_segment { 31 | th_capture_type type; 32 | th_heap_string name; 33 | th_route_handler handler[TH_METHOD_MAX]; 34 | th_route_segment* next; 35 | th_route_segment* children; 36 | th_allocator* allocator; 37 | }; 38 | 39 | typedef struct th_router { 40 | th_route_segment* routes; 41 | th_allocator* allocator; 42 | } th_router; 43 | 44 | TH_PRIVATE(void) 45 | th_router_init(th_router* router, th_allocator* allocator); 46 | 47 | TH_PRIVATE(void) 48 | th_router_deinit(th_router* router); 49 | 50 | TH_PRIVATE(th_err) 51 | th_router_handle(th_router* router, th_request* request, th_response* response); 52 | 53 | /** th_router_would_handle 54 | * Check if the router would handle the request, if 55 | * it was to be passed with the given method (and not the actual method in the request). 56 | */ 57 | TH_PRIVATE(bool) 58 | th_router_would_handle(th_router* router, th_method method, th_request* request); 59 | 60 | TH_PRIVATE(th_err) 61 | th_router_add_route(th_router* router, th_method method, th_string route, th_handler handler, void* user_data); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/th_runner.c: -------------------------------------------------------------------------------- 1 | #include "th_runner.h" 2 | #include "th_allocator.h" 3 | #include "th_log.h" 4 | #include "th_utility.h" 5 | 6 | /* th_runner begin */ 7 | 8 | TH_PRIVATE(void) 9 | th_runner_init(th_runner* runner) 10 | { 11 | runner->queue = th_task_queue_make(); 12 | runner->num_tasks = 0; 13 | runner->waiting = 0; 14 | th_task_queue_push(&runner->queue, &runner->service_task); 15 | } 16 | 17 | TH_PRIVATE(void) 18 | th_runner_set_io_service(th_runner* runner, th_io_service* service) 19 | { 20 | runner->io_service = service; 21 | } 22 | 23 | TH_PRIVATE(void) 24 | th_runner_push_task(th_runner* runner, th_task* task) 25 | { 26 | ++runner->num_tasks; 27 | th_task_queue_push(&runner->queue, task); 28 | } 29 | 30 | TH_PRIVATE(void) 31 | th_runner_push_uncounted_task(th_runner* runner, th_task* task) 32 | { 33 | th_task_queue_push(&runner->queue, task); 34 | } 35 | 36 | TH_PRIVATE(void) 37 | th_runner_increase_task_count(th_runner* runner) 38 | { 39 | ++runner->num_tasks; 40 | } 41 | 42 | TH_PRIVATE(th_err) 43 | th_runner_poll(th_runner* runner, int timeout_ms) 44 | { 45 | if (runner->num_tasks == 0) { 46 | return TH_ERR_EOF; 47 | } 48 | while (1) { 49 | th_task* task = th_task_queue_pop(&runner->queue); 50 | TH_ASSERT(task && "Task queue must never be empty"); 51 | int empty = th_task_queue_empty(&runner->queue); 52 | if (task == &runner->service_task) { 53 | th_io_service_run(runner->io_service, empty ? timeout_ms : 0); 54 | th_task_queue_push(&runner->queue, &runner->service_task); 55 | if (empty) 56 | return TH_ERR_OK; 57 | } else { 58 | task->fn(task); 59 | if (task->destroy) 60 | task->destroy(task); 61 | --runner->num_tasks; 62 | return TH_ERR_OK; 63 | } 64 | } 65 | return TH_ERR_OK; 66 | } 67 | 68 | TH_PRIVATE(void) 69 | th_runner_drain(th_runner* runner) 70 | { 71 | th_task* task = NULL; 72 | while ((task = th_task_queue_pop(&runner->queue))) { 73 | if (task != &runner->service_task) { 74 | task->fn(task); 75 | if (task->destroy) 76 | task->destroy(task); 77 | --runner->num_tasks; 78 | } 79 | } 80 | } 81 | 82 | TH_PRIVATE(void) 83 | th_runner_deinit(th_runner* runner) 84 | { 85 | th_task* task = NULL; 86 | while ((task = th_task_queue_pop(&runner->queue))) { 87 | if (task->destroy) 88 | task->destroy(task); 89 | } 90 | } 91 | 92 | /* runner end */ 93 | -------------------------------------------------------------------------------- /src/th_runner.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_RUNNER_H 2 | #define TH_RUNNER_H 3 | 4 | #include "th_io_service.h" 5 | #include "th_task.h" 6 | 7 | #include 8 | 9 | typedef struct th_runner { 10 | th_io_service* io_service; 11 | th_task service_task; 12 | int waiting; 13 | th_task_queue queue; 14 | size_t num_tasks; 15 | } th_runner; 16 | 17 | TH_PRIVATE(void) 18 | th_runner_init(th_runner* runner); 19 | 20 | TH_PRIVATE(void) 21 | th_runner_set_io_service(th_runner* runner, th_io_service* service); 22 | 23 | TH_PRIVATE(void) 24 | th_runner_push_task(th_runner* runner, th_task* task); 25 | 26 | TH_PRIVATE(void) 27 | th_runner_push_uncounted_task(th_runner* runner, th_task* task); 28 | 29 | TH_PRIVATE(void) 30 | th_runner_increase_task_count(th_runner* runner); 31 | 32 | TH_PRIVATE(th_err) 33 | th_runner_poll(th_runner* runner, int timeout_ms); 34 | 35 | TH_PRIVATE(void) 36 | th_runner_drain(th_runner* runner); 37 | 38 | TH_PRIVATE(void) 39 | th_runner_deinit(th_runner* runner); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/th_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SOCKET_H 2 | #define TH_SOCKET_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "th_context.h" 9 | #include "th_file.h" 10 | #include "th_io_service.h" 11 | #include "th_utility.h" 12 | 13 | typedef struct th_address { 14 | struct sockaddr_storage addr; 15 | socklen_t addrlen; 16 | } th_address; 17 | 18 | TH_PRIVATE(void) 19 | th_address_init(th_address* addr); 20 | 21 | /* th_socket_handler begin */ 22 | 23 | typedef th_io_handler th_socket_handler; 24 | #define th_socket_handler_init th_io_handler_init 25 | #define th_socket_handler_complete th_io_handler_complete 26 | 27 | /* th_socket_task_handler end */ 28 | /* th_socket begin */ 29 | 30 | typedef struct th_socket_methods { 31 | void (*set_fd)(void* self, int fd); 32 | void (*cancel)(void* self); 33 | th_allocator* (*get_allocator)(void* self); 34 | th_context* (*get_context)(void* self); 35 | void (*async_write)(void* self, void* addr, size_t len, th_socket_handler* handler); 36 | void (*async_writev)(void* self, th_iov* iov, size_t len, th_socket_handler* handler); 37 | void (*async_read)(void* self, void* addr, size_t len, th_socket_handler* handler); 38 | void (*async_readv)(void* self, th_iov* iov, size_t len, th_socket_handler* handler); 39 | void (*async_sendfile)(void* self, th_iov* header, size_t iovcnt, 40 | th_file* stream, size_t offset, size_t len, th_socket_handler* handler); 41 | } th_socket_methods; 42 | 43 | typedef struct th_socket { 44 | const th_socket_methods* methods; 45 | } th_socket; 46 | 47 | TH_INLINE(void) 48 | th_socket_set_fd(th_socket* socket, int fd) 49 | { 50 | socket->methods->set_fd(socket, fd); 51 | } 52 | 53 | TH_INLINE(void) 54 | th_socket_cancel(th_socket* socket) 55 | { 56 | socket->methods->cancel(socket); 57 | } 58 | 59 | TH_INLINE(th_allocator*) 60 | th_socket_get_allocator(th_socket* socket) 61 | { 62 | return socket->methods->get_allocator(socket); 63 | } 64 | 65 | TH_INLINE(th_context*) 66 | th_socket_get_context(th_socket* socket) 67 | { 68 | return socket->methods->get_context(socket); 69 | } 70 | 71 | TH_INLINE(void) 72 | th_socket_async_write(th_socket* sock, void* addr, size_t len, th_socket_handler* handler) 73 | { 74 | sock->methods->async_write(sock, addr, len, handler); 75 | } 76 | 77 | TH_INLINE(void) 78 | th_socket_async_writev(th_socket* sock, th_iov* iov, size_t len, th_socket_handler* handler) 79 | { 80 | sock->methods->async_writev(sock, iov, len, handler); 81 | } 82 | 83 | TH_INLINE(void) 84 | th_socket_async_read(th_socket* sock, void* addr, size_t len, th_socket_handler* handler) 85 | { 86 | sock->methods->async_read(sock, addr, len, handler); 87 | } 88 | 89 | TH_INLINE(void) 90 | th_socket_async_readv(th_socket* sock, th_iov* iov, size_t len, th_socket_handler* handler) 91 | { 92 | sock->methods->async_readv(sock, iov, len, handler); 93 | } 94 | 95 | TH_INLINE(void) 96 | th_socket_async_sendfile(th_socket* sock, th_iov* header, size_t iovcnt, 97 | th_file* stream, size_t offset, size_t len, th_socket_handler* handler) 98 | { 99 | sock->methods->async_sendfile(sock, header, iovcnt, stream, offset, len, handler); 100 | } 101 | 102 | /* th_socket end */ 103 | /** generic socket functions begin */ 104 | 105 | TH_PRIVATE(void) 106 | th_socket_async_write_exact(th_socket* sock, void* addr, size_t len, th_socket_handler* handler) TH_MAYBE_UNUSED; 107 | 108 | TH_PRIVATE(void) 109 | th_socket_async_writev_exact(th_socket* sock, th_iov* iov, size_t len, th_socket_handler* handler); 110 | 111 | TH_PRIVATE(void) 112 | th_socket_async_read_exact(th_socket* sock, void* addr, size_t len, th_socket_handler* handler); 113 | 114 | TH_PRIVATE(void) 115 | th_socket_async_readv_exact(th_socket* sock, th_iov* iov, size_t len, th_socket_handler* handler) TH_MAYBE_UNUSED; 116 | 117 | TH_PRIVATE(void) 118 | th_socket_async_sendfile_exact(th_socket* sock, th_iov* iov, size_t iovcnt, th_file* stream, size_t offset, size_t len, th_socket_handler* handler); 119 | 120 | /* th_socket functionss end */ 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /src/th_ssl_context.c: -------------------------------------------------------------------------------- 1 | #include "th_ssl_context.h" 2 | 3 | #if TH_WITH_SSL 4 | 5 | #include "th_log.h" 6 | #include "th_ssl_error.h" 7 | 8 | #include 9 | #include 10 | 11 | #undef TH_LOG_TAG 12 | #define TH_LOG_TAG "ssl_context" 13 | 14 | TH_PRIVATE(th_err) 15 | th_ssl_context_init(th_ssl_context* context, const char* key, const char* cert) 16 | { 17 | SSL_load_error_strings(); 18 | OpenSSL_add_ssl_algorithms(); 19 | 20 | context->ctx = SSL_CTX_new(TLS_server_method()); 21 | if (!context->ctx) { 22 | TH_LOG_FATAL("Failed to create SSL context"); 23 | goto cleanup; 24 | } 25 | 26 | if (SSL_CTX_use_certificate_chain_file(context->ctx, cert) <= 0) { 27 | TH_LOG_FATAL("Failed to load certificate file"); 28 | goto cleanup; 29 | } 30 | 31 | if (SSL_CTX_use_PrivateKey_file(context->ctx, key, SSL_FILETYPE_PEM) <= 0) { 32 | TH_LOG_FATAL("Failed to load private key file"); 33 | goto cleanup; 34 | } 35 | 36 | if (!SSL_CTX_set_min_proto_version(context->ctx, TLS1_3_VERSION)) { 37 | TH_LOG_FATAL("Failed to set minimum protocol version"); 38 | goto cleanup; 39 | } 40 | 41 | if (SSL_CTX_set_cipher_list(context->ctx, "MEDIUM:HIGH:!aNULL!MD5:!RC4!3DES") <= 0) { 42 | TH_LOG_FATAL("Failed to set cipher list"); 43 | goto cleanup; 44 | } 45 | 46 | SSL_CTX_set_session_cache_mode(context->ctx, SSL_SESS_CACHE_OFF); 47 | context->smem_method = NULL; 48 | return TH_ERR_OK; 49 | cleanup: 50 | if (context->ctx) { 51 | SSL_CTX_free(context->ctx); 52 | context->ctx = NULL; 53 | } 54 | return th_ssl_handle_error_stack(); 55 | } 56 | 57 | TH_PRIVATE(void) 58 | th_ssl_context_deinit(th_ssl_context* context) 59 | { 60 | if (context->smem_method) 61 | BIO_meth_free(context->smem_method); 62 | if (context->ctx) 63 | SSL_CTX_free(context->ctx); 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /src/th_ssl_context.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SSL_CONTEXT_H 2 | #define TH_SSL_CONTEXT_H 3 | 4 | #include "th_config.h" 5 | 6 | #if TH_WITH_SSL 7 | #include 8 | 9 | #include 10 | 11 | typedef struct th_ssl_context { 12 | SSL_CTX* ctx; 13 | BIO_METHOD* smem_method; 14 | } th_ssl_context; 15 | 16 | TH_PRIVATE(th_err) 17 | th_ssl_context_init(th_ssl_context* context, const char* key, const char* cert); 18 | 19 | TH_PRIVATE(void) 20 | th_ssl_context_deinit(th_ssl_context* context); 21 | 22 | #endif 23 | #endif 24 | -------------------------------------------------------------------------------- /src/th_ssl_error.c: -------------------------------------------------------------------------------- 1 | #include "th_ssl_error.h" 2 | 3 | #if TH_WITH_SSL 4 | 5 | #include "th_log.h" 6 | 7 | #include 8 | #include 9 | 10 | #undef TH_LOG_TAG 11 | #define TH_LOG_TAG "ssl" 12 | 13 | TH_PRIVATE(void) 14 | th_ssl_log_error_stack(void) 15 | { 16 | unsigned long code; 17 | while ((code = ERR_get_error())) { 18 | TH_LOG_ERROR("%s", ERR_reason_error_string(code)); 19 | } 20 | } 21 | 22 | TH_PRIVATE(const char*) 23 | th_ssl_strerror(int code) 24 | { 25 | switch (code) { 26 | case SSL_ERROR_NONE: 27 | return "Success"; 28 | break; 29 | case SSL_ERROR_SSL: 30 | return "SSL library error, enable logging for more details"; 31 | break; 32 | default: 33 | break; 34 | } 35 | return ERR_reason_error_string((unsigned long)code); 36 | } 37 | 38 | TH_PRIVATE(th_err) 39 | th_ssl_handle_error_stack(void) 40 | { 41 | th_ssl_log_error_stack(); 42 | return TH_ERR_SSL(SSL_ERROR_SSL); 43 | } 44 | 45 | #endif // TH_WITH_SSL 46 | -------------------------------------------------------------------------------- /src/th_ssl_error.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SSL_ERROR_H 2 | #define TH_SSL_ERROR_H 3 | 4 | #include "th_config.h" 5 | 6 | #if TH_WITH_SSL 7 | #include 8 | 9 | #include "th_config.h" 10 | 11 | TH_PRIVATE(void) 12 | th_ssl_log_error_stack(void); 13 | 14 | TH_PRIVATE(const char*) 15 | th_ssl_strerror(int code); 16 | 17 | TH_PRIVATE(th_err) 18 | th_ssl_handle_error_stack(void); 19 | 20 | #endif // TH_WITH_SSL 21 | #endif 22 | -------------------------------------------------------------------------------- /src/th_ssl_smem_bio.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SSL_SMEM_BIO_H 2 | #define TH_SSL_SMEM_BIO_H 3 | 4 | #include "th_config.h" 5 | 6 | #if TH_WITH_SSL 7 | 8 | #include 9 | 10 | #include "th_allocator.h" 11 | #include "th_iov.h" 12 | #include "th_ssl_context.h" 13 | 14 | TH_PRIVATE(BIO_METHOD*) 15 | th_smem_bio(th_ssl_context* ssl_context); 16 | 17 | TH_PRIVATE(void) 18 | th_smem_bio_setup_buf(BIO* bio, th_allocator* allocator, size_t max_len); 19 | 20 | TH_PRIVATE(size_t) 21 | th_smem_ensure_buf_size(BIO* bio, size_t size); 22 | 23 | TH_PRIVATE(void) 24 | th_smem_bio_set_eof(BIO* bio); 25 | 26 | TH_PRIVATE(void) 27 | th_smem_bio_get_rdata(BIO* bio, th_iov* buf); 28 | 29 | TH_PRIVATE(void) 30 | th_smem_bio_get_wbuf(BIO* bio, th_iov* buf); 31 | 32 | TH_PRIVATE(void) 33 | th_smem_bio_inc_read_pos(BIO* bio, size_t len); 34 | 35 | TH_PRIVATE(void) 36 | th_smem_bio_inc_write_pos(BIO* bio, size_t len); 37 | 38 | #endif 39 | #endif 40 | -------------------------------------------------------------------------------- /src/th_ssl_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SSL_SOCKET_H 2 | #define TH_SSL_SOCKET_H 3 | 4 | #include "th_config.h" 5 | 6 | #if TH_WITH_SSL 7 | 8 | #include "th_socket.h" 9 | #include "th_ssl_context.h" 10 | #include "th_tcp_socket.h" 11 | 12 | #include 13 | 14 | /* th_ssl_socket begin */ 15 | 16 | typedef struct th_ssl_socket { 17 | th_socket base; 18 | th_tcp_socket tcp_socket; 19 | SSL* ssl; 20 | BIO* wbio; // ssl output buffer 21 | BIO* rbio; // ssl input buffer 22 | } th_ssl_socket; 23 | 24 | typedef enum th_ssl_socket_mode { 25 | TH_SSL_SOCKET_MODE_SERVER, 26 | TH_SSL_SOCKET_MODE_CLIENT 27 | } th_ssl_socket_mode; 28 | 29 | TH_PRIVATE(th_err) 30 | th_ssl_socket_init(th_ssl_socket* socket, th_context* context, th_ssl_context* ssl_context, th_allocator* allocator); 31 | 32 | /** ssl socket specific functions */ 33 | 34 | TH_PRIVATE(void) 35 | th_ssl_socket_set_mode(th_ssl_socket* socket, th_ssl_socket_mode mode); 36 | 37 | TH_PRIVATE(void) 38 | th_ssl_socket_async_handshake(th_ssl_socket* socket, th_socket_handler* handler); 39 | 40 | TH_PRIVATE(void) 41 | th_ssl_socket_async_shutdown(th_ssl_socket* socket, th_socket_handler* handler); 42 | 43 | /** th_socket_close 44 | * @brief Closes the underlying file descriptor of the socket. 45 | * while the socket object is still valid and can be reused. 46 | */ 47 | TH_PRIVATE(void) 48 | th_ssl_socket_close(th_ssl_socket* socket); 49 | 50 | TH_PRIVATE(void) 51 | th_ssl_socket_deinit(th_ssl_socket* socket); 52 | 53 | #endif 54 | #endif 55 | -------------------------------------------------------------------------------- /src/th_ssl_socket_test.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphiaRa/tiny_http/f558425d448104ae751d2183aee0593075915c6b/src/th_ssl_socket_test.c -------------------------------------------------------------------------------- /src/th_string.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "th_config.h" 6 | #include "th_hash.h" 7 | #include "th_string.h" 8 | 9 | size_t th_string_npos = (size_t)-1; 10 | 11 | TH_PRIVATE(bool) 12 | th_string_is_uint(th_string str) 13 | { 14 | for (size_t i = 0; i < str.len; i++) { 15 | if (str.ptr[i] < '0' || str.ptr[i] > '9') { 16 | return false; 17 | } 18 | } 19 | return true; 20 | } 21 | 22 | TH_PRIVATE(th_err) 23 | th_string_to_uint(th_string str, unsigned int* out) 24 | { 25 | *out = 0; 26 | for (size_t i = 0; i < str.len; i++) { 27 | if (str.ptr[i] < '0' || str.ptr[i] > '9') 28 | return TH_ERR_INVALID_ARG; 29 | *out = *out * 10 + (str.ptr[i] - '0'); 30 | } 31 | return TH_ERR_OK; 32 | } 33 | 34 | TH_PRIVATE(bool) 35 | th_string_eq(th_string a, th_string b) 36 | { 37 | if (a.len != b.len) { 38 | return 0; 39 | } 40 | for (size_t i = 0; i < a.len; i++) { 41 | if (a.ptr[i] != b.ptr[i]) { 42 | return 0; 43 | } 44 | } 45 | return 1; 46 | } 47 | 48 | TH_PRIVATE(size_t) 49 | th_string_find_first(th_string str, size_t start, char c) 50 | { 51 | for (size_t i = start; i < str.len; i++) { 52 | if (str.ptr[i] == c) { 53 | return i; 54 | } 55 | } 56 | return th_string_npos; 57 | } 58 | 59 | TH_PRIVATE(size_t) 60 | th_string_find_first_not(th_string str, size_t start, char c) 61 | { 62 | for (size_t i = start; i < str.len; i++) { 63 | if (str.ptr[i] != c) { 64 | return i; 65 | } 66 | } 67 | return th_string_npos; 68 | } 69 | 70 | TH_PRIVATE(size_t) 71 | th_string_find_first_of(th_string str, size_t start, const char* chars) 72 | { 73 | for (size_t i = start; i < str.len; i++) { 74 | for (size_t j = 0; chars[j] != '\0'; j++) { 75 | if (str.ptr[i] == chars[j]) { 76 | return i; 77 | } 78 | } 79 | } 80 | return th_string_npos; 81 | } 82 | 83 | TH_PRIVATE(size_t) 84 | th_string_find_last(th_string str, size_t start, char c) 85 | { 86 | for (size_t i = start; i < str.len; i++) { 87 | if (str.ptr[str.len - i - 1] == c) { 88 | return i; 89 | } 90 | } 91 | return th_string_npos; 92 | } 93 | 94 | TH_PRIVATE(th_string) 95 | th_string_substr(th_string str, size_t start, size_t len) 96 | { 97 | if (start >= str.len) { 98 | return th_string_make(str.ptr + len, 0); 99 | } 100 | if (len == th_string_npos || start + len > str.len) { 101 | len = str.len - start; 102 | } 103 | return th_string_make(str.ptr + start, len); 104 | } 105 | 106 | TH_PRIVATE(th_string) 107 | th_string_trim(th_string str) 108 | { 109 | size_t start = 0; 110 | while (start < str.len && (str.ptr[start] == ' ' || str.ptr[start] == '\t')) { 111 | start++; 112 | } 113 | size_t end = str.len; 114 | while (end > start && (str.ptr[end - 1] == ' ' || str.ptr[end - 1] == '\t')) { 115 | end--; 116 | } 117 | return th_string_substr(str, start, end - start); 118 | } 119 | 120 | TH_PRIVATE(uint32_t) 121 | th_string_hash(th_string str) 122 | { 123 | return th_hash_bytes(str.ptr, str.len); 124 | } 125 | -------------------------------------------------------------------------------- /src/th_string.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_STRING_H 2 | #define TH_STRING_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "th_config.h" 10 | 11 | extern size_t th_string_npos; 12 | 13 | typedef struct th_string { 14 | const char* ptr; 15 | size_t len; 16 | } th_string; 17 | 18 | /** th_string_make 19 | * @brief Helper function to create a th_string from a pointer and a length. 20 | */ 21 | TH_INLINE(th_string) 22 | th_string_make(const char* ptr, size_t len) 23 | { 24 | return (th_string){ptr, len}; 25 | } 26 | 27 | /** th_string_make_empty 28 | * @brief Helper function to create an empty th_string. 29 | */ 30 | TH_INLINE(th_string) 31 | th_string_make_empty(void) 32 | { 33 | return (th_string){"", 0}; 34 | } 35 | 36 | /** th_string_from_cstr 37 | * @brief Helper function to create a th_string from a null-terminated string. 38 | */ 39 | TH_INLINE(th_string) 40 | th_string_from_cstr(const char* str) 41 | { 42 | return th_string_make(str, strlen(str)); 43 | } 44 | 45 | /** th_string_eq 46 | * @brief Helper function to compare two th_strings. 47 | * @return 1 if the strings are equal, 0 otherwise. 48 | */ 49 | TH_PRIVATE(bool) 50 | th_string_eq(th_string a, th_string b); 51 | 52 | /** th_string_empty 53 | * @brief Helper function to check if a th_string is empty. 54 | * @return true if the string is empty, false otherwise. 55 | */ 56 | TH_INLINE(bool) 57 | th_string_empty(th_string str) 58 | { 59 | return str.len == 0; 60 | } 61 | 62 | /** TH_STRING_INIT 63 | * @brief Helper macro to initialize a th_string from string literal. 64 | */ 65 | #define TH_STRING_INIT(str) {"" str, sizeof(str) - 1} 66 | 67 | /** TH_STRING 68 | * @brief Helper macro to create a th_string compound literal from a string literal. 69 | */ 70 | #define TH_STRING(str) ((th_string){"" str, sizeof(str) - 1}) 71 | 72 | /** TH_STRING_EQ 73 | * @brief Helper macro to compare a th_string with a string literal. 74 | */ 75 | #define TH_STRING_EQ(str, cmp) (th_string_eq(str, TH_STRING(cmp))) 76 | 77 | TH_PRIVATE(bool) 78 | th_string_is_uint(th_string str); 79 | 80 | TH_PRIVATE(th_err) 81 | th_string_to_uint(th_string str, unsigned int* out); 82 | 83 | TH_PRIVATE(size_t) 84 | th_string_find_first(th_string str, size_t start, char c); 85 | 86 | TH_PRIVATE(size_t) 87 | th_string_find_first_not(th_string str, size_t start, char c); 88 | 89 | TH_PRIVATE(size_t) 90 | th_string_find_first_of(th_string str, size_t start, const char* chars); 91 | 92 | TH_PRIVATE(size_t) 93 | th_string_find_last(th_string str, size_t start, char c); 94 | 95 | /** th_string_substr 96 | * @brief Returns a substring of a string. 97 | * If len == th_string_npos, the substring will go to the end of the string. 98 | * If start > len, an empty string is returned (ptr = str.ptr + str.len, len = 0). 99 | */ 100 | TH_PRIVATE(th_string) 101 | th_string_substr(th_string str, size_t start, size_t len); 102 | 103 | /** th_string_trim 104 | * @brief Removes leading and trailing whitespace from a string. 105 | * This doesn't modify the original string, just returns a new view of it. 106 | * @param str The string to trim. 107 | * @return A new string view with leading and trailing whitespace removed. 108 | */ 109 | TH_PRIVATE(th_string) 110 | th_string_trim(th_string str); 111 | 112 | TH_PRIVATE(uint32_t) 113 | th_string_hash(th_string str); 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /src/th_string_test.c: -------------------------------------------------------------------------------- 1 | #include "th_string.h" 2 | #include "th_test.h" 3 | 4 | TH_TEST_BEGIN(string) 5 | { 6 | TH_TEST_CASE_BEGIN(string_make_literal) 7 | { 8 | th_string str = TH_STRING("TEST"); 9 | TH_EXPECT(str.len == 4); 10 | TH_EXPECT(str.ptr[0] == 'T'); 11 | TH_EXPECT(str.ptr[1] == 'E'); 12 | TH_EXPECT(str.ptr[2] == 'S'); 13 | TH_EXPECT(str.ptr[3] == 'T'); 14 | } 15 | TH_TEST_CASE_END 16 | TH_TEST_CASE_BEGIN(string_make) 17 | { 18 | const char* test_string = "Test String"; 19 | th_string str = th_string_make(test_string, 4); 20 | TH_EXPECT(str.len == 4); 21 | TH_EXPECT(str.ptr[0] == 'T'); 22 | TH_EXPECT(str.ptr[1] == 'e'); 23 | TH_EXPECT(str.ptr[2] == 's'); 24 | TH_EXPECT(str.ptr[3] == 't'); 25 | } 26 | TH_TEST_CASE_END 27 | TH_TEST_CASE_BEGIN(string_trim) 28 | { 29 | th_string str = TH_STRING(" Test String "); 30 | th_string trimmed = th_string_trim(str); 31 | TH_EXPECT(TH_STRING_EQ(trimmed, "Test String")); 32 | } 33 | TH_TEST_CASE_END 34 | } 35 | TH_TEST_END 36 | -------------------------------------------------------------------------------- /src/th_system_error.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_SYSTEM_ERR_H 2 | #define TH_SYSTEM_ERR_H 3 | 4 | #include "th_config.h" 5 | 6 | #if defined(TH_CONFIG_OS_POSIX) 7 | #include 8 | #include 9 | #elif defined(TH_CONFIG_OS_WIN) 10 | #include 11 | #endif 12 | 13 | TH_INLINE(const char*) 14 | th_system_strerror(int errc) TH_MAYBE_UNUSED; 15 | 16 | TH_INLINE(const char*) 17 | th_system_strerror(int errc) 18 | { 19 | #if defined(TH_CONFIG_OS_POSIX) 20 | return strerror(errc); 21 | #elif defined(TH_CONFIG_OS_WIN) 22 | static char buf[256]; 23 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL); 24 | return buf; 25 | #elif defined(TH_CONFIG_OS_MOCK) 26 | (void)errc; 27 | return "mock error"; 28 | #endif 29 | } 30 | 31 | /* Define the system error codes that we use */ 32 | #if defined(TH_CONFIG_OS_POSIX) 33 | #define TH_ENOENT ENOENT 34 | #define TH_EINTR EINTR 35 | #define TH_EIO EIO 36 | #define TH_EBADF EBADF 37 | #define TH_EBUSY EBUSY 38 | #define TH_EAGAIN EAGAIN 39 | #define TH_EWOULDBLOCK EWOULDBLOCK 40 | #define TH_ENOMEM ENOMEM 41 | #define TH_ENOSYS ENOSYS 42 | #define TH_ETIMEDOUT ETIMEDOUT 43 | #define TH_ECANCELED ECANCELED 44 | #elif defined(TH_CONFIG_OS_WIN) 45 | #define TH_ENOENT ERROR_FILE_NOT_FOUND 46 | #define TH_EINTR ERROR_INTERRUPT 47 | #define TH_EIO ERROR_IO_DEVICE 48 | #define TH_EBADF ERROR_BAD_FORMAT 49 | #define TH_EBUSY ERROR_BUSY 50 | #define TH_EAGAIN ERROR_RETRY 51 | #define TH_EWOULDBLOCK ERROR_RETRY 52 | #define TH_ENOMEM ERROR_OUTOFMEMORY 53 | #define TH_ENOSYS ERROR_NOT_SUPPORTED 54 | #define TH_ETIMEDOUT ERROR_TIMEOUT 55 | #define TH_ECANCELED ERROR_CANCELLED 56 | #elif defined(TH_CONFIG_OS_MOCK) 57 | #define TH_ENOENT 1 58 | #define TH_EINTR 2 59 | #define TH_EIO 3 60 | #define TH_EBUSY 4 61 | #define TH_EAGAIN 5 62 | #define TH_EWOULDBLOCK 6 63 | #define TH_ENOMEM 7 64 | #define TH_ENOSYS 8 65 | #define TH_ETIMEDOUT 9 66 | #define TH_ECANCELED 10 67 | #define TH_EBADF 11 68 | #endif 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/th_task.c: -------------------------------------------------------------------------------- 1 | #include "th_task.h" 2 | #include "th_allocator.h" 3 | #include "th_utility.h" 4 | 5 | #include 6 | #include 7 | 8 | /* th_task functions begin */ 9 | 10 | TH_PRIVATE(void) 11 | th_task_init(th_task* task, void (*fn)(void*), void (*destroy)(void*)) 12 | { 13 | TH_ASSERT(task); 14 | task->fn = fn; 15 | task->destroy = destroy; 16 | task->next = NULL; 17 | } 18 | 19 | TH_PRIVATE(void) 20 | th_task_complete(th_task* task) 21 | { 22 | if (task->fn) 23 | task->fn(task); 24 | } 25 | 26 | TH_PRIVATE(void) 27 | th_task_destroy(th_task* task) 28 | { 29 | if (task->destroy) 30 | task->destroy(task); 31 | } 32 | 33 | /* th_task functions end */ 34 | -------------------------------------------------------------------------------- /src/th_task.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_TASK_H 2 | #define TH_TASK_H 3 | 4 | #include 5 | #include 6 | 7 | #include "th_allocator.h" 8 | #include "th_queue.h" 9 | 10 | typedef struct th_task { 11 | /** fn 12 | * @brief The function to execute. 13 | */ 14 | void (*fn)(void* self); 15 | 16 | /** destroy 17 | * @brief The destructor for the th_task. 18 | * Can be NULL if the th_task does not need to be destroyed. 19 | */ 20 | void (*destroy)(void* self); 21 | 22 | /** This is used internally by the runner. */ 23 | struct th_task* next; 24 | } th_task; 25 | 26 | /** th_task_init 27 | * @brief Initializes a task. 28 | */ 29 | TH_PRIVATE(void) 30 | th_task_init(th_task* task, void (*fn)(void* self), void (*destroy)(void* self)); 31 | 32 | /** th_task complete 33 | * @brief Runs the task. 34 | */ 35 | TH_PRIVATE(void) 36 | th_task_complete(th_task* task); 37 | 38 | /** th_task_destroy 39 | * @brief Destroys the task, if the task has a destroy function. 40 | */ 41 | TH_PRIVATE(void) 42 | th_task_destroy(th_task* task); 43 | 44 | /* th_task_queue declarations begin */ 45 | 46 | #ifndef TH_TASK_QUEUE 47 | #define TH_TASK_QUEUE 48 | TH_DEFINE_QUEUE(th_task_queue, th_task) 49 | #endif 50 | 51 | /* th_task_queue declarations end */ 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/th_task_test.c: -------------------------------------------------------------------------------- 1 | #include "th_task.h" 2 | #include "th_test.h" 3 | 4 | static void mock_fn(void* data) 5 | { 6 | (void)data; 7 | } 8 | 9 | TH_TEST_BEGIN(task) 10 | { 11 | TH_TEST_CASE_BEGIN(task_init) 12 | { 13 | th_task task; 14 | th_task_init(&task, mock_fn, NULL); 15 | TH_EXPECT(task.fn == mock_fn); 16 | TH_EXPECT(task.destroy == NULL); 17 | TH_EXPECT(task.next == NULL); 18 | } 19 | TH_TEST_CASE_END 20 | TH_TEST_CASE_BEGIN(task_queue_init) 21 | { 22 | th_task_queue queue = {0}; 23 | TH_EXPECT(th_task_queue_empty(&queue)); 24 | TH_EXPECT(th_task_queue_pop(&queue) == NULL); 25 | } 26 | TH_TEST_CASE_END 27 | TH_TEST_CASE_BEGIN(task_queue_push_pop) 28 | { 29 | th_task_queue queue = {0}; 30 | th_task task1, task2; 31 | th_task_init(&task1, mock_fn, NULL); 32 | th_task_init(&task2, mock_fn, NULL); 33 | th_task_queue_push(&queue, &task1); 34 | TH_EXPECT(!th_task_queue_empty(&queue)); 35 | th_task_queue_push(&queue, &task2); 36 | TH_EXPECT(th_task_queue_pop(&queue) == &task1); 37 | TH_EXPECT(th_task_queue_pop(&queue) == &task2); 38 | } 39 | TH_TEST_CASE_END 40 | } 41 | TH_TEST_END 42 | -------------------------------------------------------------------------------- /src/th_tcp_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_TCP_SOCKET_H 2 | #define TH_TCP_SOCKET_H 3 | 4 | #include "th_socket.h" 5 | 6 | /* th_tcp_socket begin */ 7 | 8 | typedef struct th_tcp_socket { 9 | th_socket base; 10 | th_context* context; 11 | th_allocator* allocator; 12 | th_io_handle* handle; 13 | } th_tcp_socket; 14 | 15 | TH_PRIVATE(void) 16 | th_tcp_socket_init(th_tcp_socket* socket, th_context* context, th_allocator* allocator); 17 | 18 | /** th_socket_close 19 | * @brief Closes the underlying file descriptor of the socket. 20 | * while the socket object is still valid and can be reused. 21 | */ 22 | TH_PRIVATE(void) 23 | th_tcp_socket_close(th_tcp_socket* socket); 24 | 25 | TH_PRIVATE(void) 26 | th_tcp_socket_deinit(th_tcp_socket* socket); 27 | 28 | #define th_tcp_socket_set_fd(socket, fd) ((socket)->base.methods->set_fd((socket), (fd))) 29 | 30 | #define th_tcp_socket_cancel(socket) ((socket)->base.methods->cancel((socket))) 31 | 32 | #define th_tcp_socket_get_allocator(socket) ((socket)->base.methods->get_allocator((socket))) 33 | 34 | #define th_tcp_socket_get_context(socket) ((socket)->base.methods->get_context((socket))) 35 | 36 | #define th_tcp_socket_async_write(socket, addr, len, handler) ((socket)->base.methods->async_write((socket), (addr), (len), (handler))) 37 | 38 | #define th_tcp_socket_async_writev(socket, iov, iovcnt, handler) ((socket)->base.methods->async_writev((socket), (iov), (iovcnt), (handler))) 39 | 40 | #define th_tcp_socket_async_read(socket, addr, len, handler) ((socket)->base.methods->async_read((socket), (addr), (len), (handler))) 41 | 42 | #define th_tcp_socket_async_readv(socket, iov, iovcnt, handler) ((socket)->base.methods->async_readv((socket), (iov), (iovcnt), (handler))) 43 | 44 | #define th_tcp_socket_async_sendfile(socket, header, iovcnt, stream, offset, len, handler) ((socket)->base.methods->async_sendfile((socket), (header), (iovcnt), (stream), (offset), (len), (handler))) 45 | 46 | /* th_tcp_socket end */ 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/th_tcp_socket_test.c: -------------------------------------------------------------------------------- 1 | #include "th_mock_syscall.h" 2 | #include "th_tcp_socket.h" 3 | #include "th_test.h" 4 | 5 | #include 6 | 7 | static th_err last_err = TH_ERR_OK; 8 | static size_t last_result = 0; 9 | static void read_handler(void* data, size_t len, th_err err) 10 | { 11 | last_result = len; 12 | last_err = err; 13 | (void)data; 14 | } 15 | 16 | static int 17 | mock_read_eof(void* data, size_t len) 18 | { 19 | (void)data; 20 | (void)len; 21 | return 0; 22 | } 23 | 24 | static int 25 | mock_read_bad(void* data, size_t len) 26 | { 27 | (void)data; 28 | (void)len; 29 | return -TH_EIO; 30 | } 31 | 32 | static int 33 | mock_write_bad(size_t len) 34 | { 35 | 36 | (void)len; 37 | return -TH_EIO; 38 | } 39 | 40 | TH_TEST_BEGIN(tcp_socket) 41 | { 42 | TH_TEST_CASE_BEGIN(tcp_socket_init) 43 | { 44 | th_tcp_socket socket = {0}; 45 | th_tcp_socket_init(&socket, NULL, NULL); 46 | th_tcp_socket_deinit(&socket); 47 | } 48 | TH_TEST_CASE_END 49 | TH_TEST_CASE_BEGIN(tcp_socket_set_read_good) 50 | { 51 | th_context context = {0}; 52 | th_context_init(&context, NULL); 53 | th_tcp_socket socket = {0}; 54 | th_tcp_socket_init(&socket, &context, NULL); 55 | th_tcp_socket_set_fd(&socket, 0); 56 | char buf[512] = {0}; 57 | th_io_handler handler = {0}; 58 | th_io_handler_init(&handler, read_handler, NULL); 59 | th_tcp_socket_async_read(&socket, buf, sizeof(buf), &handler); 60 | while (1) { 61 | if (th_context_poll(&context, -1) != TH_ERR_OK) 62 | break; 63 | } 64 | TH_EXPECT(last_err == TH_ERR_OK); 65 | TH_EXPECT(last_result == sizeof(buf)); 66 | th_tcp_socket_deinit(&socket); 67 | th_context_deinit(&context); 68 | } 69 | TH_TEST_CASE_END 70 | TH_TEST_CASE_BEGIN(tcp_socket_read_eof) 71 | { 72 | th_context context = {0}; 73 | th_context_init(&context, NULL); 74 | th_tcp_socket socket = {0}; 75 | th_tcp_socket_init(&socket, &context, NULL); 76 | th_tcp_socket_set_fd(&socket, 0); 77 | char buf[512] = {0}; 78 | th_io_handler handler = {0}; 79 | th_io_handler_init(&handler, read_handler, NULL); 80 | th_mock_syscall_get()->read = mock_read_eof; 81 | th_tcp_socket_async_read(&socket, buf, sizeof(buf), &handler); 82 | while (1) { 83 | if (th_context_poll(&context, -1) != TH_ERR_OK) 84 | break; 85 | } 86 | TH_EXPECT(last_err == TH_ERR_EOF); 87 | th_tcp_socket_deinit(&socket); 88 | th_context_deinit(&context); 89 | } 90 | TH_TEST_CASE_END 91 | TH_TEST_CASE_BEGIN(tcp_socket_read_bad) 92 | { 93 | th_context context = {0}; 94 | th_context_init(&context, NULL); 95 | th_tcp_socket socket = {0}; 96 | th_tcp_socket_init(&socket, &context, NULL); 97 | th_tcp_socket_set_fd(&socket, 0); 98 | char buf[512] = {0}; 99 | th_io_handler handler = {0}; 100 | th_io_handler_init(&handler, read_handler, NULL); 101 | th_mock_syscall_get()->read = mock_read_bad; 102 | th_tcp_socket_async_read(&socket, buf, sizeof(buf), &handler); 103 | while (1) { 104 | if (th_context_poll(&context, -1) != TH_ERR_OK) 105 | break; 106 | } 107 | TH_EXPECT(last_err == TH_ERR_SYSTEM(EIO)); 108 | th_tcp_socket_deinit(&socket); 109 | th_context_deinit(&context); 110 | } 111 | TH_TEST_CASE_END 112 | TH_TEST_CASE_BEGIN(tcp_socket_write_good) 113 | { 114 | th_context context = {0}; 115 | th_context_init(&context, NULL); 116 | th_tcp_socket socket = {0}; 117 | th_tcp_socket_init(&socket, &context, NULL); 118 | th_tcp_socket_set_fd(&socket, 0); 119 | char buf[512] = {0}; 120 | th_io_handler handler = {0}; 121 | th_io_handler_init(&handler, read_handler, NULL); 122 | th_tcp_socket_async_write(&socket, buf, sizeof(buf), &handler); 123 | while (1) { 124 | if (th_context_poll(&context, -1) != TH_ERR_OK) 125 | break; 126 | } 127 | TH_EXPECT(last_err == TH_ERR_OK); 128 | TH_EXPECT(last_result == sizeof(buf)); 129 | th_tcp_socket_deinit(&socket); 130 | th_context_deinit(&context); 131 | } 132 | TH_TEST_CASE_END 133 | TH_TEST_CASE_BEGIN(tcp_socket_write_bad) 134 | { 135 | th_context context = {0}; 136 | th_context_init(&context, NULL); 137 | th_tcp_socket socket = {0}; 138 | th_tcp_socket_init(&socket, &context, NULL); 139 | th_tcp_socket_set_fd(&socket, 0); 140 | char buf[512] = {0}; 141 | th_io_handler handler = {0}; 142 | th_io_handler_init(&handler, read_handler, NULL); 143 | th_mock_syscall_get()->write = mock_write_bad; 144 | th_tcp_socket_async_write(&socket, buf, sizeof(buf), &handler); 145 | while (1) { 146 | if (th_context_poll(&context, -1) != TH_ERR_OK) 147 | break; 148 | } 149 | TH_EXPECT(last_err == TH_ERR_SYSTEM(EIO)); 150 | th_tcp_socket_deinit(&socket); 151 | th_context_deinit(&context); 152 | } 153 | TH_TEST_CASE_END 154 | } 155 | TH_TEST_END 156 | -------------------------------------------------------------------------------- /src/th_test.c: -------------------------------------------------------------------------------- 1 | #include "th_test.h" 2 | #include "th_allocator.h" 3 | 4 | #include 5 | #include 6 | 7 | typedef struct th_alloc_list { 8 | void* ptr; 9 | struct th_alloc_list* next; 10 | } th_alloc_list; 11 | 12 | typedef struct th_test_allocator { 13 | th_allocator base; 14 | th_alloc_list* list; 15 | } th_test_allocator; 16 | 17 | static void* 18 | th_test_allocator_alloc(void* self, size_t size) 19 | { 20 | th_test_allocator* allocator = self; 21 | void* ptr = calloc(1, size); 22 | if (!ptr) 23 | return NULL; 24 | th_alloc_list* node = calloc(1, sizeof(th_alloc_list)); 25 | if (!node) { 26 | free(ptr); 27 | return NULL; 28 | } 29 | node->ptr = ptr; 30 | node->next = allocator->list; 31 | allocator->list = node; 32 | return ptr; 33 | } 34 | 35 | static void* 36 | th_test_allocator_realloc(void* self, void* ptr, size_t size) 37 | { 38 | th_test_allocator* allocator = self; 39 | for (th_alloc_list* node = allocator->list; node != NULL; node = node->next) { 40 | if (node->ptr == ptr) { 41 | void* new_ptr = realloc(ptr, size); 42 | if (!new_ptr) 43 | return NULL; 44 | node->ptr = new_ptr; 45 | return new_ptr; 46 | } 47 | } 48 | return th_test_allocator_alloc(self, size); 49 | } 50 | 51 | static void 52 | th_test_allocator_free(void* self, void* ptr) 53 | { 54 | th_test_allocator* allocator = self; 55 | th_alloc_list* prev = NULL; 56 | for (th_alloc_list* node = allocator->list; node != NULL; node = node->next) { 57 | if (node->ptr == ptr) { 58 | if (prev) 59 | prev->next = node->next; 60 | else 61 | allocator->list = node->next; 62 | free(node->ptr); 63 | free(node); 64 | return; 65 | } 66 | prev = node; 67 | } 68 | assert(0 && "th_test_allocator_free: invalid pointer"); 69 | } 70 | 71 | int th_test_allocator_outstanding(void) 72 | { 73 | th_test_allocator* allocator = (th_test_allocator*)th_default_allocator_get(); 74 | int count = 0; 75 | for (th_alloc_list* node = allocator->list; node != NULL; node = node->next) 76 | ++count; 77 | return count; 78 | } 79 | 80 | void th_test_setup(void) 81 | { 82 | static th_test_allocator allocator = { 83 | .base = { 84 | .alloc = th_test_allocator_alloc, 85 | .realloc = th_test_allocator_realloc, 86 | .free = th_test_allocator_free, 87 | }, 88 | .list = NULL, 89 | }; 90 | th_default_allocator_set(&allocator.base); 91 | } 92 | 93 | void th_test_teardown(void) 94 | { 95 | th_test_allocator* allocator = (th_test_allocator*)th_default_allocator_get(); 96 | for (th_alloc_list* node = allocator->list; node != NULL;) { 97 | th_alloc_list* next = node->next; 98 | free(node->ptr); 99 | free(node); 100 | node = next; 101 | } 102 | allocator->list = NULL; 103 | th_default_allocator_set(NULL); 104 | } 105 | -------------------------------------------------------------------------------- /src/th_test.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_TEST_H 2 | #define TH_TEST_H 3 | 4 | #include 5 | 6 | typedef enum { 7 | TH_TEST_SUCCESS = 0, 8 | TH_TEST_FAILURE = -1, 9 | } th_test_result; 10 | 11 | #define TH_EXPECT(x) \ 12 | if ((x) == 0) { \ 13 | printf("Test failed: %s, at %s:%d\n", #x, __FILE__, __LINE__); \ 14 | return TH_TEST_FAILURE; \ 15 | } 16 | 17 | /** th_test_setup 18 | * @brief Setup the test environment. This function is called before any test 19 | * cases are run. and does the following: 20 | * - Initializes the test_allocator and sets it as the default allocator. 21 | */ 22 | void th_test_setup(void); 23 | 24 | void th_test_teardown(void); 25 | 26 | /** th_test_allocator_outstanding 27 | * @brief Check if there are outstanding allocations. 28 | * @return The number of outstanding allocations. 29 | */ 30 | int th_test_allocator_outstanding(void); 31 | 32 | #define TH_TEST_BEGIN(name) \ 33 | int src_th_##name##_test(int argc, char** argv) \ 34 | { \ 35 | (void)argc; \ 36 | (void)argv; 37 | 38 | #define TH_TEST_END \ 39 | return TH_TEST_SUCCESS; \ 40 | } 41 | 42 | #define TH_TEST_CASE_BEGIN(name) \ 43 | { \ 44 | printf("Running test-case: %40s", #name); 45 | 46 | #define TH_TEST_CASE_END \ 47 | int outstanding = th_test_allocator_outstanding(); \ 48 | if (outstanding != 0) { \ 49 | printf(" Memory leak detected: %d allocations\n", outstanding); \ 50 | return TH_TEST_FAILURE; \ 51 | } \ 52 | printf(" passed\n"); \ 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/th_timer.c: -------------------------------------------------------------------------------- 1 | #include "th_timer.h" 2 | #include "th_config.h" 3 | #include "th_utility.h" 4 | 5 | #ifdef TH_CONFIG_OS_POSIX 6 | #include 7 | #include 8 | #elif defined(TH_CONFIG_OS_WIN) 9 | #include 10 | #endif 11 | 12 | TH_PRIVATE(void) 13 | th_timer_init(th_timer* timer) 14 | { 15 | timer->expire = 0; 16 | } 17 | 18 | TH_LOCAL(th_err) 19 | th_timer_monotonic_now(time_t* out) 20 | { 21 | #if defined(TH_CONFIG_OS_POSIX) 22 | struct timespec ts = {0}; 23 | int ret = clock_gettime(CLOCK_MONOTONIC, &ts); 24 | if (ret != 0) { 25 | return TH_ERR_SYSTEM(errno); 26 | } 27 | *out = ts.tv_sec; 28 | return TH_ERR_OK; 29 | #elif defined(TH_CONFIG_OS_WIN) 30 | (void)out; 31 | return TH_ERR_NOSUPPORT; 32 | #elif defined(TH_CONFIG_OS_MOCK) 33 | (void)out; 34 | return TH_ERR_NOSUPPORT; 35 | #endif 36 | } 37 | 38 | TH_PRIVATE(th_err) 39 | th_timer_set(th_timer* timer, th_duration duration) 40 | { 41 | time_t now = 0; 42 | th_err err = th_timer_monotonic_now(&now); 43 | TH_ASSERT(err == TH_ERR_OK && "th_timer_monotonic_now failed"); 44 | if (err != TH_ERR_OK) 45 | return err; 46 | timer->expire = now + duration.seconds; 47 | return TH_ERR_OK; 48 | } 49 | 50 | TH_PRIVATE(bool) 51 | th_timer_expired(th_timer* timer) 52 | { 53 | time_t now = 0; 54 | th_err err = th_timer_monotonic_now(&now); 55 | TH_ASSERT(err == TH_ERR_OK && "th_timer_monotonic_now failed"); 56 | /* We don't return the error here, as it's already handled in th_timer_set 57 | * and we can safely assume that the error won't happen here. */ 58 | if (err != TH_ERR_OK) 59 | return true; 60 | return now >= timer->expire; 61 | } 62 | -------------------------------------------------------------------------------- /src/th_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_TIMER_H 2 | #define TH_TIMER_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | typedef struct th_timer { 14 | time_t expire; 15 | } th_timer; 16 | 17 | TH_PRIVATE(void) 18 | th_timer_init(th_timer* timer); 19 | 20 | TH_PRIVATE(th_err) 21 | th_timer_set(th_timer* timer, th_duration duration); 22 | 23 | TH_PRIVATE(bool) 24 | th_timer_expired(th_timer* timer); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/th_upload.c: -------------------------------------------------------------------------------- 1 | #include "th_upload.h" 2 | 3 | TH_PRIVATE(void) 4 | th_upload_init(th_upload* upload, th_string buffer, th_fcache* fcache, th_allocator* allocator) 5 | { 6 | th_heap_string_init(&upload->name, allocator); 7 | th_heap_string_init(&upload->filename, allocator); 8 | th_heap_string_init(&upload->content_type, allocator); 9 | upload->data = buffer; 10 | upload->fcache = fcache; 11 | } 12 | 13 | TH_PRIVATE(void) 14 | th_upload_deinit(th_upload* upload) 15 | { 16 | th_heap_string_deinit(&upload->name); 17 | th_heap_string_deinit(&upload->filename); 18 | th_heap_string_deinit(&upload->content_type); 19 | } 20 | 21 | TH_PRIVATE(th_err) 22 | th_upload_set_name(th_upload* upload, th_string name) 23 | { 24 | return th_heap_string_set(&upload->name, name); 25 | } 26 | 27 | TH_PRIVATE(th_err) 28 | th_upload_set_filename(th_upload* upload, th_string filename) 29 | { 30 | return th_heap_string_set(&upload->filename, filename); 31 | } 32 | 33 | TH_PRIVATE(th_err) 34 | th_upload_set_content_type(th_upload* upload, th_string content_type) 35 | { 36 | return th_heap_string_set(&upload->content_type, content_type); 37 | } 38 | 39 | // Public API 40 | 41 | TH_PUBLIC(th_upload_info) 42 | th_upload_get_info(const th_upload* upload) 43 | { 44 | return (th_upload_info){ 45 | .name = th_heap_string_data(&upload->name), 46 | .filename = th_heap_string_data(&upload->filename), 47 | .content_type = th_heap_string_data(&upload->content_type), 48 | .size = upload->data.len, 49 | }; 50 | } 51 | 52 | TH_PUBLIC(th_buffer) 53 | th_upload_get_data(const th_upload* upload) 54 | { 55 | return (th_buffer){upload->data.ptr, upload->data.len}; 56 | } 57 | 58 | TH_PUBLIC(th_err) 59 | th_upload_save(const th_upload* upload, const char* dir_label, const char* filepath) 60 | { 61 | th_dir* dir = th_fcache_find_dir(upload->fcache, th_string_from_cstr(dir_label)); 62 | if (!dir) 63 | return TH_ERR_HTTP(TH_CODE_NOT_FOUND); 64 | th_err err = TH_ERR_OK; 65 | th_open_opt opt = {.create = true, .write = true, .truncate = true}; 66 | th_file file; 67 | if ((err = th_file_openat(&file, dir, th_string_from_cstr(filepath), opt)) != TH_ERR_OK) 68 | return err; 69 | size_t total_written = 0; 70 | while (total_written < upload->data.len) { 71 | size_t written = 0; 72 | if ((err = th_file_write(&file, upload->data.ptr + total_written, upload->data.len - total_written, total_written, &written)) != TH_ERR_OK) { 73 | th_file_close(&file); 74 | return err; 75 | } 76 | total_written += written; 77 | } 78 | th_file_close(&file); 79 | return TH_ERR_OK; 80 | } 81 | -------------------------------------------------------------------------------- /src/th_upload.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_UPLOAD_H 2 | #define TH_UPLOAD_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_fcache.h" 8 | #include "th_heap_string.h" 9 | 10 | struct th_upload { 11 | th_heap_string name; 12 | th_heap_string filename; 13 | th_heap_string content_type; 14 | th_string data; 15 | th_fcache* fcache; 16 | }; 17 | 18 | TH_PRIVATE(void) 19 | th_upload_init(th_upload* upload, th_string buffer, th_fcache* fcache, th_allocator* allocator); 20 | 21 | TH_PRIVATE(void) 22 | th_upload_deinit(th_upload* upload); 23 | 24 | TH_PRIVATE(th_err) 25 | th_upload_set_name(th_upload* upload, th_string name); 26 | 27 | TH_PRIVATE(th_err) 28 | th_upload_set_filename(th_upload* upload, th_string filename); 29 | 30 | TH_PRIVATE(th_err) 31 | th_upload_set_content_type(th_upload* upload, th_string content_type); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/th_url_decode.c: -------------------------------------------------------------------------------- 1 | #include "th_url_decode.h" 2 | 3 | TH_LOCAL(th_err) 4 | th_url_decode_next(th_string str, size_t* pos, char* out, th_url_decode_type type) 5 | { 6 | size_t i = *pos; 7 | if (str.ptr[i] == '%') { 8 | char c = 0; 9 | for (size_t k = 0; k < 2; k++) { 10 | if (i + 1 + k >= str.len) 11 | return TH_ERR_HTTP(TH_CODE_BAD_REQUEST); 12 | c <<= 4; 13 | if (str.ptr[i + 1 + k] >= '0' && str.ptr[i + 1 + k] <= '9') { 14 | c |= str.ptr[i + 1 + k] - '0'; 15 | } else if (str.ptr[i + 1 + k] >= 'a' && str.ptr[i + 1 + k] <= 'f') { 16 | c |= str.ptr[i + 1 + k] - 'a' + 10; 17 | } else if (str.ptr[i + 1 + k] >= 'A' && str.ptr[i + 1 + k] <= 'F') { 18 | c |= str.ptr[i + 1 + k] - 'A' + 10; 19 | } else { 20 | return TH_ERR_HTTP(TH_CODE_BAD_REQUEST); 21 | } 22 | } 23 | *out = c; 24 | i += 3; 25 | } else if (type == TH_URL_DECODE_TYPE_QUERY && str.ptr[i] == '+') { 26 | *out = ' '; 27 | i++; 28 | } else { 29 | *out = str.ptr[i++]; 30 | } 31 | *pos = i; 32 | return TH_ERR_OK; 33 | } 34 | 35 | /* 36 | TH_PRIVATE(th_err) 37 | th_url_decode_inplace(char* str, size_t* in_out_len, th_url_decode_type type) 38 | { 39 | size_t i = 0; 40 | size_t j = 0; 41 | size_t len = *in_out_len; 42 | while (i < len) { 43 | char c; 44 | th_err err = th_url_decode_next(str, &i, &c, type); 45 | if (err != TH_ERR_OK) { 46 | return err; 47 | } 48 | str[j++] = c; 49 | } 50 | str[j] = '\0'; 51 | *in_out_len = j; 52 | return TH_ERR_OK; 53 | } 54 | */ 55 | 56 | TH_PRIVATE(th_err) 57 | th_url_decode_string(th_string input, th_heap_string* output, th_url_decode_type type) 58 | { 59 | th_heap_string_clear(output); 60 | 61 | th_err err = TH_ERR_OK; 62 | if (input.len == 0) 63 | return TH_ERR_OK; 64 | size_t i = 0; 65 | while (i < input.len) { 66 | char c; 67 | if ((err = th_url_decode_next(input, &i, &c, type)) != TH_ERR_OK) { 68 | return err; 69 | } 70 | if ((err = th_heap_string_push_back(output, c)) != TH_ERR_OK) { 71 | return err; 72 | } 73 | } 74 | return TH_ERR_OK; 75 | } 76 | -------------------------------------------------------------------------------- /src/th_url_decode.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_URL_DECODE_H 2 | #define TH_URL_DECODE_H 3 | 4 | #include 5 | 6 | #include "th_config.h" 7 | #include "th_heap_string.h" 8 | 9 | #include 10 | 11 | typedef enum th_url_decode_type { 12 | TH_URL_DECODE_TYPE_PATH = 0, 13 | TH_URL_DECODE_TYPE_QUERY 14 | } th_url_decode_type; 15 | 16 | /* 17 | TH_PRIVATE(th_err) 18 | th_url_decode_inplace(char* str, size_t* in_out_len, th_url_decode_type type); 19 | */ 20 | 21 | TH_PRIVATE(th_err) 22 | th_url_decode_string(th_string input, th_heap_string* output, th_url_decode_type type); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/th_url_decode_test.c: -------------------------------------------------------------------------------- 1 | #include "th_test.h" 2 | #include "th_url_decode.h" 3 | 4 | TH_TEST_BEGIN(url_decode) 5 | { 6 | TH_TEST_CASE_BEGIN(url_decode_empty) 7 | { 8 | char str[] = ""; 9 | size_t len = sizeof(str) - 1; 10 | TH_EXPECT(th_url_decode_inplace(str, &len, TH_URL_DECODE_TYPE_PATH) == TH_ERR_OK); 11 | TH_EXPECT(len == 0); 12 | } 13 | TH_TEST_CASE_END 14 | TH_TEST_CASE_BEGIN(url_decode_nothing) 15 | { 16 | char str[] = "hello"; 17 | size_t len = sizeof(str) - 1; 18 | TH_EXPECT(th_url_decode_inplace(str, &len, TH_URL_DECODE_TYPE_PATH) == TH_ERR_OK); 19 | TH_EXPECT(len == 5); 20 | TH_EXPECT(strcmp(str, "hello") == 0); 21 | } 22 | TH_TEST_CASE_END 23 | TH_TEST_CASE_BEGIN(url_decode_space) 24 | { 25 | char str[] = "hello%20world"; 26 | size_t len = sizeof(str) - 1; 27 | TH_EXPECT(th_url_decode_inplace(str, &len, TH_URL_DECODE_TYPE_PATH) == TH_ERR_OK); 28 | TH_EXPECT(len == 11); 29 | TH_EXPECT(strcmp(str, "hello world") == 0); 30 | } 31 | TH_TEST_CASE_END 32 | TH_TEST_CASE_BEGIN(url_decode_bad_request) 33 | { 34 | char str[] = "hello%2"; 35 | size_t len = sizeof(str) - 1; 36 | TH_EXPECT(th_url_decode_inplace(str, &len, TH_URL_DECODE_TYPE_PATH) == TH_ERR_HTTP(TH_CODE_BAD_REQUEST)); 37 | } 38 | TH_TEST_CASE_END 39 | } 40 | TH_TEST_END 41 | -------------------------------------------------------------------------------- /src/th_utility.h: -------------------------------------------------------------------------------- 1 | #ifndef TH_UTILITY_H 2 | #define TH_UTILITY_H 3 | 4 | #include "th_log.h" 5 | 6 | #include 7 | 8 | #define TH_MIN(a, b) ((a) < (b) ? (a) : (b)) 9 | #define TH_MAX(a, b) ((a) > (b) ? (a) : (b)) 10 | #define TH_ABS(a) ((a) < 0 ? -(a) : (a)) 11 | 12 | #define TH_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) 13 | 14 | // Move a pointer from src to dst and set src to NULL 15 | TH_INLINE(void*) 16 | th_move_ptr(void** src) 17 | { 18 | void* dst = *src; 19 | *src = NULL; 20 | return dst; 21 | } 22 | 23 | #define TH_MOVE_PTR(ptr) th_move_ptr((void**)&(ptr)) 24 | 25 | // Custom assert macros 26 | 27 | #ifndef NDEBUG 28 | #define TH_ASSERT(cond) \ 29 | do { \ 30 | if (!(cond)) { \ 31 | TH_LOG_FATAL("Assertion failed: %s at %s:%d", #cond, __FILE__, __LINE__); \ 32 | abort(); \ 33 | } \ 34 | } while (0) 35 | #else 36 | #define TH_ASSERT(cond) ((void)0) 37 | #endif 38 | 39 | // Mathematical utility functions 40 | 41 | TH_INLINE(size_t) 42 | th_next_pow2(size_t n) 43 | { 44 | TH_ASSERT(n > 0); 45 | n--; 46 | n |= n >> 1; 47 | n |= n >> 2; 48 | n |= n >> 4; 49 | n |= n >> 8; 50 | n |= n >> 16; 51 | n++; 52 | return n; 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/th_vec_test.c: -------------------------------------------------------------------------------- 1 | #include "th_test.h" 2 | #include "th_vec.h" 3 | 4 | TH_DEFINE_VEC(th_vec_cstr, const char*, (void)) 5 | 6 | TH_TEST_BEGIN(th_vec) 7 | { 8 | TH_TEST_CASE_BEGIN(th_vec_init) 9 | { 10 | th_vec_cstr vec; 11 | th_vec_cstr_init(&vec, NULL); 12 | TH_EXPECT(vec.data == NULL); 13 | TH_EXPECT(vec.size == 0); 14 | TH_EXPECT(vec.capacity == 0); 15 | th_vec_cstr_deinit(&vec); 16 | } 17 | TH_TEST_CASE_END 18 | } 19 | TH_TEST_END 20 | --------------------------------------------------------------------------------