├── .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 | [](https://github.com/RaphiaRa/tiny_http/actions/workflows/linux.yml)
5 | [](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.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 |
--------------------------------------------------------------------------------