├── src ├── time.h ├── includes.h ├── cert.h ├── time.c ├── secure_context.h ├── basic.h ├── basic.c ├── parse.h ├── cert.c ├── byte_queue.h ├── secure_context.c ├── server.h ├── client.h ├── byte_queue.c ├── socket.h ├── server.c ├── client.c └── socket.c ├── misc ├── TODO.txt └── amalg.py ├── .gitignore ├── examples ├── 000_simple_client.c ├── 001_simple_server.c ├── 006_parallel_client.c ├── 004_https_server.c ├── 003_virtual_hosts.c ├── 002_proxy.c └── 005_virtual_hosts_over_https.c ├── Makefile ├── LICENSE └── README.md /src/time.h: -------------------------------------------------------------------------------- 1 | 2 | typedef uint64_t Time; 3 | 4 | #define INVALID_TIME ((Time) UINT64_MAX-1) 5 | 6 | Time get_current_time(void); -------------------------------------------------------------------------------- /misc/TODO.txt: -------------------------------------------------------------------------------- 1 | [ ] Per RFC 6265, cookie values can be quoted. This parser would include the quotes in the value. 2 | [ ] Return an appropriate error code when the user waits on a client with no pending requests 3 | [ ] Add connection reuse limit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 000_simple_client 2 | 000_simple_client.exe 3 | 001_simple_server 4 | 001_simple_server.exe 5 | 002_proxy 6 | 002_proxy.exe 7 | 003_virtual_hosts 8 | 003_virtual_hosts.exe 9 | 004_https_server 10 | 004_https_server.exe 11 | 005_virtual_hosts_over_https 12 | 005_virtual_hosts_over_https.exe 13 | -------------------------------------------------------------------------------- /examples/000_simple_client.c: -------------------------------------------------------------------------------- 1 | #include "../chttp.h" 2 | 3 | int main(void) 4 | { 5 | CHTTP_Response *response; 6 | 7 | int ret = chttp_get(CHTTP_STR("http://coz.is/"), NULL, 0, &response); 8 | if (ret == CHTTP_OK) { 9 | printf("Received %d bytes\n", response->body.len); 10 | chttp_free_response(response); 11 | } else { 12 | printf("Request failure: %s\n", chttp_strerror(ret)); 13 | } 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/includes.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef _WIN32 10 | #define WIN32_LEAN_AND_MEAN 11 | #include 12 | #include 13 | #include 14 | #else 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #endif 27 | 28 | #ifdef HTTPS_ENABLED 29 | #include 30 | #include 31 | #endif 32 | -------------------------------------------------------------------------------- /src/cert.h: -------------------------------------------------------------------------------- 1 | // This is an utility to create self-signed certificates 2 | // useful when testing HTTPS servers locally. This is only 3 | // meant to be used by people starting out with a library 4 | // and simplifying the zero to one phase. 5 | // 6 | // The C, O, and CN are respectively country name, organization name, 7 | // and common name of the certificate. For instance: 8 | // 9 | // C="IT" 10 | // O="My Organization" 11 | // CN="my_website.com" 12 | // 13 | // The output is a certificate file in PEM format and a private 14 | // key file with the key used to sign the certificate. 15 | int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN, 16 | CHTTP_String cert_file, CHTTP_String key_file); 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS = -ggdb -Wall -Wextra 3 | LFLAGS = 4 | 5 | ifeq ($(shell uname -s),Linux) 6 | EXT = 7 | else 8 | EXT = .exe 9 | LFLAGS += -lws2_32 10 | endif 11 | 12 | HTTPS ?= 0 13 | ifneq ($(HTTPS),0) 14 | CFLAGS += -DHTTPS_ENABLED 15 | LFLAGS += -lcrypto -lssl 16 | endif 17 | 18 | .PHONY: all clean example 19 | 20 | all: chttp.c chttp.h 000_simple_client$(EXT) 001_simple_server$(EXT) 002_proxy$(EXT) 003_virtual_hosts$(EXT) 004_https_server$(EXT) 005_virtual_hosts_over_https$(EXT) 21 | 22 | chttp.c chttp.h: $(wildcard src/*.c src/*.h) misc/amalg.py Makefile 23 | python misc/amalg.py 24 | 25 | %$(EXT): examples/%.c chttp.c chttp.h 26 | gcc $< chttp.c -o $@ $(CFLAGS) $(LFLAGS) 27 | 28 | clean: 29 | rm chttp.c chttp.h 000_simple_client 000_simple_client.exe 001_simple_server 001_simple_server.exe 002_proxy 002_proxy.exe 003_virtual_hosts 003_virtual_hosts.exe 004_https_server 004_https_server.exe 005_virtual_hosts_over_https 005_virtual_hosts_over_https.exe 30 | -------------------------------------------------------------------------------- /src/time.c: -------------------------------------------------------------------------------- 1 | 2 | Time get_current_time(void) 3 | { 4 | #ifdef _WIN32 5 | { 6 | int64_t count; 7 | int64_t freq; 8 | int ok; 9 | 10 | ok = QueryPerformanceCounter((LARGE_INTEGER*) &count); 11 | if (!ok) return INVALID_TIME; 12 | 13 | ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq); 14 | if (!ok) return INVALID_TIME; 15 | 16 | uint64_t res = 1000 * (double) count / freq; 17 | return res; 18 | } 19 | #else 20 | { 21 | struct timespec time; 22 | 23 | if (clock_gettime(CLOCK_REALTIME, &time)) 24 | return INVALID_TIME; 25 | 26 | uint64_t res; 27 | 28 | uint64_t sec = time.tv_sec; 29 | if (sec > UINT64_MAX / 1000) 30 | return INVALID_TIME; 31 | res = sec * 1000; 32 | 33 | uint64_t nsec = time.tv_nsec; 34 | if (res > UINT64_MAX - nsec / 1000000) 35 | return INVALID_TIME; 36 | res += nsec / 1000000; 37 | 38 | return res; 39 | } 40 | #endif 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Francesco Cozzuto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/001_simple_server.c: -------------------------------------------------------------------------------- 1 | #include "../chttp.h" 2 | 3 | int main(void) 4 | { 5 | int ret; 6 | 7 | CHTTP_Server server; 8 | ret = chttp_server_init(&server); 9 | if (ret < 0) { 10 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 11 | return -1; 12 | } 13 | 14 | chttp_server_set_reuse_addr(&server, true); 15 | chttp_server_set_trace_bytes(&server, true); 16 | 17 | ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080); 18 | if (ret < 0) { 19 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 20 | return -1; 21 | } 22 | 23 | for (;;) { 24 | 25 | CHTTP_Request *request; 26 | CHTTP_ResponseBuilder builder; 27 | chttp_server_wait_request(&server, &request, &builder); 28 | 29 | chttp_response_builder_status(builder, 200); 30 | chttp_response_builder_body(builder, CHTTP_STR("Hello, world!")); 31 | chttp_response_builder_send(builder); 32 | } 33 | 34 | chttp_server_free(&server); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/secure_context.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SERVER_CERTIFICATE_LIMIT 3 | // Maximum number of certificates that can be 4 | // associated to a TLS server. This doesn't include 5 | // the default certificate. 6 | #define SERVER_CERTIFICATE_LIMIT 8 7 | #endif 8 | 9 | int global_secure_context_init(void); 10 | int global_secure_context_free(void); 11 | 12 | typedef struct { 13 | #ifdef HTTPS_ENABLED 14 | SSL_CTX *p; 15 | #endif 16 | } ClientSecureContext; 17 | 18 | int client_secure_context_init(ClientSecureContext *ctx); 19 | void client_secure_context_free(ClientSecureContext *ctx); 20 | 21 | typedef struct { 22 | #ifdef HTTPS_ENABLED 23 | char domain[128]; 24 | SSL_CTX *ctx; 25 | #endif 26 | } ServerCertificate; 27 | 28 | typedef struct { 29 | #ifdef HTTPS_ENABLED 30 | SSL_CTX *p; 31 | int num_certs; 32 | ServerCertificate certs[SERVER_CERTIFICATE_LIMIT]; 33 | #endif 34 | } ServerSecureContext; 35 | 36 | int server_secure_context_init(ServerSecureContext *ctx, 37 | CHTTP_String cert_file, CHTTP_String key_file); 38 | void server_secure_context_free(ServerSecureContext *ctx); 39 | int server_secure_context_add_certificate(ServerSecureContext *ctx, 40 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file); 41 | -------------------------------------------------------------------------------- /examples/006_parallel_client.c: -------------------------------------------------------------------------------- 1 | #include "../chttp.h" 2 | 3 | int main(void) 4 | { 5 | int ret; 6 | 7 | CHTTP_Client client; 8 | ret = chttp_client_init(&client); 9 | if (ret < 0) { 10 | printf("Couldn't initialize client (%s)\n", chttp_strerror(ret)); 11 | return -1; 12 | } 13 | 14 | CHTTP_RequestBuilder builder = chttp_client_get_builder(&client); 15 | 16 | chttp_request_builder_method(builder, CHTTP_METHOD_GET); 17 | chttp_request_builder_target(builder, CHTTP_STR("http://coz.is")); 18 | chttp_request_builder_header(builder, CHTTP_STR("Greeting: Hello from the cHTTP example!")); 19 | 20 | ret = chttp_request_builder_send(builder); 21 | if (ret < 0) { 22 | printf("Couldn't build request (%s)\n", chttp_strerror(ret)); 23 | return -1; 24 | } 25 | 26 | int result; 27 | void *user; 28 | CHTTP_Response *response; 29 | chttp_client_wait_response(&client, &result, &user, &response); 30 | 31 | if (result == CHTTP_OK) { 32 | printf("Received %d bytes\n", response->body.len); 33 | } else { 34 | printf("Couldn't receive response (%s)\n", chttp_strerror(result)); 35 | } 36 | 37 | chttp_free_response(response); 38 | chttp_client_free(&client); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /examples/004_https_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../chttp.h" 4 | 5 | int main(void) 6 | { 7 | CHTTP_String local_addr = CHTTP_STR("127.0.0.1"); 8 | uint16_t local_port = 8443; 9 | 10 | CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem"); 11 | CHTTP_String key_file = CHTTP_STR("websiteA_key.pem"); 12 | 13 | CHTTP_Server server; 14 | int ret = chttp_server_init(&server); 15 | if (ret < 0) { 16 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 17 | return -1; 18 | } 19 | 20 | chttp_server_set_reuse_addr(&server, true); 21 | chttp_server_set_trace_bytes(&server, true); 22 | 23 | ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file); 24 | if (ret < 0) { 25 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 26 | return -1; 27 | } 28 | 29 | for (;;) { 30 | 31 | CHTTP_Request *req; 32 | CHTTP_ResponseBuilder builder; 33 | chttp_server_wait_request(&server, &req, &builder); 34 | 35 | chttp_response_builder_status(builder, 200); 36 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!")); 37 | chttp_response_builder_send(builder); 38 | } 39 | 40 | chttp_server_free(&server); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /src/basic.h: -------------------------------------------------------------------------------- 1 | 2 | enum { 3 | 4 | CHTTP_OK = 0, 5 | 6 | // A generic error occurred 7 | CHTTP_ERROR_UNSPECIFIED = -1, 8 | 9 | // Out of memory 10 | CHTTP_ERROR_OOM = -2, 11 | 12 | // Invalid URL 13 | CHTTP_ERROR_BADURL = -3, 14 | 15 | // Parallel request limit reached 16 | CHTTP_ERROR_REQLIMIT = -4, 17 | 18 | // Invalid handle 19 | CHTTP_ERROR_BADHANDLE = -5, 20 | 21 | // TLS support not built-in 22 | CHTTP_ERROR_NOTLS = -6, 23 | }; 24 | 25 | // String type used throughout cHTTP. 26 | typedef struct { 27 | char *ptr; 28 | int len; 29 | } CHTTP_String; 30 | 31 | // Compare two strings and return true iff they have 32 | // the same contents. 33 | bool chttp_streq(CHTTP_String s1, CHTTP_String s2); 34 | 35 | // Compre two strings case-insensitively (uppercase and 36 | // lowercase versions of a letter are considered the same) 37 | // and return true iff they have the same contents. 38 | bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2); 39 | 40 | // Remove spaces and tabs from the start and the end of 41 | // a string. This doesn't change the original string and 42 | // the new one references the contents of the original one. 43 | CHTTP_String chttp_trim(CHTTP_String s); 44 | 45 | // Print the contents of a byte string with the given prefix. 46 | // This is primarily used for debugging purposes. 47 | void print_bytes(CHTTP_String prefix, CHTTP_String src); 48 | 49 | // TODO: comment 50 | char *chttp_strerror(int code); 51 | 52 | // Macro to simplify converting string literals to 53 | // CHTTP_String. 54 | // 55 | // Instead of doing this: 56 | // 57 | // char *s = "some string"; 58 | // 59 | // You do this: 60 | // 61 | // CHTTP_String s = CHTTP_STR("some string") 62 | // 63 | // This is a bit cumbersome, but better than null-terminated 64 | // strings, having a pointer and length variable pairs whenever 65 | // a function operates on a string. If this wasn't a library 66 | // I would have done for 67 | // 68 | // #define S(X) ... 69 | // 70 | // But I don't want to cause collisions with user code. 71 | #define CHTTP_STR(X) ((CHTTP_String) {(X), sizeof(X)-1}) 72 | 73 | // Returns the number of items of a static array. 74 | #define CHTTP_COUNT(X) (int) (sizeof(X) / sizeof((X)[0])) 75 | 76 | // Macro to unpack an CHTTP_String into its length and pointer components. 77 | // Useful for passing CHTTP_String to printf-style functions with "%.*s" format. 78 | // Example: printf("%.*s", CHTTP_UNPACK(str)); 79 | #define CHTTP_UNPACK(X) (X).len, (X).ptr 80 | 81 | // TODO: comment 82 | #define CHTTP_UNREACHABLE __builtin_trap() 83 | -------------------------------------------------------------------------------- /examples/003_virtual_hosts.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../chttp.h" 4 | 5 | int main(void) 6 | { 7 | // To test this program you need to add the following 8 | // lines to your hosts file: 9 | // 10 | // 127.0.0.1 websiteA.com 11 | // 127.0.0.1 websiteB.com 12 | // 127.0.0.1 websiteC.com 13 | // 14 | // That you can find at /etc/hosts on Linux and 15 | // C:\Windows\System32\drivers\etc\hosts on Windows 16 | 17 | int ret; 18 | 19 | CHTTP_Server server; 20 | ret = chttp_server_init(&server); 21 | if (ret < 0) { 22 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 23 | return -1; 24 | } 25 | 26 | chttp_server_set_reuse_addr(&server, true); 27 | chttp_server_set_trace_bytes(&server, true); 28 | 29 | CHTTP_String local_addr = CHTTP_STR("127.0.0.1"); 30 | uint16_t local_port = 8080; 31 | ret = chttp_server_listen_tcp(&server, local_addr, local_port); 32 | if (ret < 0) { 33 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 34 | return -1; 35 | } 36 | 37 | // The following loop will serve responses for 38 | // 39 | // http://websiteA.com:8080/ 40 | // http://websiteB.com:8080/ 41 | // http://websiteC.com:8080/ 42 | // 43 | // If a host name is missing or there isn't one 44 | // 45 | // http://127.0.0.1:8080/ 46 | // 47 | // The websiteA.com handler is used 48 | 49 | for (;;) { 50 | 51 | CHTTP_Request *req; 52 | CHTTP_ResponseBuilder builder; 53 | chttp_server_wait_request(&server, &req, &builder); 54 | 55 | if (chttp_match_host(req, CHTTP_STR("websiteB.com"), local_port)) { 56 | // Website B 57 | chttp_response_builder_status(builder, 200); 58 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!")); 59 | chttp_response_builder_send(builder); 60 | 61 | } else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), local_port)) { 62 | // Website C 63 | chttp_response_builder_status(builder, 200); 64 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!")); 65 | chttp_response_builder_send(builder); 66 | } else { 67 | // Serve websiteA by default 68 | chttp_response_builder_status(builder, 200); 69 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!")); 70 | chttp_response_builder_send(builder); 71 | } 72 | } 73 | 74 | chttp_server_free(&server); 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /src/basic.c: -------------------------------------------------------------------------------- 1 | 2 | bool chttp_streq(CHTTP_String s1, CHTTP_String s2) 3 | { 4 | if (s1.len != s2.len) 5 | return false; 6 | 7 | for (int i = 0; i < s1.len; i++) 8 | if (s1.ptr[i] != s2.ptr[i]) 9 | return false; 10 | 11 | return true; 12 | } 13 | 14 | static char to_lower(char c) 15 | { 16 | if (c >= 'A' && c <= 'Z') 17 | return c - 'A' + 'a'; 18 | return c; 19 | } 20 | 21 | bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2) 22 | { 23 | if (s1.len != s2.len) 24 | return false; 25 | 26 | for (int i = 0; i < s1.len; i++) 27 | if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) 28 | return false; 29 | 30 | return true; 31 | } 32 | 33 | CHTTP_String chttp_trim(CHTTP_String s) 34 | { 35 | int i = 0; 36 | while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) 37 | i++; 38 | 39 | if (i == s.len) { 40 | s.ptr = NULL; 41 | s.len = 0; 42 | } else { 43 | s.ptr += i; 44 | s.len -= i; 45 | while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') 46 | s.len--; 47 | } 48 | 49 | return s; 50 | } 51 | 52 | static bool is_printable(char c) 53 | { 54 | return c >= ' ' && c <= '~'; 55 | } 56 | 57 | void print_bytes(CHTTP_String prefix, CHTTP_String src) 58 | { 59 | if (src.len == 0) 60 | return; 61 | 62 | FILE *stream = stdout; 63 | 64 | bool new_line = true; 65 | int cur = 0; 66 | for (;;) { 67 | int start = cur; 68 | 69 | while (cur < src.len && is_printable(src.ptr[cur])) 70 | cur++; 71 | 72 | if (new_line) { 73 | fwrite(prefix.ptr, 1, prefix.len, stream); 74 | new_line = false; 75 | } 76 | 77 | fwrite(src.ptr + start, 1, cur - start, stream); 78 | 79 | if (cur == src.len) 80 | break; 81 | 82 | if (src.ptr[cur] == '\n') { 83 | putc('\\', stream); 84 | putc('n', stream); 85 | putc('\n', stream); 86 | new_line = true; 87 | } else if (src.ptr[cur] == '\r') { 88 | putc('\\', stream); 89 | putc('r', stream); 90 | } else { 91 | putc('.', stream); 92 | } 93 | cur++; 94 | } 95 | putc('\n', stream); 96 | } 97 | 98 | char *chttp_strerror(int code) 99 | { 100 | switch (code) { 101 | case CHTTP_OK: return "No error"; 102 | case CHTTP_ERROR_UNSPECIFIED: return "Unspecified error"; 103 | case CHTTP_ERROR_OOM: return "Out of memory"; 104 | case CHTTP_ERROR_BADURL: return "Invalid URL"; 105 | case CHTTP_ERROR_REQLIMIT: return "Parallel request limit reached"; 106 | case CHTTP_ERROR_BADHANDLE: return "Invalid handle"; 107 | case CHTTP_ERROR_NOTLS: return "TLS support not built-in"; 108 | } 109 | return "???"; 110 | } 111 | -------------------------------------------------------------------------------- /misc/amalg.py: -------------------------------------------------------------------------------- 1 | class Amalgamator: 2 | def __init__(self): 3 | self.out = "" 4 | 5 | def append_text(self, text): 6 | self.out += text 7 | 8 | def append_file(self, file): 9 | self.out += "\n" 10 | self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" 11 | self.out += "// " + file + "\n" 12 | self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" 13 | self.out += open(file).read() 14 | 15 | if len(self.out) > 0 and self.out[len(self.out) - 1] != "\n": 16 | self.out += "\n" 17 | 18 | def save(self, file): 19 | open(file, "w").write(self.out) 20 | 21 | 22 | desc = """// cHTTP, an HTTP client and server library! 23 | // 24 | // This file was generated automatically. Do not modify directly. 25 | // 26 | // Refer to the end of this file for the license""" 27 | 28 | license = """ 29 | //////////////////////////////////////////////////////////////////////////////////////// 30 | // Copyright 2025 Francesco Cozzuto 31 | // 32 | // Permission is hereby granted, free of charge, to any person 33 | // obtaining a copy of this software and associated documentation 34 | // files (the "Software"), to deal in the Software without 35 | // restriction, including without limitation the rights to use, 36 | // copy, modify, merge, publish, distribute, sublicense, and/or 37 | // sell copies of the Software, and to permit persons to whom 38 | // the Software is furnished to do so, subject to the following 39 | // conditions: 40 | // 41 | // The above copyright notice and this permission notice shall 42 | // be included in all copies or substantial portions of the Software. 43 | // 44 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 45 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 46 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 47 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 48 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 49 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 51 | // DEALINGS IN THE SOFTWARE. 52 | //////////////////////////////////////////////////////////////////////////////////////// 53 | """ 54 | 55 | header = Amalgamator() 56 | header.append_text("#ifndef CHTTP_INCLUDED\n") 57 | header.append_text("#define CHTTP_INCLUDED\n") 58 | header.append_text(desc) 59 | header.append_file("src/includes.h") 60 | header.append_file("src/basic.h") 61 | header.append_file("src/parse.h") 62 | header.append_file("src/time.h") 63 | header.append_file("src/secure_context.h") 64 | header.append_file("src/socket.h") 65 | header.append_file("src/byte_queue.h") 66 | header.append_file("src/cert.h") 67 | header.append_file("src/client.h") 68 | header.append_file("src/server.h") 69 | header.append_text(license) 70 | header.append_text("#endif // CHTTP_INCLUDED\n") 71 | header.save("chttp.h") 72 | 73 | source = Amalgamator() 74 | source.append_text(desc) 75 | source.append_text("\n") 76 | source.append_text("#ifndef CHTTP_DONT_INCLUDE\n") 77 | source.append_text('#include "chttp.h"\n') 78 | source.append_text("#endif\n") 79 | source.append_file("src/basic.c") 80 | source.append_file("src/parse.c") 81 | source.append_file("src/time.c") 82 | source.append_file("src/secure_context.c") 83 | source.append_file("src/socket.c") 84 | source.append_file("src/byte_queue.c") 85 | source.append_file("src/cert.c") 86 | source.append_file("src/client.c") 87 | source.append_file("src/server.c") 88 | source.append_text(license) 89 | source.save("chttp.c") 90 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | 2 | #define CHTTP_MAX_HEADERS 32 3 | 4 | typedef struct { 5 | unsigned int data; 6 | } CHTTP_IPv4; 7 | 8 | typedef struct { 9 | unsigned short data[8]; 10 | } CHTTP_IPv6; 11 | 12 | typedef enum { 13 | CHTTP_HOST_MODE_VOID = 0, 14 | CHTTP_HOST_MODE_NAME, 15 | CHTTP_HOST_MODE_IPV4, 16 | CHTTP_HOST_MODE_IPV6, 17 | } CHTTP_HostMode; 18 | 19 | typedef struct { 20 | CHTTP_HostMode mode; 21 | CHTTP_String text; 22 | union { 23 | CHTTP_String name; 24 | CHTTP_IPv4 ipv4; 25 | CHTTP_IPv6 ipv6; 26 | }; 27 | } CHTTP_Host; 28 | 29 | typedef struct { 30 | CHTTP_String userinfo; 31 | CHTTP_Host host; 32 | int port; 33 | } CHTTP_Authority; 34 | 35 | // ZII 36 | typedef struct { 37 | CHTTP_String scheme; 38 | CHTTP_Authority authority; 39 | CHTTP_String path; 40 | CHTTP_String query; 41 | CHTTP_String fragment; 42 | } CHTTP_URL; 43 | 44 | typedef enum { 45 | CHTTP_METHOD_GET, 46 | CHTTP_METHOD_HEAD, 47 | CHTTP_METHOD_POST, 48 | CHTTP_METHOD_PUT, 49 | CHTTP_METHOD_DELETE, 50 | CHTTP_METHOD_CONNECT, 51 | CHTTP_METHOD_OPTIONS, 52 | CHTTP_METHOD_TRACE, 53 | CHTTP_METHOD_PATCH, 54 | } CHTTP_Method; 55 | 56 | typedef struct { 57 | CHTTP_String name; 58 | CHTTP_String value; 59 | } CHTTP_Header; 60 | 61 | typedef struct { 62 | bool secure; 63 | CHTTP_Method method; 64 | CHTTP_URL url; 65 | int minor; 66 | int num_headers; 67 | CHTTP_Header headers[CHTTP_MAX_HEADERS]; 68 | CHTTP_String body; 69 | } CHTTP_Request; 70 | 71 | typedef struct { 72 | void* context; 73 | int minor; 74 | int status; 75 | CHTTP_String reason; 76 | int num_headers; 77 | CHTTP_Header headers[CHTTP_MAX_HEADERS]; 78 | CHTTP_String body; 79 | } CHTTP_Response; 80 | 81 | int chttp_parse_ipv4 (char *src, int len, CHTTP_IPv4 *ipv4); 82 | int chttp_parse_ipv6 (char *src, int len, CHTTP_IPv6 *ipv6); 83 | int chttp_parse_url (char *src, int len, CHTTP_URL *url); 84 | int chttp_parse_request (char *src, int len, CHTTP_Request *req); 85 | int chttp_parse_response (char *src, int len, CHTTP_Response *res); 86 | 87 | int chttp_find_header (CHTTP_Header *headers, int num_headers, CHTTP_String name); 88 | 89 | CHTTP_String chttp_get_cookie (CHTTP_Request *req, CHTTP_String name); 90 | CHTTP_String chttp_get_param (CHTTP_String body, CHTTP_String str, char *mem, int cap); 91 | int chttp_get_param_i (CHTTP_String body, CHTTP_String str); 92 | 93 | // Checks whether the request was meant for the host with the given 94 | // domain an port. If port is -1, the default value of 80 is assumed. 95 | bool chttp_match_host(CHTTP_Request *req, CHTTP_String domain, int port); 96 | 97 | // Date and cookie types for Set-Cookie header parsing 98 | typedef enum { 99 | CHTTP_WEEKDAY_MON, 100 | CHTTP_WEEKDAY_TUE, 101 | CHTTP_WEEKDAY_WED, 102 | CHTTP_WEEKDAY_THU, 103 | CHTTP_WEEKDAY_FRI, 104 | CHTTP_WEEKDAY_SAT, 105 | CHTTP_WEEKDAY_SUN, 106 | } CHTTP_WeekDay; 107 | 108 | typedef enum { 109 | CHTTP_MONTH_JAN, 110 | CHTTP_MONTH_FEB, 111 | CHTTP_MONTH_MAR, 112 | CHTTP_MONTH_APR, 113 | CHTTP_MONTH_MAY, 114 | CHTTP_MONTH_JUN, 115 | CHTTP_MONTH_JUL, 116 | CHTTP_MONTH_AUG, 117 | CHTTP_MONTH_SEP, 118 | CHTTP_MONTH_OCT, 119 | CHTTP_MONTH_NOV, 120 | CHTTP_MONTH_DEC, 121 | } CHTTP_Month; 122 | 123 | typedef struct { 124 | CHTTP_WeekDay week_day; 125 | int day; 126 | CHTTP_Month month; 127 | int year; 128 | int hour; 129 | int minute; 130 | int second; 131 | } CHTTP_Date; 132 | 133 | typedef struct { 134 | CHTTP_String name; 135 | CHTTP_String value; 136 | 137 | bool secure; 138 | bool chttp_only; 139 | 140 | bool have_date; 141 | CHTTP_Date date; 142 | 143 | bool have_max_age; 144 | uint32_t max_age; 145 | 146 | bool have_domain; 147 | CHTTP_String domain; 148 | 149 | bool have_path; 150 | CHTTP_String path; 151 | } CHTTP_SetCookie; 152 | 153 | // Parses a Set-Cookie header value 154 | // Returns 0 on success, -1 on error 155 | int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out); 156 | -------------------------------------------------------------------------------- /src/cert.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef HTTPS_ENABLED 3 | 4 | static EVP_PKEY *generate_rsa_key_pair(int key_bits) 5 | { 6 | EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); 7 | if (!ctx) 8 | return NULL; 9 | 10 | if (EVP_PKEY_keygen_init(ctx) <= 0) { 11 | EVP_PKEY_CTX_free(ctx); 12 | return NULL; 13 | } 14 | 15 | if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { 16 | EVP_PKEY_CTX_free(ctx); 17 | return NULL; 18 | } 19 | 20 | EVP_PKEY *pkey = NULL; 21 | if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { 22 | EVP_PKEY_CTX_free(ctx); 23 | return NULL; 24 | } 25 | 26 | EVP_PKEY_CTX_free(ctx); 27 | return pkey; 28 | } 29 | 30 | static X509 *create_certificate(EVP_PKEY *pkey, CHTTP_String C, CHTTP_String O, CHTTP_String CN, int days) 31 | { 32 | X509 *x509 = X509_new(); 33 | if (!x509) 34 | return NULL; 35 | 36 | // Set version (version 3) 37 | X509_set_version(x509, 2); 38 | 39 | // Set serial number 40 | ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); 41 | 42 | // Set validity period 43 | X509_gmtime_adj(X509_get_notBefore(x509), 0); 44 | X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year 45 | 46 | // Set public key 47 | X509_set_pubkey(x509, pkey); 48 | 49 | // Set subject name 50 | X509_NAME *name = X509_get_subject_name(x509); 51 | X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); 52 | X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); 53 | X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); 54 | 55 | // Set issuer name (same as subject for self-signed) 56 | X509_set_issuer_name(x509, name); 57 | 58 | if (!X509_sign(x509, pkey, EVP_sha256())) { 59 | X509_free(x509); 60 | return NULL; 61 | } 62 | 63 | return x509; 64 | } 65 | 66 | static int save_private_key(EVP_PKEY *pkey, CHTTP_String file) 67 | { 68 | char copy[1<<10]; 69 | if (file.len >= (int) sizeof(copy)) 70 | return -1; 71 | memcpy(copy, file.ptr, file.len); 72 | copy[file.len] = '\0'; 73 | 74 | FILE *fp = fopen(copy, "wb"); 75 | if (!fp) 76 | return -1; 77 | 78 | // Write private key in PEM format 79 | if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { 80 | fclose(fp); 81 | return -1; 82 | } 83 | 84 | fclose(fp); 85 | return 0; 86 | } 87 | 88 | static int save_certificate(X509 *x509, CHTTP_String file) 89 | { 90 | char copy[1<<10]; 91 | if (file.len >= (int) sizeof(copy)) 92 | return -1; 93 | memcpy(copy, file.ptr, file.len); 94 | copy[file.len] = '\0'; 95 | 96 | FILE *fp = fopen(copy, "wb"); 97 | if (!fp) 98 | return -1; 99 | 100 | // Write certificate in PEM format 101 | if (!PEM_write_X509(fp, x509)) { 102 | fclose(fp); 103 | return -1; 104 | } 105 | 106 | fclose(fp); 107 | return 0; 108 | } 109 | 110 | int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN, 111 | CHTTP_String cert_file, CHTTP_String key_file) 112 | { 113 | EVP_PKEY *pkey = generate_rsa_key_pair(2048); 114 | if (pkey == NULL) 115 | return -1; 116 | 117 | X509 *x509 = create_certificate(pkey, C, O, CN, 1); 118 | if (x509 == NULL) { 119 | EVP_PKEY_free(pkey); 120 | return -1; 121 | } 122 | 123 | if (save_private_key(pkey, key_file) < 0) { 124 | X509_free(x509); 125 | EVP_PKEY_free(pkey); 126 | return -1; 127 | } 128 | 129 | if (save_certificate(x509, cert_file) < 0) { 130 | X509_free(x509); 131 | EVP_PKEY_free(pkey); 132 | return -1; 133 | } 134 | 135 | X509_free(x509); 136 | EVP_PKEY_free(pkey); 137 | return 0; 138 | } 139 | 140 | #else 141 | 142 | int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN, 143 | CHTTP_String cert_file, CHTTP_String key_file) 144 | { 145 | (void) C; 146 | (void) O; 147 | (void) CN; 148 | (void) cert_file; 149 | (void) key_file; 150 | return -1; 151 | } 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cHTTP 2 | cHTTP is an HTTP client and server library distributed as a single file with support for HTTPS, virtual hosts, fully non-blocking operations. 3 | 4 | ## Quick Start 5 | 6 | ### Your first request 7 | 8 | The simplest way to perform a GET request looks like this: 9 | 10 | ```c 11 | #include "chttp.h" 12 | 13 | int main(void) 14 | { 15 | CHTTP_Response *response; 16 | 17 | int ret = chttp_get(CHTTP_STR("http://coz.is/"), NULL, 0, &response); 18 | if (ret == CHTTP_OK) { 19 | printf("Received %d bytes\n", response->body.len); 20 | chttp_free_response(response); 21 | } else { 22 | printf("Request failure: %s\n", chttp_strerror(ret)); 23 | } 24 | return 0; 25 | } 26 | ``` 27 | 28 | (Note the `http:` schema. If you want HTTPS, you'll have to enable it explicitly! Refer to the HTTPS section.) 29 | 30 | Copy this code to `first_request.c` near `chttp.c` and compile it by running: 31 | 32 | ```sh 33 | # Linux 34 | gcc chttp.c first_request.c -o first_request 35 | 36 | # Windows (mingw) 37 | gcc chttp.c first_request.c -o first_request.exe -lws2_32 38 | ``` 39 | 40 | Then, run the program 41 | 42 | ```sh 43 | # Linux 44 | ./first_request 45 | 46 | # Windows 47 | .\first_request.exe 48 | ``` 49 | 50 | Done! 51 | 52 | ### Your first server 53 | 54 | The setup for a basic server looks like this: 55 | 56 | ```c 57 | #include "chttp.h" 58 | 59 | int main(void) 60 | { 61 | int ret; 62 | 63 | CHTTP_Server server; 64 | ret = chttp_server_init(&server); 65 | if (ret < 0) { 66 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 67 | return -1; 68 | } 69 | 70 | chttp_server_set_reuse_addr(&server, true); 71 | chttp_server_set_trace_bytes(&server, true); 72 | 73 | ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080); 74 | if (ret < 0) { 75 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 76 | return -1; 77 | } 78 | 79 | for (;;) { 80 | 81 | CHTTP_Request *request; 82 | CHTTP_ResponseBuilder builder; 83 | chttp_server_wait_request(&server, &request, &builder); 84 | 85 | chttp_response_builder_status(builder, 200); 86 | chttp_response_builder_body(builder, CHTTP_STR("Hello, world!")); 87 | chttp_response_builder_send(builder); 88 | } 89 | 90 | chttp_server_free(&server); 91 | return 0; 92 | } 93 | ``` 94 | 95 | Copy this code to a `first_server.c` file and compile it by running 96 | 97 | ```sh 98 | # Linux 99 | gcc chttp.c first_server.c -o first_server 100 | 101 | # Windows (mingw) 102 | gcc chttp.c first_server.c -o first_server.exe -lws2_32 103 | ``` 104 | 105 | Then, run the program 106 | 107 | ```sh 108 | # Linux 109 | ./first_server 110 | 111 | # Windows 112 | .\first_server.exe 113 | ``` 114 | 115 | While the program is running, open a browser and visit `http://127.0.0.1:8080/`. You should see the text "Hello, world!" sent by the server and a log of the HTTP requests and responses processed by the server in the console. 116 | 117 | ## HTTPS 118 | 119 | HTTPS is supported via OpenSSL, which is easily available on Linux and less so on Windows. 120 | 121 | First, install the OpenSSL development libraries: 122 | 123 | ```sh 124 | # Ubuntu/Debian Linux 125 | sudo apt install libssl-dev gcc 126 | ``` 127 | 128 | Then, enable HTTPS by compiling your program with the following flags: 129 | 130 | ```sh 131 | # Linux 132 | gcc chttp.c main.c -lssl -lcrypto -DHTTPS_ENABLED 133 | 134 | # Windows 135 | gcc chttp.c main.c -lws2_32 -lssl -lcrypto -DHTTPS_ENABLED 136 | ``` 137 | 138 | ## Development Status 139 | 140 | The major limitation of cHTTP is HTTPS on Windows. For that to work correctly it will be necessary to port the OpenSSL code to SChannel. 141 | 142 | Other limitations: 143 | * HTTP client doesn't follow redirections (responses with code 3xx) 144 | * Support for HTTP client cookies is limited 145 | * HTTP server adherence to the spec can be improved 146 | 147 | ## Contributing 148 | 149 | Contributions are welcome! The following are some notes on how to work with the codebase. Don't worry if you get something wrong. I will remind you. 150 | 151 | The source code in the src/ directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons: 152 | 153 | * You never need need to include other cHTTP source files 154 | * All inclusions of third-party headers are to be placed inside src/includes.h 155 | * All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file. 156 | * All files must end with a single empty line. -------------------------------------------------------------------------------- /examples/002_proxy.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef _WIN32 3 | #include 4 | #define POLL WSAPoll 5 | #else 6 | #include 7 | #define POLL poll 8 | #endif 9 | 10 | #include "../chttp.h" 11 | 12 | static int pick_timeout(int t1, int t2) 13 | { 14 | if (t1 < 0) return t2; 15 | if (t2 < 0) return t1; 16 | return (t1 < t2) ? t1 : t2; 17 | } 18 | 19 | int main() 20 | { 21 | int ret; 22 | 23 | CHTTP_String remote = CHTTP_STR("http://coz.is"); 24 | 25 | CHTTP_Client client; 26 | ret = chttp_client_init(&client); 27 | if (ret < 0) { 28 | printf("Couldn't initialize client (%s)\n", chttp_strerror(ret)); 29 | return -1; 30 | } 31 | 32 | CHTTP_Server server; 33 | ret = chttp_server_init(&server); 34 | if (ret < 0) { 35 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 36 | return -1; 37 | } 38 | 39 | chttp_server_set_reuse_addr(&server, true); 40 | chttp_server_set_trace_bytes(&server, true); 41 | 42 | ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080); 43 | if (ret < 0) { 44 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 45 | return -1; 46 | } 47 | 48 | bool used[CHTTP_SERVER_CAPACITY]; 49 | for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++) 50 | used[i] = false; 51 | 52 | CHTTP_ResponseBuilder pending[CHTTP_SERVER_CAPACITY]; 53 | int num_pending = 0; 54 | 55 | for (;;) { 56 | 57 | #define POLL_CAPACITY (CHTTP_CLIENT_POLL_CAPACITY + CHTTP_SERVER_POLL_CAPACITY) 58 | 59 | void *ptrs[POLL_CAPACITY]; 60 | struct pollfd polled[POLL_CAPACITY]; 61 | 62 | EventRegister server_reg = { 63 | ptrs, 64 | polled, 65 | 0, 66 | -1 67 | }; 68 | chttp_server_register_events(&server, &server_reg); 69 | 70 | EventRegister client_reg = { 71 | ptrs + server_reg.num_polled, 72 | polled + server_reg.num_polled, 73 | 0, 74 | -1 75 | }; 76 | chttp_client_register_events(&client, &client_reg); 77 | 78 | int num_polled = server_reg.num_polled 79 | + client_reg.num_polled; 80 | 81 | int timeout = pick_timeout(server_reg.timeout, client_reg.timeout); 82 | 83 | POLL(polled, num_polled, timeout); 84 | 85 | int result; 86 | void *user; 87 | CHTTP_Response *response; 88 | chttp_client_process_events(&client, client_reg); 89 | while (chttp_client_next_response(&client, &result, &user, &response)) { 90 | 91 | CHTTP_ResponseBuilder *builder = (CHTTP_ResponseBuilder*) user; 92 | 93 | int i = builder - pending; 94 | assert(i > -1); 95 | assert(i < CHTTP_SERVER_CAPACITY); 96 | 97 | if (result == CHTTP_OK) { 98 | chttp_response_builder_status(*builder, response->status); 99 | chttp_response_builder_body(*builder, response->body); 100 | chttp_response_builder_send(*builder); 101 | } else { 102 | chttp_response_builder_status(*builder, 500); 103 | chttp_response_builder_send(*builder); 104 | } 105 | 106 | used[i] = false; 107 | num_pending--; 108 | } 109 | 110 | CHTTP_Request *request; 111 | CHTTP_ResponseBuilder response_builder; 112 | chttp_server_process_events(&server, server_reg); 113 | while (chttp_server_next_request(&server, &request, &response_builder)) { 114 | 115 | int i = 0; 116 | while (used[i]) { 117 | i++; 118 | assert(i < CHTTP_SERVER_CAPACITY); 119 | } 120 | 121 | CHTTP_String path = request->url.path; 122 | if (path.len == 0) 123 | path = CHTTP_STR("/"); 124 | 125 | char target[1<<10]; 126 | int target_len = snprintf(target, sizeof(target), 127 | "%.*s%.*s", CHTTP_UNPACK(remote), CHTTP_UNPACK(path)); 128 | if (target_len < 0 || target_len >= (int) sizeof(target)) { 129 | chttp_response_builder_status(response_builder, 500); 130 | chttp_response_builder_send(response_builder); 131 | continue; 132 | } 133 | 134 | CHTTP_RequestBuilder request_builder = chttp_client_get_builder(&client); 135 | chttp_request_builder_set_user(request_builder, &pending[i]); 136 | chttp_request_builder_trace(request_builder, true); 137 | chttp_request_builder_method(request_builder, request->method); 138 | chttp_request_builder_target(request_builder, (CHTTP_String) { target, target_len }); 139 | ret = chttp_request_builder_send(request_builder); 140 | if (ret < 0) { 141 | chttp_response_builder_status(response_builder, 500); 142 | chttp_response_builder_send(response_builder); 143 | continue; 144 | } 145 | 146 | used[i] = true; 147 | pending[i] = response_builder; 148 | num_pending++; 149 | } 150 | } 151 | 152 | chttp_server_free(&server); 153 | chttp_client_free(&client); 154 | return 0; 155 | } 156 | -------------------------------------------------------------------------------- /src/byte_queue.h: -------------------------------------------------------------------------------- 1 | // This is the implementation of a byte queue useful 2 | // for systems that need to process engs of bytes. 3 | // 4 | // It features sticky errors, a zero-copy interface, 5 | // and a safe mechanism to patch previously written 6 | // bytes. 7 | // 8 | // Only up to 4GB of data can be stored at once. 9 | 10 | // Internal use only 11 | enum { 12 | BYTE_QUEUE_ERROR = 1 << 0, 13 | BYTE_QUEUE_READ = 1 << 1, 14 | BYTE_QUEUE_WRITE = 1 << 2, 15 | }; 16 | 17 | typedef struct { 18 | char *ptr; 19 | size_t len; 20 | } ByteView; 21 | 22 | // Fields are for internal use only 23 | typedef struct { 24 | uint64_t curs; 25 | char* data; 26 | uint32_t head; 27 | uint32_t size; 28 | uint32_t used; 29 | uint32_t limit; 30 | char* read_target; 31 | uint32_t read_target_size; 32 | int flags; 33 | } ByteQueue; 34 | 35 | // Represents an offset inside the queue relative 36 | // to the first byte ever appended to the queue, 37 | // therefore consuming bytes from the queue does 38 | // not invalidate this type of offset. 39 | typedef uint64_t ByteQueueOffset; 40 | 41 | // Initialize the queue with a given capacity limit. 42 | // This is just a soft limit. The queue will allocate 43 | // dynamically as needed up to this limit and won't 44 | // grow further. When the limit is reached, chttp_queue_full 45 | // returns true. 46 | void byte_queue_init(ByteQueue *queue, uint32_t limit); 47 | 48 | // Free resources associated to this queue 49 | void byte_queue_free(ByteQueue *queue); 50 | 51 | // Check whether an error occurred inside the queue 52 | int byte_queue_error(ByteQueue *queue); 53 | 54 | // Returns 1 if the queue has no bytes inside it, 55 | // or 0 otherwise. 56 | int byte_queue_empty(ByteQueue *queue); 57 | 58 | // Returns 1 if the queue reached its limit, or 0 59 | // otherwise. 60 | int byte_queue_full(ByteQueue *queue); 61 | 62 | // These two functions are to be used together. 63 | // read_buf returns a view into the queue of the 64 | // bytes that can be read from it. The caller can 65 | // decide how many of those bytes can be removed 66 | // by passing the count to the read_ack function. 67 | // If an error occurred inside the queue, this 68 | // function returns an empty view. 69 | // 70 | // Note that the calls to read_buf and read_ack 71 | // may be far apart. Other operations won't interfere 72 | // with the read. The only rule is you can't call 73 | // read_buf multiple times before calling read_ack. 74 | ByteView byte_queue_read_buf(ByteQueue *queue); 75 | void byte_queue_read_ack(ByteQueue *queue, uint32_t num); 76 | 77 | // Similar to the read_buf/read_ack functions, 78 | // but write_buf returns a view of the unused 79 | // memory inside the queue, and write_ack is 80 | // used to tell the queue how many bytes were 81 | // written into it. Note that to ensure there 82 | // is a minimum amount of free space in the queue, 83 | // the user needs to call byte_queue_setmincap. 84 | // If an error occurred inside the queue, this 85 | // function returns an empty view. 86 | // 87 | // Note that the calls to write_buf and write_ack 88 | // may be far apart. Other operations won't interfere 89 | // with the write (except for other byte_queue_write_* 90 | // functions). The only rule is you can't call 91 | // write_buf multiple times before calling write_ack. 92 | ByteView byte_queue_write_buf(ByteQueue *queue); 93 | void byte_queue_write_ack(ByteQueue *queue, uint32_t num); 94 | 95 | // Sets the minimum capacity for the next write 96 | // operation and returns 1 if the content of the 97 | // queue was moved, else 0 is returned. 98 | // 99 | // You must not call this function while a write 100 | // is pending. In other words, you must do this: 101 | // 102 | // byte_queue_write_setmincap(queue, mincap); 103 | // dst = byte_queue_write_buf(queue, &cap); 104 | // ... 105 | // byte_queue_write_ack(num); 106 | // 107 | // And NOT this: 108 | // 109 | // dst = byte_queue_write_buf(queue); 110 | // byte_queue_write_setmincap(queue, mincap); <-- BAD 111 | // ... 112 | // byte_queue_write_ack(num); 113 | // 114 | int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap); 115 | 116 | // Write some bytes to the queue. This is a 117 | // short hand for write_buf/memcpy/write_ack 118 | void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len); 119 | 120 | // Write the result of the format into the queue 121 | void byte_queue_write_fmt(ByteQueue *queue, const char *fmt, ...); 122 | 123 | // Write the result of the format into the queue 124 | void byte_queue_write_fmt2(ByteQueue *queue, const char *fmt, 125 | va_list args); 126 | 127 | // Returns the current offset inside the queue 128 | ByteQueueOffset byte_queue_offset(ByteQueue *queue); 129 | 130 | // Writes some bytes at the specified offset. It's 131 | // the responsibility of the user to make sure that 132 | // the offset still refers to content inside the queue. 133 | void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len); 134 | 135 | // Returns the number of bytes from the given offset 136 | // to the end of the queue. 137 | uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off); 138 | 139 | // Removes all bytes from the given offset to the the 140 | // end of the queue. 141 | void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset); 142 | -------------------------------------------------------------------------------- /src/secure_context.c: -------------------------------------------------------------------------------- 1 | 2 | int global_secure_context_init(void) 3 | { 4 | #ifdef HTTPS_ENABLED 5 | SSL_library_init(); 6 | SSL_load_error_strings(); 7 | OpenSSL_add_all_algorithms(); 8 | #endif 9 | return 0; 10 | } 11 | 12 | int global_secure_context_free(void) 13 | { 14 | #ifdef HTTPS_ENABLED 15 | EVP_cleanup(); 16 | #endif 17 | return 0; 18 | } 19 | 20 | int client_secure_context_init(ClientSecureContext *ctx) 21 | { 22 | #ifdef HTTPS_ENABLED 23 | SSL_CTX *p = SSL_CTX_new(TLS_client_method()); 24 | if (!p) 25 | return -1; 26 | 27 | SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); 28 | 29 | SSL_CTX_set_verify(p, SSL_VERIFY_PEER, NULL); 30 | 31 | if (SSL_CTX_set_default_verify_paths(p) != 1) { 32 | SSL_CTX_free(p); 33 | return -1; 34 | } 35 | 36 | ctx->p = p; 37 | return 0; 38 | #else 39 | (void) ctx; 40 | return -1; 41 | #endif 42 | } 43 | 44 | void client_secure_context_free(ClientSecureContext *ctx) 45 | { 46 | #ifdef HTTPS_ENABLED 47 | SSL_CTX_free(ctx->p); 48 | #else 49 | (void) ctx; 50 | #endif 51 | } 52 | 53 | #ifdef HTTPS_ENABLED 54 | static int servername_callback(SSL *ssl, int *ad, void *arg) 55 | { 56 | ServerSecureContext *ctx = arg; 57 | 58 | // The 'ad' parameter is used to set the alert description when returning 59 | // SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused. 60 | (void) ad; 61 | 62 | const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 63 | if (servername == NULL) 64 | return SSL_TLSEXT_ERR_NOACK; 65 | 66 | for (int i = 0; i < ctx->num_certs; i++) { 67 | ServerCertificate *cert = &ctx->certs[i]; 68 | if (!strcmp(cert->domain, servername)) { 69 | SSL_set_SSL_CTX(ssl, cert->ctx); 70 | return SSL_TLSEXT_ERR_OK; 71 | } 72 | } 73 | 74 | return SSL_TLSEXT_ERR_NOACK; 75 | } 76 | #endif 77 | 78 | int server_secure_context_init(ServerSecureContext *ctx, 79 | CHTTP_String cert_file, CHTTP_String key_file) 80 | { 81 | #ifdef HTTPS_ENABLED 82 | SSL_CTX *p = SSL_CTX_new(TLS_server_method()); 83 | if (!p) 84 | return -1; 85 | 86 | SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); 87 | 88 | char cert_buffer[1024]; 89 | if (cert_file.len >= (int) sizeof(cert_buffer)) { 90 | SSL_CTX_free(p); 91 | return -1; 92 | } 93 | memcpy(cert_buffer, cert_file.ptr, cert_file.len); 94 | cert_buffer[cert_file.len] = '\0'; 95 | 96 | // Copy private key file path to static buffer 97 | char key_buffer[1024]; 98 | if (key_file.len >= (int) sizeof(key_buffer)) { 99 | SSL_CTX_free(p); 100 | return -1; 101 | } 102 | memcpy(key_buffer, key_file.ptr, key_file.len); 103 | key_buffer[key_file.len] = '\0'; 104 | 105 | // Load certificate and private key 106 | if (SSL_CTX_use_certificate_chain_file(p, cert_buffer) != 1) { 107 | SSL_CTX_free(p); 108 | return -1; 109 | } 110 | 111 | if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { 112 | SSL_CTX_free(p); 113 | return -1; 114 | } 115 | 116 | // Verify that the private key matches the certificate 117 | if (SSL_CTX_check_private_key(p) != 1) { 118 | SSL_CTX_free(p); 119 | return -1; 120 | } 121 | 122 | SSL_CTX_set_tlsext_servername_callback(p, servername_callback); 123 | SSL_CTX_set_tlsext_servername_arg(p, ctx); 124 | 125 | ctx->p = p; 126 | ctx->num_certs = 0; 127 | return 0; 128 | #else 129 | (void) ctx; 130 | (void) cert_file; 131 | (void) key_file; 132 | return -1; 133 | #endif 134 | } 135 | 136 | void server_secure_context_free(ServerSecureContext *ctx) 137 | { 138 | #ifdef HTTPS_ENABLED 139 | SSL_CTX_free(ctx->p); 140 | for (int i = 0; i < ctx->num_certs; i++) 141 | SSL_CTX_free(ctx->certs[i].ctx); 142 | #else 143 | (void) ctx; 144 | #endif 145 | } 146 | 147 | int server_secure_context_add_certificate(ServerSecureContext *ctx, 148 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file) 149 | { 150 | #ifdef HTTPS_ENABLED 151 | if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT) 152 | return -1; 153 | 154 | SSL_CTX *p = SSL_CTX_new(TLS_server_method()); 155 | if (!p) 156 | return -1; 157 | 158 | SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); 159 | 160 | char cert_buffer[1024]; 161 | if (cert_file.len >= (int) sizeof(cert_buffer)) { 162 | SSL_CTX_free(p); 163 | return -1; 164 | } 165 | memcpy(cert_buffer, cert_file.ptr, cert_file.len); 166 | cert_buffer[cert_file.len] = '\0'; 167 | 168 | char key_buffer[1024]; 169 | if (key_file.len >= (int) sizeof(key_buffer)) { 170 | SSL_CTX_free(p); 171 | return -1; 172 | } 173 | memcpy(key_buffer, key_file.ptr, key_file.len); 174 | key_buffer[key_file.len] = '\0'; 175 | 176 | if (SSL_CTX_use_certificate_chain_file(p, cert_buffer) != 1) { 177 | SSL_CTX_free(p); 178 | return -1; 179 | } 180 | 181 | if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { 182 | SSL_CTX_free(p); 183 | return -1; 184 | } 185 | 186 | if (SSL_CTX_check_private_key(p) != 1) { 187 | SSL_CTX_free(p); 188 | return -1; 189 | } 190 | 191 | ServerCertificate *cert = &ctx->certs[ctx->num_certs]; 192 | if (domain.len >= (int) sizeof(cert->domain)) { 193 | SSL_CTX_free(p); 194 | return -1; 195 | } 196 | memcpy(cert->domain, domain.ptr, domain.len); 197 | cert->domain[domain.len] = '\0'; 198 | cert->ctx = p; 199 | ctx->num_certs++; 200 | return 0; 201 | #else 202 | (void) ctx; 203 | (void) domain; 204 | (void) cert_file; 205 | (void) key_file; 206 | return -1; 207 | #endif 208 | } 209 | -------------------------------------------------------------------------------- /examples/005_virtual_hosts_over_https.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../chttp.h" 4 | 5 | // This is an example of how to serve different websites 6 | // over a single HTTPS server instance. 7 | 8 | int setup_test_certificates(void); 9 | 10 | int main(void) 11 | { 12 | // To test this program you need to add the following 13 | // lines to your hosts file: 14 | // 15 | // 127.0.0.1 websiteA.com 16 | // 127.0.0.1 websiteB.com 17 | // 127.0.0.1 websiteC.com 18 | // 19 | // That you can find at /etc/hosts on Linux and 20 | // C:\Windows\System32\drivers\etc\hosts on Windows 21 | 22 | // First, create three certificates for the domains: 23 | // 24 | // websiteA.com 25 | // websiteB.com 26 | // websiteC.com 27 | // 28 | // This will create a number of certificate files 29 | // and private key files 30 | // 31 | // websiteA_cert.pem websiteA_key.pem 32 | // websiteB_cert.pem websiteB_key.pem 33 | // websiteC_cert.pem websiteC_key.pem 34 | // 35 | // Of course this is just for testing. It is expected 36 | // you have your own. 37 | int ret = setup_test_certificates(); 38 | if (ret < 0) 39 | return -1; 40 | 41 | CHTTP_Server server; 42 | ret = chttp_server_init(&server); 43 | if (ret < 0) { 44 | fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret)); 45 | return -1; 46 | } 47 | 48 | chttp_server_set_reuse_addr(&server, true); 49 | chttp_server_set_trace_bytes(&server, true); 50 | 51 | 52 | // First, set up an HTTPS server instance with one 53 | // of the certificate. This will act as default certificate 54 | // when ecrypted connections don't target a specific domain. 55 | 56 | CHTTP_String local_addr = CHTTP_STR("127.0.0.1"); 57 | uint16_t local_port = 8443; 58 | 59 | CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem"); 60 | CHTTP_String key_file = CHTTP_STR("websiteA_key.pem"); 61 | 62 | ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file); 63 | if (ret < 0) { 64 | fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret)); 65 | return -1; 66 | } 67 | 68 | // Then we can add an arbitrary number of additional 69 | // certificates using the add_website function 70 | 71 | ret = chttp_server_add_certificate(&server, 72 | CHTTP_STR("websiteB.com"), 73 | CHTTP_STR("websiteB_cert.pem"), 74 | CHTTP_STR("websiteB_key.pem")); 75 | if (ret < 0) 76 | return -1; 77 | 78 | ret = chttp_server_add_certificate(&server, 79 | CHTTP_STR("websiteC.com"), 80 | CHTTP_STR("websiteC_cert.pem"), 81 | CHTTP_STR("websiteC_key.pem")); 82 | if (ret < 0) 83 | return -1; 84 | 85 | // Now the server is ready to accept incoming HTTP 86 | // or HTTPS connections. 87 | // 88 | // Note that the add_website function is only used 89 | // to serve the correct certificate to the client. 90 | // The HTTP request itself may very well hold a 91 | // different domain name in the host header: 92 | // 93 | // [client] [server] 94 | // | | 95 | // | TLS hanshake to domain1.com | 96 | // | -------------------------------> | 97 | // | | 98 | // | cert for domain1.com | 99 | // | <------------------------------- | 100 | // | | 101 | // | HTTP request to domain2.com | 102 | // | over the encrypted connection | 103 | // | established with domain1.com | 104 | // | -------------------------------> | 105 | // | | 106 | // | response as domain2.com | 107 | // | <------------------------------- | 108 | // | | 109 | 110 | for (;;) { 111 | 112 | CHTTP_Request *req; 113 | CHTTP_ResponseBuilder builder; 114 | chttp_server_wait_request(&server, &req, &builder); 115 | 116 | if (chttp_match_host(req, CHTTP_STR("websiteB.com"), 8080) || 117 | chttp_match_host(req, CHTTP_STR("websiteB.com"), 8443)) { 118 | 119 | chttp_response_builder_status(builder, 200); 120 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!")); 121 | chttp_response_builder_send(builder); 122 | 123 | } else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), 8080) || 124 | chttp_match_host(req, CHTTP_STR("websiteC.com"), 8443)) { 125 | 126 | chttp_response_builder_status(builder, 200); 127 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!")); 128 | chttp_response_builder_send(builder); 129 | 130 | } else { 131 | 132 | // Serve websiteA.com by default to be consistent 133 | // with the certificate setup 134 | 135 | chttp_response_builder_status(builder, 200); 136 | chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!")); 137 | chttp_response_builder_send(builder); 138 | } 139 | } 140 | 141 | chttp_server_free(&server); 142 | return 0; 143 | } 144 | 145 | int setup_test_certificates(void) 146 | { 147 | int ret = chttp_create_test_certificate( 148 | CHTTP_STR("IT"), 149 | CHTTP_STR("Organization A"), 150 | CHTTP_STR("websiteA.com"), 151 | CHTTP_STR("websiteA_cert.pem"), 152 | CHTTP_STR("websiteA_key.pem") 153 | ); 154 | if (ret < 0) 155 | return -1; 156 | 157 | ret = chttp_create_test_certificate( 158 | CHTTP_STR("IT"), 159 | CHTTP_STR("Organization B"), 160 | CHTTP_STR("websiteB.com"), 161 | CHTTP_STR("websiteB_cert.pem"), 162 | CHTTP_STR("websiteB_key.pem") 163 | ); 164 | if (ret < 0) 165 | return -1; 166 | 167 | ret = chttp_create_test_certificate( 168 | CHTTP_STR("IT"), 169 | CHTTP_STR("Organization C"), 170 | CHTTP_STR("websiteC.com"), 171 | CHTTP_STR("websiteC_cert.pem"), 172 | CHTTP_STR("websiteC_key.pem") 173 | ); 174 | if (ret < 0) 175 | return -1; 176 | 177 | return 0; 178 | } 179 | -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CHTTP_SERVER_CAPACITY 3 | // The maximum ammount of requests that can be handled 4 | // in parallel. 5 | #define CHTTP_SERVER_CAPACITY (1<<9) 6 | #endif 7 | 8 | // Maximum number of descriptors the server will want 9 | // to wait on. It's one per connection plus two for the 10 | // TCP and TLS listener, plus one for the wakeup self-pipe. 11 | #define CHTTP_SERVER_POLL_CAPACITY (CHTTP_SERVER_CAPACITY+3) 12 | 13 | typedef enum { 14 | 15 | // This struct is unused 16 | CHTTP_SERVER_CONN_FREE, 17 | 18 | // No request was buffered yet. 19 | CHTTP_SERVER_CONN_BUFFERING, 20 | 21 | // A request was just buffered and is waiting for 22 | // the user to build a response. To be specific, 23 | // it's waiting for the user to set a response status. 24 | CHTTP_SERVER_CONN_WAIT_STATUS, 25 | 26 | // A request is buffered and a status was set. Now 27 | // the user can set a header or append the first 28 | // bytes of the response body. 29 | CHTTP_SERVER_CONN_WAIT_HEADER, 30 | 31 | // A request is buffered and some bytes were appended 32 | // to the response. Now the user can either append more 33 | // bytes or send out the response. 34 | CHTTP_SERVER_CONN_WAIT_BODY, 35 | 36 | // A response has been produced and it's being flushed. 37 | CHTTP_SERVER_CONN_FLUSHING, 38 | 39 | } CHTTP_ServerConnState; 40 | 41 | // This structure represents the HTTP connection to 42 | // a client. 43 | typedef struct { 44 | 45 | // If false, this struct is unused 46 | CHTTP_ServerConnState state; 47 | 48 | // Handle to the socket 49 | SocketHandle handle; 50 | 51 | // Data received by the client 52 | ByteQueue input; 53 | 54 | // Data being sent to the client 55 | ByteQueue output; 56 | 57 | // Generation counter. This is used to invalidate 58 | // response builders that refer to this connection. 59 | uint16_t gen; 60 | 61 | // This is set during the WAIT_XXX states or 62 | // the FLUSHING state. When the connection 63 | // completes flushing and no more bytes are 64 | // in the output buffer, it frees the connection 65 | // instead of turning it back to BUFFERING. 66 | bool closing; 67 | 68 | // When the state is WAIT_STATUS, WAIT_HEADER, 69 | // or WAIT_BODY, this contains the parsed version 70 | // of the buffered request. 71 | CHTTP_Request request; 72 | 73 | // Length of the buffered request when the request 74 | // field is valid. 75 | int request_len; 76 | 77 | // Offset of the first response byte in the output 78 | // buffer. This is useful when the user wants to 79 | // undo the response it's building and start from 80 | // scratch. 81 | ByteQueueOffset response_offset; 82 | 83 | // When the first byte of the response content is 84 | // written, before it are prepended special headers, 85 | // including Content-Length and Connection. This 86 | // offset points to the first byte that comes after 87 | // the string "Content-Length: ". 88 | ByteQueueOffset content_length_value_offset; 89 | 90 | // Similarly to the previous field, this one points 91 | // to the first byte of the body. This allows calculating 92 | // the length of the response content byte subtracting 93 | // it from the offset reached when the response is marked 94 | // as done. 95 | ByteQueueOffset content_length_offset; 96 | } CHTTP_ServerConn; 97 | 98 | typedef struct { 99 | 100 | // Size limit of the input and output buffer of each 101 | // connection. 102 | uint32_t input_buffer_limit; 103 | uint32_t output_buffer_limit; 104 | 105 | bool trace_bytes; 106 | bool reuse_addr; 107 | int backlog; 108 | 109 | // Array of connections. The counter contains the 110 | // number of structs such that state=FREE. 111 | int num_conns; 112 | CHTTP_ServerConn conns[CHTTP_SERVER_CAPACITY]; 113 | 114 | // Queue of indices referring to connections that 115 | // are in the WAIT_STATUS state. 116 | int num_ready; 117 | int ready_head; 118 | int ready[CHTTP_SERVER_CAPACITY]; 119 | 120 | // Asynchronous TCP and TLS socket abstraction 121 | SocketManager sockets; 122 | 123 | // The server object doesn't interact with this 124 | // field directly, it just initializes the socket 125 | // manager with a pointer to it. This allows 126 | // allocating the exact number of sockets we 127 | // will need. 128 | Socket socket_pool[CHTTP_SERVER_CAPACITY]; 129 | 130 | } CHTTP_Server; 131 | 132 | // Initialize the HTTP server object. By default, it won't 133 | // listen for connections. You need to call 134 | // 135 | // chttp_server_listen_tcp 136 | // chttp_server_listen_tls 137 | // 138 | // to listen for connection. Note that you can have a 139 | // single server listening for HTTP and HTTPS requests 140 | // by calling both. 141 | int chttp_server_init(CHTTP_Server *server); 142 | 143 | // Release resources associated to the server. 144 | void chttp_server_free(CHTTP_Server *server); 145 | 146 | // Set input and output buffer size limit for any 147 | // given connection. The default value is 1MB 148 | void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit); 149 | void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit); 150 | 151 | // TODO: Comment 152 | void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value); 153 | 154 | // TODO: Comment 155 | void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse); 156 | 157 | // TODO: comment 158 | void chttp_server_set_backlog(CHTTP_Server *server, int backlog); 159 | 160 | // Enable listening for plain HTTP requests at the 161 | // specified interface. 162 | int chttp_server_listen_tcp(CHTTP_Server *server, 163 | CHTTP_String addr, Port port); 164 | 165 | // Enable listening for HTTPS requests at the specified 166 | // interfact, using the specified certificate and key 167 | // to verify the connection. 168 | int chttp_server_listen_tls(CHTTP_Server *server, CHTTP_String addr, Port port, 169 | CHTTP_String cert_file_name, CHTTP_String key_file_name); 170 | 171 | // Add the certificate for an additional domain when 172 | // the server is listening for HTTPS requests. 173 | int chttp_server_add_certificate(CHTTP_Server *server, 174 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file); 175 | 176 | // When a thread is blocked waiting for server events, 177 | // other threads can call this function to wake it up. 178 | int chttp_server_wakeup(CHTTP_Server *server); 179 | 180 | // Resets the event register with the list of descriptors 181 | // the server wants monitored. 182 | void chttp_server_register_events(CHTTP_Server *server, 183 | EventRegister *reg); 184 | 185 | // The caller has waited for poll() to return and some 186 | // I/O events to be triggered, so now the HTTP server 187 | // can continue its buffering and flushing operations. 188 | void chttp_server_process_events(CHTTP_Server *server, 189 | EventRegister reg); 190 | 191 | typedef struct { 192 | CHTTP_Server *server; 193 | uint16_t index; 194 | uint16_t gen; 195 | } CHTTP_ResponseBuilder; 196 | 197 | // After some I/O events were processes, some requests 198 | // may be availabe. This function returns one of the 199 | // buffered requests. If a request was available, true 200 | // is returned. If no more are avaiable, false is returned. 201 | // Note that It's possible to get multiple requests to 202 | // respond in batches. 203 | // For each request returned by this function, the user 204 | // must build a response using the response builder API. 205 | bool chttp_server_next_request(CHTTP_Server *server, 206 | CHTTP_Request **request, CHTTP_ResponseBuilder *builder); 207 | 208 | // TODO: comment 209 | void chttp_server_wait_request(CHTTP_Server *server, 210 | CHTTP_Request **request, CHTTP_ResponseBuilder *builder); 211 | 212 | // This function is called to set the status code of 213 | // a request's response. If this function is called 214 | // after the other response builder functions, it will 215 | // reset the response and set a new status. 216 | void chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status); 217 | 218 | // Append a header to the response. This can only be 219 | // used after having set the status and before appending 220 | // to the body. 221 | void chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str); 222 | 223 | // Append some bytes to the response's body 224 | void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str); 225 | 226 | // TODO: comment 227 | void chttp_response_builder_body_cap(CHTTP_ResponseBuilder builder, int cap); 228 | char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap); 229 | void chttp_response_builder_body_ack(CHTTP_ResponseBuilder builder, int num); 230 | 231 | // Mark the response as complete. This will invalidate 232 | // the response builder handle. 233 | void chttp_response_builder_send(CHTTP_ResponseBuilder builder); 234 | -------------------------------------------------------------------------------- /src/client.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CHTTP_CLIENT_CAPACITY 3 | // The maximum ammount of requests that can be performed 4 | // in parallel. 5 | #define CHTTP_CLIENT_CAPACITY (1<<7) 6 | #endif 7 | 8 | // Maximum number of descriptors the client will want 9 | // to wait on. It's one per connection plus the wakeup 10 | // self-pipe. 11 | #define CHTTP_CLIENT_POLL_CAPACITY (CHTTP_CLIENT_CAPACITY+1) 12 | 13 | #ifndef CHTTP_COOKIE_JAR_CAPACITY 14 | // Maximum number of cookies that can be associated to a 15 | // single client. 16 | #define CHTTP_COOKIE_JAR_CAPACITY 128 17 | #endif 18 | 19 | typedef struct { 20 | 21 | // Cookie name and value 22 | CHTTP_String name; 23 | CHTTP_String value; 24 | 25 | // If the "exact_domain" is true, the cookie 26 | // can only be sent to the exact domain referred 27 | // to by "domain" (which is never empty). If 28 | // "exact_domain" is false, then the cookie is 29 | // compatible with subdomains. 30 | bool exact_domain; 31 | CHTTP_String domain; 32 | 33 | // If "exact_path" is set, the cookie is only 34 | // compatible with requests to paths that match 35 | // "path" exactly. If "exact_path" is not set, 36 | // then any path that starts with "path" is 37 | // compatible with the cookie. 38 | bool exact_path; 39 | CHTTP_String path; 40 | 41 | // This cookie can only be sent over HTTPS 42 | bool secure; 43 | 44 | } CHTTP_CookieJarEntry; 45 | 46 | typedef struct { 47 | int count; 48 | CHTTP_CookieJarEntry items[CHTTP_COOKIE_JAR_CAPACITY]; 49 | } CHTTP_CookieJar; 50 | 51 | typedef enum { 52 | CHTTP_CLIENT_CONN_FREE, 53 | CHTTP_CLIENT_CONN_WAIT_METHOD, 54 | CHTTP_CLIENT_CONN_WAIT_URL, 55 | CHTTP_CLIENT_CONN_WAIT_HEADER, 56 | CHTTP_CLIENT_CONN_WAIT_BODY, 57 | CHTTP_CLIENT_CONN_FLUSHING, 58 | CHTTP_CLIENT_CONN_BUFFERING, 59 | CHTTP_CLIENT_CONN_COMPLETE, 60 | } CHTTP_ClientConnState; 61 | 62 | // Fields of this struct are private 63 | typedef struct CHTTP_Client CHTTP_Client; 64 | 65 | typedef struct { 66 | CHTTP_ClientConnState state; 67 | 68 | // Handle to the socket 69 | SocketHandle handle; 70 | 71 | // Pointer back to the client 72 | CHTTP_Client *client; 73 | 74 | // Generation counter for request builder validation 75 | uint16_t gen; 76 | 77 | // Opaque pointer set by the user while building 78 | // the request. It's returned alongside the result. 79 | void *user; 80 | 81 | // TODO: comment 82 | bool trace_bytes; 83 | 84 | // TODO: comment 85 | bool dont_verify_cert; 86 | 87 | // Allocated copy of the URL string 88 | CHTTP_String url_buffer; 89 | 90 | // Parsed URL for connection establishment 91 | // All url.* pointers reference into url_buffer 92 | CHTTP_URL url; 93 | 94 | // Data received from the server 95 | ByteQueue input; 96 | 97 | // Data being sent to the server 98 | ByteQueue output; 99 | 100 | // If the request is COMPLETE, indicates 101 | // whether it completed with an error (-1) 102 | // or a success (0). If it was a success, 103 | // the response field is valid. 104 | int result; 105 | 106 | // Parsed response once complete 107 | CHTTP_Response response; 108 | 109 | // This offset points to the first byte that comes 110 | // after the string "Content-Length: ". 111 | ByteQueueOffset content_length_value_offset; 112 | 113 | // This one points to the first byte of the body. 114 | // This allows calculating the length of the request 115 | // content byte subtracting it from the offset reached 116 | // when the request is marked as done. 117 | ByteQueueOffset content_length_offset; 118 | } CHTTP_ClientConn; 119 | 120 | // Fields of this struct are private 121 | struct CHTTP_Client { 122 | 123 | // Size limit of the input and output buffer of each 124 | // connection. 125 | uint32_t input_buffer_limit; 126 | uint32_t output_buffer_limit; 127 | 128 | // List of cookies created during this session 129 | CHTTP_CookieJar cookie_jar; 130 | 131 | // Array of connections. The counter contains the 132 | // number of structs such that state!=FREE. 133 | int num_conns; 134 | CHTTP_ClientConn conns[CHTTP_CLIENT_CAPACITY]; 135 | 136 | // Queue of indices referring to connections that 137 | // are in the COMPLETE state. 138 | int num_ready; 139 | int ready_head; 140 | int ready[CHTTP_CLIENT_CAPACITY]; 141 | 142 | // Asynchronous TCP and TLS socket abstraction 143 | SocketManager sockets; 144 | 145 | // The client object doesn't interact with this 146 | // field directly, it just initializes the socket 147 | // manager with a pointer to it. This allows 148 | // allocating the exact number of sockets we 149 | // will need. 150 | Socket socket_pool[CHTTP_CLIENT_CAPACITY]; 151 | }; 152 | 153 | // Initialize an HTTP client object. This allows one to 154 | // perform a number of requests in parallel. 155 | int chttp_client_init(CHTTP_Client *client); 156 | 157 | // Release resources associated to a client object. 158 | void chttp_client_free(CHTTP_Client *client); 159 | 160 | // Set input and output buffer size limit for any 161 | // given connection. The default value is 1MB 162 | void chttp_client_set_input_limit(CHTTP_Client *client, uint32_t limit); 163 | void chttp_client_set_output_limit(CHTTP_Client *client, uint32_t limit); 164 | 165 | // When a thread is blocked waiting for client events, 166 | // other threads can call this function to wake it up. 167 | int chttp_client_wakeup(CHTTP_Client *client); 168 | 169 | typedef struct { 170 | CHTTP_Client *client; 171 | uint16_t index; 172 | uint16_t gen; 173 | } CHTTP_RequestBuilder; 174 | 175 | // Create a new request builder object. 176 | CHTTP_RequestBuilder chttp_client_get_builder(CHTTP_Client *client); 177 | 178 | // TODO: comment 179 | void chttp_request_builder_set_user(CHTTP_RequestBuilder builder, 180 | void *user); 181 | 182 | // TODO: comment 183 | void chttp_request_builder_trace(CHTTP_RequestBuilder builder, 184 | bool trace_bytes); 185 | 186 | // TODO: comment 187 | void chttp_request_builder_insecure(CHTTP_RequestBuilder builder, 188 | bool insecure); 189 | 190 | // Set the method of the current request. This is the first 191 | // function of the request builder that the user must call. 192 | void chttp_request_builder_method(CHTTP_RequestBuilder builder, 193 | CHTTP_Method method); 194 | 195 | // Set the URL of the current request. This must be set after 196 | // the method and before any header/body 197 | void chttp_request_builder_target(CHTTP_RequestBuilder builder, 198 | CHTTP_String url); 199 | 200 | // After the URL, the user may set zero or more headers. 201 | void chttp_request_builder_header(CHTTP_RequestBuilder builder, 202 | CHTTP_String str); 203 | 204 | // Append bytes to the request's body. You can call this 205 | // any amount of times, as long as it's after having set 206 | // the URL. 207 | void chttp_request_builder_body(CHTTP_RequestBuilder builder, 208 | CHTTP_String str); 209 | 210 | // Mark this request as complete. This invalidates the 211 | // builder. 212 | // Returns 0 on success, -1 on error. 213 | int chttp_request_builder_send(CHTTP_RequestBuilder builder); 214 | 215 | // Resets the event register with the list of descriptors 216 | // the client wants monitored. 217 | void chttp_client_register_events(CHTTP_Client *client, 218 | EventRegister *reg); 219 | 220 | // The caller has waited for poll() to return and some 221 | // I/O events to be triggered, so now the HTTP client 222 | // can continue its buffering and flushing operations. 223 | void chttp_client_process_events(CHTTP_Client *client, 224 | EventRegister reg); 225 | 226 | // After some I/O events were processes, some responses 227 | // may be availabe. This function returns one of the 228 | // buffered responses. If a request was available, true 229 | // is returned. If no more are avaiable, false is returned. 230 | // The returned response must be freed using the 231 | // chttp_free_response function. 232 | // TODO: Better comment talking about output arguments 233 | bool chttp_client_next_response(CHTTP_Client *client, 234 | int *result, void **user, CHTTP_Response **response); 235 | 236 | // TODO: comment 237 | void chttp_client_wait_response(CHTTP_Client *client, 238 | int *result, void **user, CHTTP_Response **response); 239 | 240 | // Free a response object. You can't access its fields 241 | // again after this. 242 | void chttp_free_response(CHTTP_Response *response); 243 | 244 | // Perform a blocking GET request 245 | int chttp_get(CHTTP_String url, CHTTP_String *headers, 246 | int num_headers, CHTTP_Response **response); 247 | 248 | // Perform a blocking POST request 249 | int chttp_post(CHTTP_String url, CHTTP_String *headers, 250 | int num_headers, CHTTP_String body, 251 | CHTTP_Response **response); 252 | 253 | // Perform a blocking PUT request 254 | int chttp_put(CHTTP_String url, CHTTP_String *headers, 255 | int num_headers, CHTTP_String body, 256 | CHTTP_Response **response); 257 | 258 | // Perform a blocking DELETE request 259 | int chttp_delete(CHTTP_String url, CHTTP_String *headers, 260 | int num_headers, CHTTP_Response **response); 261 | -------------------------------------------------------------------------------- /src/byte_queue.c: -------------------------------------------------------------------------------- 1 | 2 | void byte_queue_init(ByteQueue *queue, uint32_t limit) 3 | { 4 | queue->flags = 0; 5 | queue->head = 0; 6 | queue->size = 0; 7 | queue->used = 0; 8 | queue->curs = 0; 9 | queue->limit = limit; 10 | queue->data = NULL; 11 | queue->read_target = NULL; 12 | } 13 | 14 | // Deinitialize the queue 15 | void byte_queue_free(ByteQueue *queue) 16 | { 17 | if (queue->read_target) { 18 | if (queue->read_target != queue->data) 19 | free(queue->read_target); 20 | queue->read_target = NULL; 21 | queue->read_target_size = 0; 22 | } 23 | 24 | free(queue->data); 25 | queue->data = NULL; 26 | } 27 | 28 | int byte_queue_error(ByteQueue *queue) 29 | { 30 | return queue->flags & BYTE_QUEUE_ERROR; 31 | } 32 | 33 | int byte_queue_empty(ByteQueue *queue) 34 | { 35 | return queue->used == 0; 36 | } 37 | 38 | int byte_queue_full(ByteQueue *queue) 39 | { 40 | return queue->used == queue->limit; 41 | } 42 | 43 | ByteView byte_queue_read_buf(ByteQueue *queue) 44 | { 45 | if (queue->flags & BYTE_QUEUE_ERROR) 46 | return (ByteView) {NULL, 0}; 47 | 48 | assert((queue->flags & BYTE_QUEUE_READ) == 0); 49 | queue->flags |= BYTE_QUEUE_READ; 50 | queue->read_target = queue->data; 51 | queue->read_target_size = queue->size; 52 | 53 | if (queue->data == NULL) 54 | return (ByteView) {NULL, 0}; 55 | 56 | return (ByteView) { queue->data + queue->head, queue->used }; 57 | } 58 | 59 | void byte_queue_read_ack(ByteQueue *queue, uint32_t num) 60 | { 61 | if (queue->flags & BYTE_QUEUE_ERROR) 62 | return; 63 | 64 | if ((queue->flags & BYTE_QUEUE_READ) == 0) 65 | return; 66 | 67 | queue->flags &= ~BYTE_QUEUE_READ; 68 | 69 | assert((uint32_t) num <= queue->used); 70 | queue->head += (uint32_t) num; 71 | queue->used -= (uint32_t) num; 72 | queue->curs += (uint32_t) num; 73 | 74 | if (queue->read_target) { 75 | if (queue->read_target != queue->data) 76 | free(queue->read_target); 77 | queue->read_target = NULL; 78 | queue->read_target_size = 0; 79 | } 80 | } 81 | 82 | ByteView byte_queue_write_buf(ByteQueue *queue) 83 | { 84 | if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) 85 | return (ByteView) {NULL, 0}; 86 | 87 | assert((queue->flags & BYTE_QUEUE_WRITE) == 0); 88 | queue->flags |= BYTE_QUEUE_WRITE; 89 | 90 | return (ByteView) { 91 | queue->data + (queue->head + queue->used), 92 | queue->size - (queue->head + queue->used), 93 | }; 94 | } 95 | 96 | void byte_queue_write_ack(ByteQueue *queue, uint32_t num) 97 | { 98 | if (queue->flags & BYTE_QUEUE_ERROR) 99 | return; 100 | 101 | if ((queue->flags & BYTE_QUEUE_WRITE) == 0) 102 | return; 103 | 104 | queue->flags &= ~BYTE_QUEUE_WRITE; 105 | queue->used += num; 106 | } 107 | 108 | int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap) 109 | { 110 | // Sticky error 111 | if (queue->flags & BYTE_QUEUE_ERROR) 112 | return 0; 113 | 114 | // In general, the queue's contents look like this: 115 | // 116 | // size 117 | // v 118 | // [___xxxxxxxxxxxx________] 119 | // ^ ^ ^ 120 | // 0 head head + used 121 | // 122 | // This function needs to make sure that at least [mincap] 123 | // bytes are available on the right side of the content. 124 | // 125 | // We have 3 cases: 126 | // 127 | // 1) If there is enough memory already, this function doesn't 128 | // need to do anything. 129 | // 130 | // 2) If there isn't enough memory on the right but there is 131 | // enough free memory if we cound the left unused region, 132 | // then the content is moved back to the 133 | // start of the buffer. 134 | // 135 | // 3) If there isn't enough memory considering both sides, this 136 | // function needs to allocate a new buffer. 137 | // 138 | // If there are pending read or write operations, the application 139 | // is holding pointers to the buffer, so we need to make sure 140 | // to not invalidate them. The only real problem is pending reads 141 | // since this function can only be called before starting a write 142 | // opearation. 143 | // 144 | // To avoid invalidating the read pointer when we allocate a new 145 | // buffer, we don't free the old buffer. Instead, we store the 146 | // pointer in the "old" field so that the read ack function can 147 | // free it. 148 | // 149 | // To avoid invalidating the pointer when we are moving back the 150 | // content since there is enough memory at the start of the buffer, 151 | // we just avoid that. Even if there is enough memory considering 152 | // left and right free regions, we allocate a new buffer. 153 | 154 | assert((queue->flags & BYTE_QUEUE_WRITE) == 0); 155 | 156 | uint32_t total_free_space = queue->size - queue->used; 157 | uint32_t free_space_after_data = queue->size - queue->used - queue->head; 158 | 159 | int moved = 0; 160 | if (free_space_after_data < mincap) { 161 | 162 | if (total_free_space < mincap || (queue->read_target == queue->data)) { 163 | // Resize required 164 | 165 | if (queue->used + mincap > queue->limit) { 166 | queue->flags |= BYTE_QUEUE_ERROR; 167 | return 0; 168 | } 169 | 170 | uint32_t size; 171 | if (queue->size > UINT32_MAX / 2) 172 | size = UINT32_MAX; 173 | else 174 | size = 2 * queue->size; 175 | 176 | if (size < queue->used + mincap) 177 | size = queue->used + mincap; 178 | 179 | if (size > queue->limit) 180 | size = queue->limit; 181 | 182 | char *data = malloc(size); 183 | if (!data) { 184 | queue->flags |= BYTE_QUEUE_ERROR; 185 | return 0; 186 | } 187 | 188 | if (queue->used > 0) 189 | memcpy(data, queue->data + queue->head, queue->used); 190 | 191 | if (queue->read_target != queue->data) 192 | free(queue->data); 193 | 194 | queue->data = data; 195 | queue->head = 0; 196 | queue->size = size; 197 | 198 | } else { 199 | // Move required 200 | memmove(queue->data, queue->data + queue->head, queue->used); 201 | queue->head = 0; 202 | } 203 | 204 | moved = 1; 205 | } 206 | 207 | return moved; 208 | } 209 | 210 | void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len) 211 | { 212 | byte_queue_write_setmincap(queue, len); 213 | ByteView dst = byte_queue_write_buf(queue); 214 | if (dst.ptr) { 215 | memcpy(dst.ptr, ptr, len); 216 | byte_queue_write_ack(queue, len); 217 | } 218 | } 219 | 220 | void byte_queue_write_fmt2(ByteQueue *queue, 221 | const char *fmt, va_list args) 222 | { 223 | if (queue->flags & BYTE_QUEUE_ERROR) 224 | return; 225 | 226 | va_list args2; 227 | va_copy(args2, args); 228 | 229 | byte_queue_write_setmincap(queue, 128); 230 | ByteView dst = byte_queue_write_buf(queue); 231 | 232 | int len = vsnprintf(dst.ptr, dst.len, fmt, args); 233 | if (len < 0) { 234 | queue->flags |= BYTE_QUEUE_ERROR; 235 | va_end(args2); 236 | return; 237 | } 238 | 239 | if ((size_t) len > dst.len) { 240 | byte_queue_write_ack(queue, 0); 241 | byte_queue_write_setmincap(queue, len+1); 242 | dst = byte_queue_write_buf(queue); 243 | vsnprintf(dst.ptr, dst.len, fmt, args2); 244 | } 245 | 246 | byte_queue_write_ack(queue, len); 247 | 248 | va_end(args2); 249 | } 250 | 251 | void byte_queue_write_fmt(ByteQueue *queue, 252 | const char *fmt, ...) 253 | { 254 | va_list args; 255 | va_start(args, fmt); 256 | byte_queue_write_fmt2(queue, fmt, args); 257 | va_end(args); 258 | } 259 | 260 | ByteQueueOffset byte_queue_offset(ByteQueue *queue) 261 | { 262 | if (queue->flags & BYTE_QUEUE_ERROR) 263 | return (ByteQueueOffset) { 0 }; 264 | return (ByteQueueOffset) { queue->curs + queue->used }; 265 | } 266 | 267 | void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, 268 | void *src, uint32_t len) 269 | { 270 | if (queue->flags & BYTE_QUEUE_ERROR) 271 | return; 272 | 273 | // Check that the offset is in range 274 | assert(off >= queue->curs && off - queue->curs < queue->used); 275 | 276 | // Check that the length is in range 277 | assert(len <= queue->used - (off - queue->curs)); 278 | 279 | // Perform the patch 280 | char *dst = queue->data + queue->head + (off - queue->curs); 281 | memcpy(dst, src, len); 282 | } 283 | 284 | uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off) 285 | { 286 | return queue->curs + queue->used - off; 287 | } 288 | 289 | void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset) 290 | { 291 | if (queue->flags & BYTE_QUEUE_ERROR) 292 | return; 293 | 294 | uint64_t num = (queue->curs + queue->used) - offset; 295 | assert(num <= queue->used); 296 | 297 | queue->used -= num; 298 | } 299 | -------------------------------------------------------------------------------- /src/socket.h: -------------------------------------------------------------------------------- 1 | // This file (and its relative .c file) implements an asynchronous TCP/TLS 2 | // server and client abstraction. 3 | // 4 | // It introduces the concept of a "socket manager", which is a pool of 5 | // connection sockets and a listener socket. The listener is managed 6 | // internally, which means the manager automatically accepts sockets 7 | // from it and adds them to the pool. 8 | // 9 | // If the listener is configured using the function: 10 | // 11 | // socket_manager_listen_tcp 12 | // 13 | // the resulting connections will not use TLS. If instead the listener 14 | // is configured using: 15 | // 16 | // socket_manager_listen_tls 17 | // 18 | // the listener will use TLS. Note that both functions can be used on 19 | // the same manager to allow both plaintext and encrypted connections. 20 | // Users may enable zero listeners, in which case only outgoing 21 | // connections are allowed (more on this later). 22 | // 23 | // Once the manager is set up, one can wait for events by following 24 | // this pattern: 25 | // 26 | // struct pollfd polled[...]; 27 | // int num_polled = socket_manager_register_events(sm, polled, max_polled); 28 | // poll(polled, num_polled, -1); 29 | // 30 | // #define MAX_EVENTS ... 31 | // SocketEvent events[MAX_EVENTS]; 32 | // int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled); 33 | // for (int i = 0; i < num_events; i++) { 34 | // ... Here call socket_recv, socket_send, socket_close, ... 35 | // } 36 | // 37 | // Note that from the user's perspective, there is no difference 38 | // between connections that use plain TCP and those that use TCP/TLS. 39 | // 40 | // Users can also establish outgoing connections by calling the 41 | // function: 42 | // 43 | // socket_connect 44 | // 45 | // Which allows the creation of a connection towards an host given 46 | // its domain, IPv4, IPv6, or an array of them. This can be done both 47 | // for TCP and TCP/TLS connection. Note that users that only intend 48 | // to establish outgoing connection may omit the configuration of 49 | // listeners entirely. 50 | 51 | #ifdef _WIN32 52 | #define NATIVE_SOCKET SOCKET 53 | #define NATIVE_SOCKET_INVALID INVALID_SOCKET 54 | #define CLOSE_NATIVE_SOCKET closesocket 55 | #else 56 | #define NATIVE_SOCKET int 57 | #define NATIVE_SOCKET_INVALID -1 58 | #define CLOSE_NATIVE_SOCKET close 59 | #endif 60 | 61 | typedef uint32_t SocketHandle; 62 | #define SOCKET_HANDLE_INVALID ((SocketHandle) 0) 63 | 64 | typedef uint16_t Port; 65 | 66 | typedef enum { 67 | SOCKET_EVENT_READY, 68 | SOCKET_EVENT_CREATION_TIMEOUT, 69 | SOCKET_EVENT_RECV_TIMEOUT, 70 | SOCKET_EVENT_DISCONNECT, 71 | } SocketEventType; 72 | 73 | typedef struct { 74 | SocketEventType type; 75 | SocketHandle handle; 76 | void* user; 77 | } SocketEvent; 78 | 79 | // Internal use only 80 | typedef enum { 81 | 82 | // The Socket struct is unused 83 | SOCKET_STATE_FREE, 84 | 85 | // The state associated to a socket created 86 | // by a connect operation that hasn't been 87 | // processed yet. 88 | SOCKET_STATE_PENDING, 89 | 90 | // A connect() operation was started but is 91 | // still pending. 92 | SOCKET_STATE_CONNECTING, 93 | 94 | // Outgoing connection was established, but 95 | // a TLS handshake may need to be performed. 96 | SOCKET_STATE_CONNECTED, 97 | 98 | // Incoming connection was established, but 99 | // a TLS handshake may need to be performed. 100 | SOCKET_STATE_ACCEPTED, 101 | 102 | // The connection was esablished, but the user 103 | // wants to perform a read or write operation that 104 | // would block. 105 | SOCKET_STATE_ESTABLISHED_WAIT, 106 | 107 | // The connection was established and it's possible 108 | // to perform read or write operations on it without 109 | // blocking. 110 | SOCKET_STATE_ESTABLISHED_READY, 111 | 112 | // The socket was marked to be closed. 113 | SOCKET_STATE_SHUTDOWN, 114 | 115 | // The current socket is was closed. The only 116 | // valid thing to do here is free its resources. 117 | SOCKET_STATE_DIED, 118 | } SocketState; 119 | 120 | typedef struct { 121 | int refs; 122 | char data[]; 123 | } RegisteredName; 124 | 125 | // Internal use only 126 | typedef struct { 127 | union { 128 | CHTTP_IPv4 ipv4; 129 | CHTTP_IPv6 ipv6; 130 | }; 131 | bool is_ipv4; 132 | Port port; 133 | 134 | #ifdef HTTPS_ENABLED 135 | // When connecting to a peer using TLS, if the address 136 | // was resolved from a registered name, that name is 137 | // used to request the correct certificate once the TCP 138 | // handshake is established, and therefore need to 139 | // store it somewhere until that happens. 140 | RegisteredName *name; 141 | #endif 142 | } AddressAndPort; 143 | 144 | // Internal use only 145 | typedef struct { 146 | SocketState state; 147 | 148 | // OS-specific socket type 149 | NATIVE_SOCKET sock; 150 | 151 | // Native socket events that need to be monitored 152 | int events; 153 | 154 | // If this is set, the raw socket handle shouldn't be monitored 155 | bool silent; 156 | 157 | // Generation counter to invalidate any SocketHandle 158 | // referring to this socket when it is freed. 159 | // Note that this counter may wrap but always skips 160 | // the 0 value to ensure the 0 SocketHandle is always 161 | // invalid. 162 | uint16_t gen; 163 | 164 | // User-provided context pointer 165 | void *user; 166 | 167 | Time creation_time; 168 | Time last_recv_time; 169 | 170 | Time recv_timeout; 171 | Time creation_timeout; 172 | 173 | // A single connect operation may involve 174 | // trying to establish a connection towards 175 | // one of a set of addresses. 176 | int num_addr; 177 | int next_addr; 178 | union { 179 | AddressAndPort addr; // When num_addr=1 180 | AddressAndPort *addrs; // Dynamically allocated when num_addr>1 181 | }; 182 | 183 | #ifdef HTTPS_ENABLED 184 | ClientSecureContext *client_secure_context; 185 | ServerSecureContext *server_secure_context; 186 | SSL *ssl; 187 | bool dont_verify_cert; 188 | #endif 189 | 190 | } Socket; 191 | 192 | // Glorified array of sockets. This structure 193 | // is private to the .c file associated to this 194 | // header. 195 | typedef struct { 196 | 197 | // TODO: comment 198 | Time recv_timeout; 199 | Time creation_timeout; 200 | 201 | // TCP listener sockets. The first is intended 202 | // for plaintext, while the second is for TLS. 203 | // The socket manager will accept and add new 204 | // sockets to the pool automatically. Note that 205 | // either may be unset. If both are unset, users 206 | // can only create outgoing connections. 207 | NATIVE_SOCKET plain_sock; 208 | NATIVE_SOCKET secure_sock; 209 | 210 | // Handles for the self-pipe trick necessary for 211 | // other threads to wake up sockets blocked on 212 | // poll(). 213 | NATIVE_SOCKET wait_sock; 214 | NATIVE_SOCKET signal_sock; 215 | 216 | // TLS contexts. One is used for outgoing connections 217 | // (the client context) and one for incoming 218 | // connections (server). If the secure_sock is 219 | // set, the server context is initialized. If at 220 | // least one connect was performed using TLS 221 | // (and the flag is set), the client context is 222 | // initialized. 223 | bool at_least_one_secure_connect; 224 | ClientSecureContext client_secure_context; 225 | ServerSecureContext server_secure_context; 226 | 227 | // If the socket manager needed to initialize some 228 | // global state for its initialization, this flag 229 | // will be set so that it will remember to cleanup 230 | // that state during deinitialization. 231 | bool global_cleanup; 232 | 233 | // Array of sockets. Structs with state FREE 234 | // are unused. 235 | int num_used; 236 | int max_used; 237 | Socket *sockets; 238 | 239 | } SocketManager; 240 | 241 | // Instanciate a socket manager. Returns 0 on 242 | // success and -1 on error. 243 | int socket_manager_init(SocketManager *sm, Socket *socks, 244 | int num_socks); 245 | 246 | // Deinitialize a socket manager 247 | void socket_manager_free(SocketManager *sm); 248 | 249 | void socket_manager_set_creation_timeout(SocketManager *sm, int timeout); 250 | void socket_manager_set_recv_timeout(SocketManager *sm, int timeout); 251 | 252 | // Configure the socket manager to listen on 253 | // the specified interface for TCP connections. 254 | // Incoming connections will be automatically 255 | // added to the internal pool. This function 256 | // can only be used once per manager. 257 | // Returns 0 on success, -1 on error. 258 | int socket_manager_listen_tcp(SocketManager *sm, 259 | CHTTP_String addr, Port port, int backlog, 260 | bool reuse_addr); 261 | 262 | // Same as the previous function, but incoming 263 | // connections will be interpreted as TLS. You 264 | // can only call this function once per manager, 265 | // but you can call this and the plaintext variant 266 | // on the same manager to accept both plaintext 267 | // and secure connections. 268 | // Returns 0 on success, -1 on error. 269 | int socket_manager_listen_tls(SocketManager *sm, 270 | CHTTP_String addr, Port port, int backlog, 271 | bool reuse_addr, CHTTP_String cert_file, 272 | CHTTP_String key_file); 273 | 274 | // If the socket manager was configures to accept 275 | // TLS connections, this adds additional certificates 276 | // the client can use to verify the server's 277 | // authenticity. 278 | // Returns 0 on success, -1 on error. 279 | int socket_manager_add_certificate(SocketManager *sm, 280 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file); 281 | 282 | // When a thread is blocked on a poll() call for 283 | // descriptors associated to this socket manager, 284 | // other threads can call this function to wake 285 | // up that blocked thread. 286 | // Returns 0 on success, -1 on error. 287 | int socket_manager_wakeup(SocketManager *sm); 288 | 289 | typedef struct { 290 | void **ptrs; 291 | struct pollfd *polled; 292 | int num_polled; 293 | int timeout; 294 | } EventRegister; 295 | 296 | // Resets the event register with the list of descriptors 297 | // the socket manager wants monitored. 298 | void socket_manager_register_events(SocketManager *sm, 299 | EventRegister *reg); 300 | 301 | // After poll() is called on the previously registered 302 | // pollfd array and the revents fields are set, this 303 | // function processes those events to produce higher-level 304 | // socket events. Returns the number of socket events 305 | // written to the output array, or -1 on error. 306 | // 307 | // The maximum number of events this will write 308 | // to the events array is equal to the numero of 309 | // socket structs provided to the socket manager 310 | // via the init function. 311 | int socket_manager_translate_events(SocketManager *sm, 312 | SocketEvent *events, EventRegister reg); 313 | 314 | typedef enum { 315 | CONNECT_TARGET_NAME, 316 | CONNECT_TARGET_IPV4, 317 | CONNECT_TARGET_IPV6, 318 | } ConnectTargetType; 319 | 320 | typedef struct { 321 | ConnectTargetType type; 322 | Port port; 323 | union { 324 | CHTTP_IPv4 ipv4; 325 | CHTTP_IPv6 ipv6; 326 | CHTTP_String name; 327 | }; 328 | } ConnectTarget; 329 | 330 | // Connect to one of the given targets. The socket 331 | // manager will try to connecting to addresses until 332 | // one succedes. If secure=true, the socket uses TLS. 333 | // Returns 0 on success, -1 on error. 334 | int socket_connect(SocketManager *sm, int num_targets, 335 | ConnectTarget *targets, bool secure, bool dont_verify_cert, 336 | void *user); 337 | 338 | int socket_recv(SocketManager *sm, SocketHandle handle, 339 | char *dst, int max); 340 | 341 | int socket_send(SocketManager *sm, SocketHandle handle, 342 | char *src, int len); 343 | 344 | void socket_close(SocketManager *sm, SocketHandle handle); 345 | 346 | // Returns -1 on error, 0 if the socket was accepted 347 | // from the plaintext listener, or 1 if it was accepted 348 | // by the secure listener. 349 | bool socket_is_secure(SocketManager *sm, SocketHandle handle); 350 | 351 | // Set the user pointer of a socket 352 | void socket_set_user(SocketManager *sm, SocketHandle handle, void *user); 353 | 354 | // Returns true iff the socket is ready for reading or 355 | // writing. 356 | bool socket_ready(SocketManager *sm, SocketHandle handle); 357 | 358 | // When a socket is marked as silent it will not generate events 359 | void socket_silent(SocketManager *sm, SocketHandle handle, bool value); 360 | -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | 2 | static void chttp_server_conn_init(CHTTP_ServerConn *conn, 3 | SocketHandle handle, uint32_t input_buffer_limit, 4 | uint32_t output_buffer_limit) 5 | { 6 | conn->state = CHTTP_SERVER_CONN_BUFFERING; 7 | conn->handle = handle; 8 | conn->closing = false; 9 | byte_queue_init(&conn->input, input_buffer_limit); 10 | byte_queue_init(&conn->output, output_buffer_limit); 11 | } 12 | 13 | static void chttp_server_conn_free(CHTTP_ServerConn *conn) 14 | { 15 | byte_queue_free(&conn->output); 16 | byte_queue_free(&conn->input); 17 | conn->state = CHTTP_SERVER_CONN_FREE; 18 | } 19 | 20 | int chttp_server_init(CHTTP_Server *server) 21 | { 22 | server->input_buffer_limit = 1<<20; 23 | server->output_buffer_limit = 1<<24; 24 | 25 | server->trace_bytes = false; 26 | server->reuse_addr = false; 27 | server->backlog = 32; 28 | 29 | server->num_conns = 0; 30 | for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++) { 31 | server->conns[i].state = CHTTP_SERVER_CONN_FREE; 32 | server->conns[i].gen = 0; 33 | } 34 | 35 | server->num_ready = 0; 36 | server->ready_head = 0; 37 | 38 | return socket_manager_init(&server->sockets, 39 | server->socket_pool, CHTTP_SERVER_CAPACITY); 40 | } 41 | 42 | void chttp_server_free(CHTTP_Server *server) 43 | { 44 | socket_manager_free(&server->sockets); 45 | 46 | for (int i = 0, j = 0; j < server->num_conns; i++) { 47 | CHTTP_ServerConn *conn = &server->conns[i]; 48 | if (conn->state == CHTTP_SERVER_CONN_FREE) 49 | continue; 50 | j++; 51 | 52 | chttp_server_conn_free(conn); 53 | } 54 | } 55 | 56 | void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit) 57 | { 58 | server->input_buffer_limit = limit; 59 | } 60 | 61 | void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit) 62 | { 63 | server->output_buffer_limit = limit; 64 | } 65 | 66 | void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value) 67 | { 68 | server->trace_bytes = value; 69 | } 70 | 71 | void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse) 72 | { 73 | server->reuse_addr = reuse; 74 | } 75 | 76 | void chttp_server_set_backlog(CHTTP_Server *server, int backlog) 77 | { 78 | server->backlog = backlog; 79 | } 80 | 81 | int chttp_server_listen_tcp(CHTTP_Server *server, 82 | CHTTP_String addr, Port port) 83 | { 84 | return socket_manager_listen_tcp(&server->sockets, 85 | addr, port, server->backlog, server->reuse_addr); 86 | } 87 | 88 | int chttp_server_listen_tls(CHTTP_Server *server, 89 | CHTTP_String addr, Port port, CHTTP_String cert_file_name, 90 | CHTTP_String key_file_name) 91 | { 92 | return socket_manager_listen_tls(&server->sockets, 93 | addr, port, server->backlog, server->reuse_addr, 94 | cert_file_name, key_file_name); 95 | } 96 | 97 | int chttp_server_add_certificate(CHTTP_Server *server, 98 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file) 99 | { 100 | return socket_manager_add_certificate(&server->sockets, 101 | domain, cert_file, key_file); 102 | } 103 | 104 | int chttp_server_wakeup(CHTTP_Server *server) 105 | { 106 | return socket_manager_wakeup(&server->sockets); 107 | } 108 | 109 | void chttp_server_register_events(CHTTP_Server *server, 110 | EventRegister *reg) 111 | { 112 | socket_manager_register_events(&server->sockets, reg); 113 | } 114 | 115 | // Look at the head of the input buffer to see if 116 | // a request was buffered. If it was, change the 117 | // connection's status to WAIT_STATUS and push it 118 | // to the ready queue. If the request is invalid, 119 | // close the socket. 120 | static void 121 | check_request_buffer(CHTTP_Server *server, CHTTP_ServerConn *conn) 122 | { 123 | assert(conn->state == CHTTP_SERVER_CONN_BUFFERING); 124 | 125 | ByteView src = byte_queue_read_buf(&conn->input); 126 | int ret = chttp_parse_request(src.ptr, src.len, &conn->request); 127 | if (ret < 0) { 128 | 129 | // Invalid request 130 | byte_queue_read_ack(&conn->input, 0); 131 | socket_close(&server->sockets, conn->handle); 132 | 133 | } else if (ret == 0) { 134 | 135 | // Still waiting 136 | byte_queue_read_ack(&conn->input, 0); 137 | 138 | // If the queue reached its limit and we still didn't receive 139 | // a complete request, abort the exchange. 140 | if (byte_queue_full(&conn->input)) 141 | socket_close(&server->sockets, conn->handle); 142 | 143 | } else { 144 | 145 | // Ready 146 | assert(ret > 0); 147 | 148 | // Stop receiving I/O events while we are building the response 149 | socket_silent(&server->sockets, conn->handle, true); 150 | 151 | conn->state = CHTTP_SERVER_CONN_WAIT_STATUS; 152 | conn->request_len = ret; 153 | conn->response_offset = byte_queue_offset(&conn->output); 154 | 155 | // Push to the ready queue 156 | assert(server->num_ready < CHTTP_SERVER_CAPACITY); 157 | int tail = (server->ready_head + server->num_ready) % CHTTP_SERVER_CAPACITY; 158 | server->ready[tail] = conn - server->conns; 159 | server->num_ready++; 160 | } 161 | } 162 | 163 | static void 164 | chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn) 165 | { 166 | if (conn->state == CHTTP_SERVER_CONN_FLUSHING) { 167 | 168 | ByteView src = byte_queue_read_buf(&conn->output); 169 | 170 | int num = 0; 171 | if (src.len) 172 | num = socket_send(&server->sockets, conn->handle, src.ptr, src.len); 173 | 174 | if (server->trace_bytes) 175 | print_bytes(CHTTP_STR("<< "), (CHTTP_String) { src.ptr, num }); 176 | 177 | byte_queue_read_ack(&conn->output, num); 178 | 179 | if (byte_queue_error(&conn->output)) { 180 | socket_close(&server->sockets, conn->handle); 181 | return; 182 | } 183 | 184 | if (byte_queue_empty(&conn->output)) { 185 | // We finished sending the response. Now we can 186 | // either close the connection or process a new 187 | // buffered request. 188 | if (conn->closing) { 189 | socket_close(&server->sockets, conn->handle); 190 | return; 191 | } 192 | conn->state = CHTTP_SERVER_CONN_BUFFERING; 193 | } 194 | } 195 | 196 | if (conn->state == CHTTP_SERVER_CONN_BUFFERING) { 197 | 198 | int min_recv = 1<<9; 199 | byte_queue_write_setmincap(&conn->input, min_recv); 200 | 201 | // Note that it's extra important that we don't 202 | // buffer while the user is building the response. 203 | // If we did that, a resize would invalidate all 204 | // pointers on the parsed request structure. 205 | ByteView dst = byte_queue_write_buf(&conn->input); 206 | 207 | int num = 0; 208 | if (dst.len) 209 | num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); 210 | 211 | if (server->trace_bytes) 212 | print_bytes(CHTTP_STR(">> "), (CHTTP_String) { dst.ptr, num }); 213 | 214 | byte_queue_write_ack(&conn->input, num); 215 | 216 | if (byte_queue_error(&conn->input)) { 217 | socket_close(&server->sockets, conn->handle); 218 | } else { 219 | check_request_buffer(server, conn); 220 | } 221 | } 222 | } 223 | 224 | void chttp_server_process_events(CHTTP_Server *server, 225 | EventRegister reg) 226 | { 227 | SocketEvent events[CHTTP_SERVER_CAPACITY]; 228 | int num_events = socket_manager_translate_events(&server->sockets, events, reg); 229 | 230 | for (int i = 0; i < num_events; i++) { 231 | 232 | CHTTP_ServerConn *conn = events[i].user; 233 | 234 | if (events[i].type == SOCKET_EVENT_DISCONNECT) { 235 | 236 | if (conn) { 237 | chttp_server_conn_free(conn); // TODO: what if this was in the ready queue? 238 | server->num_conns--; 239 | } 240 | 241 | } else if (events[i].type == SOCKET_EVENT_CREATION_TIMEOUT) { 242 | 243 | // TODO: This is too abrupt 244 | socket_close(&server->sockets, events[i].handle); 245 | 246 | } else if (events[i].type == SOCKET_EVENT_RECV_TIMEOUT) { 247 | 248 | // TODO: This is too abrupt 249 | socket_close(&server->sockets, events[i].handle); 250 | 251 | } else if (events[i].type == SOCKET_EVENT_READY) { 252 | 253 | if (events[i].user == NULL) { 254 | 255 | if (server->num_conns == CHTTP_SERVER_CAPACITY) { 256 | socket_close(&server->sockets, events[i].handle); 257 | continue; 258 | } 259 | 260 | int j = 0; 261 | while (server->conns[j].state != CHTTP_SERVER_CONN_FREE) { 262 | j++; 263 | assert(j < CHTTP_SERVER_CAPACITY); 264 | } 265 | 266 | conn = &server->conns[j]; 267 | chttp_server_conn_init(conn, 268 | events[i].handle, 269 | server->input_buffer_limit, 270 | server->output_buffer_limit); 271 | server->num_conns++; 272 | 273 | socket_set_user(&server->sockets, events[i].handle, conn); 274 | } 275 | 276 | while (socket_ready(&server->sockets, events[i].handle) 277 | && conn->state != CHTTP_SERVER_CONN_WAIT_STATUS) 278 | chttp_server_conn_process_events(server, conn); 279 | } 280 | } 281 | } 282 | 283 | bool chttp_server_next_request(CHTTP_Server *server, 284 | CHTTP_Request **request, CHTTP_ResponseBuilder *builder) 285 | { 286 | if (server->num_ready == 0) 287 | return false; 288 | 289 | CHTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]]; 290 | server->ready_head = (server->ready_head + 1) % CHTTP_SERVER_CAPACITY; 291 | server->num_ready--; 292 | 293 | assert(conn->state == CHTTP_SERVER_CONN_WAIT_STATUS); 294 | *request = &conn->request; 295 | *builder = (CHTTP_ResponseBuilder) { server, conn - server->conns, conn->gen }; 296 | return true; 297 | } 298 | 299 | void chttp_server_wait_request(CHTTP_Server *server, 300 | CHTTP_Request **request, CHTTP_ResponseBuilder *builder) 301 | { 302 | for (;;) { 303 | void *ptrs[CHTTP_SERVER_POLL_CAPACITY]; 304 | struct pollfd polled[CHTTP_SERVER_POLL_CAPACITY]; 305 | 306 | EventRegister reg = { ptrs, polled, 0, -1 }; 307 | chttp_server_register_events(server, ®); 308 | 309 | POLL(reg.polled, reg.num_polled, reg.timeout); 310 | 311 | chttp_server_process_events(server, reg); 312 | 313 | if (chttp_server_next_request(server, request, builder)) 314 | break; 315 | } 316 | } 317 | 318 | // Get a connection pointer from a response builder. 319 | // If the builder is invalid, returns NULL. 320 | // Note that only connections in the responding states 321 | // can be returned, as any builder is invalidated by 322 | // incrementing the connection's generation counter 323 | // when a response is completed. 324 | static CHTTP_ServerConn* 325 | builder_to_conn(CHTTP_ResponseBuilder builder) 326 | { 327 | CHTTP_Server *server = builder.server; 328 | if (server == NULL) 329 | return NULL; 330 | 331 | if (builder.index > CHTTP_SERVER_CAPACITY) 332 | return NULL; 333 | 334 | CHTTP_ServerConn *conn = &server->conns[builder.index]; 335 | if (builder.gen != conn->gen) 336 | return NULL; 337 | 338 | return conn; 339 | } 340 | 341 | static const char* 342 | get_status_text(int code) 343 | { 344 | switch(code) { 345 | 346 | case 100: return "Continue"; 347 | case 101: return "Switching Protocols"; 348 | case 102: return "Processing"; 349 | 350 | case 200: return "OK"; 351 | case 201: return "Created"; 352 | case 202: return "Accepted"; 353 | case 203: return "Non-Authoritative Information"; 354 | case 204: return "No Content"; 355 | case 205: return "Reset Content"; 356 | case 206: return "Partial Content"; 357 | case 207: return "Multi-Status"; 358 | case 208: return "Already Reported"; 359 | 360 | case 300: return "Multiple Choices"; 361 | case 301: return "Moved Permanently"; 362 | case 302: return "Found"; 363 | case 303: return "See Other"; 364 | case 304: return "Not Modified"; 365 | case 305: return "Use Proxy"; 366 | case 306: return "Switch Proxy"; 367 | case 307: return "Temporary Redirect"; 368 | case 308: return "Permanent Redirect"; 369 | 370 | case 400: return "Bad Request"; 371 | case 401: return "Unauthorized"; 372 | case 402: return "Payment Required"; 373 | case 403: return "Forbidden"; 374 | case 404: return "Not Found"; 375 | case 405: return "Method Not Allowed"; 376 | case 406: return "Not Acceptable"; 377 | case 407: return "Proxy Authentication Required"; 378 | case 408: return "Request Timeout"; 379 | case 409: return "Conflict"; 380 | case 410: return "Gone"; 381 | case 411: return "Length Required"; 382 | case 412: return "Precondition Failed"; 383 | case 413: return "Request Entity Too Large"; 384 | case 414: return "Request-URI Too Long"; 385 | case 415: return "Unsupported Media Type"; 386 | case 416: return "Requested Range Not Satisfiable"; 387 | case 417: return "Expectation Failed"; 388 | case 418: return "I'm a teapot"; 389 | case 420: return "Enhance your calm"; 390 | case 422: return "Unprocessable Entity"; 391 | case 426: return "Upgrade Required"; 392 | case 429: return "Too many requests"; 393 | case 431: return "Request Header Fields Too Large"; 394 | case 449: return "Retry With"; 395 | case 451: return "Unavailable For Legal Reasons"; 396 | 397 | case 500: return "Internal Server Error"; 398 | case 501: return "Not Implemented"; 399 | case 502: return "Bad Gateway"; 400 | case 503: return "Service Unavailable"; 401 | case 504: return "Gateway Timeout"; 402 | case 505: return "HTTP Version Not Supported"; 403 | case 509: return "Bandwidth Limit Exceeded"; 404 | } 405 | return "???"; 406 | } 407 | 408 | static void 409 | write_status(CHTTP_ServerConn *conn, int status) 410 | { 411 | byte_queue_write_fmt(&conn->output, 412 | "HTTP/1.1 %d %s\r\n", 413 | status, get_status_text(status)); 414 | } 415 | 416 | void chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status) 417 | { 418 | CHTTP_ServerConn *conn = builder_to_conn(builder); 419 | if (conn == NULL) 420 | return; 421 | 422 | if (conn->state != CHTTP_SERVER_CONN_WAIT_STATUS) { 423 | // Reset all response content and start from scrach. 424 | byte_queue_remove_from_offset(&conn->output, conn->response_offset); 425 | conn->state = CHTTP_SERVER_CONN_WAIT_STATUS; 426 | } 427 | 428 | write_status(conn, status); 429 | 430 | conn->state = CHTTP_SERVER_CONN_WAIT_HEADER; 431 | } 432 | 433 | static bool is_header_valid(CHTTP_String str) 434 | { 435 | bool has_colon = false; 436 | for (int i = 0; i < str.len; i++) { 437 | char c = str.ptr[i]; 438 | if (c == ':') 439 | has_colon = true; 440 | // Reject control characters (especially \r and \n) 441 | if (c < 0x20 && c != '\t') 442 | return false; 443 | } 444 | return has_colon; 445 | } 446 | 447 | void chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str) 448 | { 449 | CHTTP_ServerConn *conn = builder_to_conn(builder); 450 | if (conn == NULL) 451 | return; 452 | 453 | if (conn->state != CHTTP_SERVER_CONN_WAIT_HEADER) 454 | return; 455 | 456 | // Header must contain a colon and no control characters 457 | // to prevent HTTP response splitting attacks 458 | if (!is_header_valid(str)) return; // Silently drop it 459 | 460 | byte_queue_write(&conn->output, str.ptr, str.len); 461 | byte_queue_write(&conn->output, "\r\n", 2); 462 | } 463 | 464 | static void append_special_headers(CHTTP_ServerConn *conn) 465 | { 466 | CHTTP_String s; 467 | 468 | if (conn->closing) { 469 | s = CHTTP_STR("Connection: Close\r\n"); 470 | byte_queue_write(&conn->output, s.ptr, s.len); 471 | } else { 472 | s = CHTTP_STR("Connection: Keep-Alive\r\n"); 473 | byte_queue_write(&conn->output, s.ptr, s.len); 474 | } 475 | 476 | s = CHTTP_STR("Content-Length: "); 477 | byte_queue_write(&conn->output, s.ptr, s.len); 478 | 479 | conn->content_length_value_offset = byte_queue_offset(&conn->output); 480 | 481 | #define TEN_SPACES " " 482 | _Static_assert(sizeof(TEN_SPACES) == 10+1, ""); 483 | 484 | s = CHTTP_STR(TEN_SPACES "\r\n"); 485 | byte_queue_write(&conn->output, s.ptr, s.len); 486 | 487 | byte_queue_write(&conn->output, "\r\n", 2); 488 | conn->content_length_offset = byte_queue_offset(&conn->output); 489 | } 490 | 491 | static void patch_special_headers(CHTTP_ServerConn *conn) 492 | { 493 | int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset); 494 | 495 | char tmp[11]; 496 | int len = snprintf(tmp, sizeof(tmp), "%d", content_length); 497 | assert(len > 0 && len < 11); 498 | 499 | byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len); 500 | } 501 | 502 | void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str) 503 | { 504 | CHTTP_ServerConn *conn = builder_to_conn(builder); 505 | if (conn == NULL) 506 | return; 507 | 508 | if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) { 509 | append_special_headers(conn); 510 | conn->state = CHTTP_SERVER_CONN_WAIT_BODY; 511 | } 512 | 513 | if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY) 514 | return; 515 | 516 | byte_queue_write(&conn->output, str.ptr, str.len); 517 | } 518 | 519 | void chttp_response_builder_body_cap(CHTTP_ResponseBuilder builder, int cap) 520 | { 521 | CHTTP_ServerConn *conn = builder_to_conn(builder); 522 | if (conn == NULL) 523 | return; 524 | 525 | if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) { 526 | append_special_headers(conn); 527 | conn->state = CHTTP_SERVER_CONN_WAIT_BODY; 528 | } 529 | 530 | if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY) 531 | return; 532 | 533 | byte_queue_write_setmincap(&conn->output, cap); 534 | } 535 | 536 | char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap) 537 | { 538 | CHTTP_ServerConn *conn = builder_to_conn(builder); 539 | if (conn == NULL) 540 | return NULL; 541 | 542 | if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) { 543 | append_special_headers(conn); 544 | conn->state = CHTTP_SERVER_CONN_WAIT_BODY; 545 | } 546 | 547 | if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY) 548 | return NULL; 549 | 550 | ByteView tmp = byte_queue_write_buf(&conn->output); 551 | *cap = tmp.len; 552 | return tmp.ptr; 553 | } 554 | 555 | void chttp_response_builder_body_ack(CHTTP_ResponseBuilder builder, int num) 556 | { 557 | CHTTP_ServerConn *conn = builder_to_conn(builder); 558 | if (conn == NULL) 559 | return; 560 | 561 | if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY) 562 | return; 563 | 564 | byte_queue_write_ack(&conn->output, num); 565 | } 566 | 567 | void chttp_response_builder_send(CHTTP_ResponseBuilder builder) 568 | { 569 | CHTTP_ServerConn *conn = builder_to_conn(builder); 570 | if (conn == NULL) 571 | return; 572 | 573 | if (conn->state == CHTTP_SERVER_CONN_WAIT_STATUS) { 574 | write_status(conn, 500); 575 | conn->state = CHTTP_SERVER_CONN_WAIT_HEADER; 576 | } 577 | 578 | if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) { 579 | append_special_headers(conn); 580 | conn->state = CHTTP_SERVER_CONN_WAIT_BODY; 581 | } 582 | 583 | assert(conn->state == CHTTP_SERVER_CONN_WAIT_BODY); 584 | patch_special_headers(conn); 585 | 586 | // Remove the buffered request 587 | byte_queue_read_ack(&conn->input, conn->request_len); 588 | 589 | conn->state = CHTTP_SERVER_CONN_FLUSHING; 590 | conn->gen++; 591 | 592 | // Enable back I/O events 593 | socket_silent(&builder.server->sockets, conn->handle, false); 594 | 595 | chttp_server_conn_process_events(builder.server, conn); 596 | } 597 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | 2 | static void chttp_client_conn_free(CHTTP_ClientConn *conn) 3 | { 4 | byte_queue_free(&conn->output); 5 | byte_queue_free(&conn->input); 6 | } 7 | 8 | int chttp_client_init(CHTTP_Client *client) 9 | { 10 | client->input_buffer_limit = 1<<20; 11 | client->output_buffer_limit = 1<<24; 12 | 13 | client->cookie_jar.count = 0; 14 | 15 | client->num_conns = 0; 16 | for (int i = 0; i < CHTTP_CLIENT_CAPACITY; i++) { 17 | client->conns[i].state = CHTTP_CLIENT_CONN_FREE; 18 | client->conns[i].gen = 0; 19 | } 20 | 21 | client->num_ready = 0; 22 | client->ready_head = 0; 23 | 24 | return socket_manager_init(&client->sockets, 25 | client->socket_pool, CHTTP_CLIENT_CAPACITY); 26 | } 27 | 28 | void chttp_client_free(CHTTP_Client *client) 29 | { 30 | socket_manager_free(&client->sockets); 31 | 32 | for (int i = 0; i < client->cookie_jar.count; i++) 33 | free(client->cookie_jar.items[i].name.ptr); 34 | 35 | for (int i = 0, j = 0; j < client->num_conns; i++) { 36 | CHTTP_ClientConn *conn = &client->conns[i]; 37 | if (conn->state == CHTTP_CLIENT_CONN_FREE) 38 | continue; 39 | j++; 40 | 41 | chttp_client_conn_free(conn); 42 | } 43 | } 44 | 45 | void chttp_client_set_input_limit(CHTTP_Client *client, uint32_t limit) 46 | { 47 | client->input_buffer_limit = limit; 48 | } 49 | 50 | void chttp_client_set_output_limit(CHTTP_Client *client, uint32_t limit) 51 | { 52 | client->output_buffer_limit = limit; 53 | } 54 | 55 | int chttp_client_wakeup(CHTTP_Client *client) 56 | { 57 | return socket_manager_wakeup(&client->sockets); 58 | } 59 | 60 | // Get a connection pointer from a request builder. 61 | // If the builder is invalid, returns NULL. 62 | static CHTTP_ClientConn* 63 | request_builder_to_conn(CHTTP_RequestBuilder builder) 64 | { 65 | CHTTP_Client *client = builder.client; 66 | if (client == NULL) 67 | return NULL; 68 | 69 | if (builder.index >= CHTTP_CLIENT_CAPACITY) 70 | return NULL; 71 | 72 | CHTTP_ClientConn *conn = &client->conns[builder.index]; 73 | if (builder.gen != conn->gen) 74 | return NULL; 75 | 76 | return conn; 77 | } 78 | 79 | CHTTP_RequestBuilder chttp_client_get_builder(CHTTP_Client *client) 80 | { 81 | // Find a free connection slot 82 | if (client->num_conns == CHTTP_CLIENT_CAPACITY) 83 | return (CHTTP_RequestBuilder) { NULL, -1, -1 }; 84 | 85 | int i = 0; 86 | while (client->conns[i].state != CHTTP_CLIENT_CONN_FREE) { 87 | i++; 88 | assert(i < CHTTP_CLIENT_CAPACITY); 89 | } 90 | client->num_conns++; 91 | 92 | client->conns[i].state = CHTTP_CLIENT_CONN_WAIT_METHOD; 93 | client->conns[i].handle = SOCKET_HANDLE_INVALID; 94 | client->conns[i].client = client; 95 | client->conns[i].user = NULL; 96 | client->conns[i].trace_bytes = false; 97 | byte_queue_init(&client->conns[i].input, client->input_buffer_limit); 98 | byte_queue_init(&client->conns[i].output, client->output_buffer_limit); 99 | 100 | return (CHTTP_RequestBuilder) { client, i, client->conns[i].gen }; 101 | } 102 | 103 | // TODO: test this function 104 | static bool is_subdomain(CHTTP_String domain, CHTTP_String subdomain) 105 | { 106 | if (chttp_streq(domain, subdomain)) 107 | return true; // Exact match 108 | 109 | if (domain.len > subdomain.len) 110 | return false; 111 | 112 | CHTTP_String subdomain_suffix = { 113 | subdomain.ptr + subdomain.len - domain.len, 114 | domain.len 115 | }; 116 | if (subdomain_suffix.ptr[-1] != '.' || !chttp_streq(domain, subdomain_suffix)) 117 | return false; 118 | 119 | return true; 120 | } 121 | 122 | // TODO: test this function 123 | static bool is_subpath(CHTTP_String path, CHTTP_String subpath) 124 | { 125 | if (path.len > subpath.len) 126 | return false; 127 | 128 | if (subpath.len != path.len && subpath.ptr[path.len] != '/') 129 | return false; 130 | 131 | subpath.len = path.len; 132 | return chttp_streq(path, subpath); 133 | } 134 | 135 | static bool should_send_cookie(CHTTP_CookieJarEntry entry, CHTTP_URL url) 136 | { 137 | // TODO: If the cookie is expired, ignore it regardless 138 | 139 | if (entry.exact_domain) { 140 | // Cookie domain and URL domain must match exactly 141 | if (!chttp_streq(entry.domain, url.authority.host.text)) 142 | return false; 143 | } else { 144 | // The URL's domain must match or be a subdomain of the cookie's domain 145 | if (!is_subdomain(entry.domain, url.authority.host.text)) 146 | return false; 147 | } 148 | 149 | if (entry.exact_path) { 150 | // Cookie path and URL path must match exactly 151 | if (!chttp_streq(entry.path, url.path)) 152 | return false; 153 | } else { 154 | if (!is_subpath(entry.path, url.path)) 155 | return false; 156 | } 157 | 158 | if (entry.secure) { 159 | if (!chttp_streq(url.scheme, CHTTP_STR("https"))) 160 | return false; // Cookie was marked as secure but the target URL is not HTTPS 161 | } 162 | 163 | return true; 164 | } 165 | 166 | static CHTTP_String get_method_string(CHTTP_Method method) 167 | { 168 | switch (method) { 169 | case CHTTP_METHOD_GET : return CHTTP_STR("GET"); 170 | case CHTTP_METHOD_HEAD : return CHTTP_STR("HEAD"); 171 | case CHTTP_METHOD_POST : return CHTTP_STR("POST"); 172 | case CHTTP_METHOD_PUT : return CHTTP_STR("PUT"); 173 | case CHTTP_METHOD_DELETE : return CHTTP_STR("DELETE"); 174 | case CHTTP_METHOD_CONNECT: return CHTTP_STR("CONNECT"); 175 | case CHTTP_METHOD_OPTIONS: return CHTTP_STR("OPTIONS"); 176 | case CHTTP_METHOD_TRACE : return CHTTP_STR("TRACE"); 177 | case CHTTP_METHOD_PATCH : return CHTTP_STR("PATCH"); 178 | } 179 | return CHTTP_STR("???"); 180 | } 181 | 182 | void chttp_request_builder_set_user(CHTTP_RequestBuilder builder, void *user) 183 | { 184 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 185 | if (conn == NULL) 186 | return; // Invalid builder 187 | 188 | conn->user = user; 189 | } 190 | 191 | void chttp_request_builder_trace(CHTTP_RequestBuilder builder, bool trace_bytes) 192 | { 193 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 194 | if (conn == NULL) 195 | return; // Invalid builder 196 | 197 | conn->trace_bytes = trace_bytes; 198 | } 199 | 200 | // TODO: comment 201 | void chttp_request_builder_insecure(CHTTP_RequestBuilder builder, 202 | bool insecure) 203 | { 204 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 205 | if (conn == NULL) 206 | return; // Invalid builder 207 | 208 | conn->dont_verify_cert = insecure; 209 | } 210 | 211 | void chttp_request_builder_method(CHTTP_RequestBuilder builder, 212 | CHTTP_Method method) 213 | { 214 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 215 | if (conn == NULL) 216 | return; // Invalid builder 217 | 218 | if (conn->state != CHTTP_CLIENT_CONN_WAIT_METHOD) 219 | return; // Request line already written 220 | 221 | // Write method 222 | CHTTP_String method_str = get_method_string(method); 223 | byte_queue_write(&conn->output, method_str.ptr, method_str.len); 224 | byte_queue_write(&conn->output, " ", 1); 225 | 226 | conn->state = CHTTP_CLIENT_CONN_WAIT_URL; 227 | } 228 | 229 | void chttp_request_builder_target(CHTTP_RequestBuilder builder, 230 | CHTTP_String url) 231 | { 232 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 233 | if (conn == NULL) 234 | return; // Invalid builder 235 | 236 | if (conn->state != CHTTP_CLIENT_CONN_WAIT_URL) 237 | return; // Request line already written 238 | 239 | if (url.len == 0) { 240 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 241 | conn->result = CHTTP_ERROR_BADURL; 242 | return; 243 | } 244 | 245 | // Allocate a copy of the URL string so the parsed 246 | // URL pointers remain valid 247 | char *url_copy = malloc(url.len); 248 | if (url_copy == NULL) { 249 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 250 | conn->result = CHTTP_ERROR_OOM; 251 | return; 252 | } 253 | memcpy(url_copy, url.ptr, url.len); 254 | 255 | conn->url_buffer.ptr = url_copy; 256 | conn->url_buffer.len = url.len; 257 | 258 | // Parse the copied URL (all url.* pointers will reference url_buffer) 259 | if (chttp_parse_url(conn->url_buffer.ptr, conn->url_buffer.len, &conn->url) < 0) { 260 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 261 | conn->result = CHTTP_ERROR_BADURL; 262 | return; 263 | } 264 | 265 | if (!chttp_streq(conn->url.scheme, CHTTP_STR("http")) && 266 | !chttp_streq(conn->url.scheme, CHTTP_STR("https"))) { 267 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 268 | conn->result = CHTTP_ERROR_BADURL; 269 | return; 270 | } 271 | 272 | // Write path 273 | if (conn->url.path.len == 0) 274 | byte_queue_write(&conn->output, "/", 1); 275 | else 276 | byte_queue_write(&conn->output, 277 | conn->url.path.ptr, 278 | conn->url.path.len); 279 | 280 | // Write query string 281 | CHTTP_String query = conn->url.query; 282 | if (query.len > 0) { 283 | byte_queue_write(&conn->output, "?", 1); 284 | byte_queue_write(&conn->output, query.ptr, query.len); 285 | } 286 | 287 | CHTTP_String version = CHTTP_STR(" HTTP/1.1"); 288 | byte_queue_write(&conn->output, version.ptr, version.len); 289 | 290 | byte_queue_write(&conn->output, "\r\n", 2); 291 | 292 | // Add Host header automatically 293 | byte_queue_write_fmt(&conn->output, "Host: %.*s", 294 | conn->url.authority.host.text.len, 295 | conn->url.authority.host.text.ptr); 296 | if (conn->url.authority.port > 0) 297 | byte_queue_write_fmt(&conn->output, ":%d", conn->url.authority.port); 298 | 299 | byte_queue_write(&conn->output, "\r\n", 2); 300 | 301 | // Find all entries from the cookie jar that should 302 | // be sent to this server and append headers for them 303 | CHTTP_Client *client = builder.client; 304 | CHTTP_CookieJar *cookie_jar = &client->cookie_jar; 305 | for (int i = 0; i < cookie_jar->count; i++) { 306 | CHTTP_CookieJarEntry entry = cookie_jar->items[i]; 307 | if (should_send_cookie(entry, conn->url)) { 308 | // TODO: Adding one header per cookie may cause the number of 309 | // headers to increase significantly. Should probably group 310 | // 3-4 cookies in the same headers. 311 | byte_queue_write(&conn->output, "Cookie: ", 8); 312 | byte_queue_write(&conn->output, entry.name.ptr, entry.name.len); 313 | byte_queue_write(&conn->output, "=", 1); 314 | byte_queue_write(&conn->output, entry.value.ptr, entry.value.len); 315 | byte_queue_write(&conn->output, "\r\n", 2); 316 | } 317 | } 318 | 319 | CHTTP_String s; 320 | 321 | s = CHTTP_STR("Connection: Close\r\n"); 322 | byte_queue_write(&conn->output, s.ptr, s.len); 323 | 324 | s = CHTTP_STR("Content-Length: "); 325 | byte_queue_write(&conn->output, s.ptr, s.len); 326 | 327 | conn->content_length_value_offset = byte_queue_offset(&conn->output); 328 | 329 | #define TEN_SPACES " " 330 | _Static_assert(sizeof(TEN_SPACES) == 10+1, ""); 331 | 332 | s = CHTTP_STR(TEN_SPACES "\r\n"); 333 | byte_queue_write(&conn->output, s.ptr, s.len); 334 | 335 | conn->state = CHTTP_CLIENT_CONN_WAIT_HEADER; 336 | } 337 | 338 | void chttp_request_builder_header(CHTTP_RequestBuilder builder, 339 | CHTTP_String str) 340 | { 341 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 342 | if (conn == NULL) 343 | return; 344 | 345 | if (conn->state != CHTTP_CLIENT_CONN_WAIT_HEADER) 346 | return; 347 | 348 | // Validate header: must contain a colon and no control characters 349 | bool has_colon = false; 350 | for (int i = 0; i < str.len; i++) { 351 | char c = str.ptr[i]; 352 | if (c == ':') 353 | has_colon = true; 354 | // Reject control characters (especially \r and \n) 355 | if (c < 0x20 && c != '\t') 356 | return; 357 | } 358 | if (!has_colon) 359 | return; 360 | 361 | byte_queue_write(&conn->output, str.ptr, str.len); 362 | byte_queue_write(&conn->output, "\r\n", 2); 363 | } 364 | 365 | void chttp_request_builder_body(CHTTP_RequestBuilder builder, 366 | CHTTP_String str) 367 | { 368 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 369 | if (conn == NULL) 370 | return; 371 | 372 | // Transition from WAIT_HEADER to WAIT_BODY if needed 373 | if (conn->state == CHTTP_CLIENT_CONN_WAIT_HEADER) { 374 | byte_queue_write(&conn->output, "\r\n", 2); 375 | conn->content_length_offset = byte_queue_offset(&conn->output); 376 | conn->state = CHTTP_CLIENT_CONN_WAIT_BODY; 377 | } 378 | 379 | if (conn->state != CHTTP_CLIENT_CONN_WAIT_BODY) 380 | return; 381 | 382 | byte_queue_write(&conn->output, str.ptr, str.len); 383 | } 384 | 385 | static ConnectTarget url_to_connect_target(CHTTP_URL url) 386 | { 387 | CHTTP_Authority authority = url.authority; 388 | 389 | ConnectTarget target; 390 | if (authority.port < 1) { 391 | if (chttp_streq(url.scheme, CHTTP_STR("https"))) 392 | target.port = 443; 393 | else 394 | target.port = 80; 395 | } else { 396 | target.port = authority.port; 397 | } 398 | 399 | if (authority.host.mode == CHTTP_HOST_MODE_NAME) { 400 | target.type = CONNECT_TARGET_NAME; 401 | target.name = authority.host.name; 402 | } else if (authority.host.mode == CHTTP_HOST_MODE_IPV4) { 403 | target.type = CONNECT_TARGET_IPV4; 404 | target.ipv4 = authority.host.ipv4; 405 | } else if (authority.host.mode == CHTTP_HOST_MODE_IPV6) { 406 | target.type = CONNECT_TARGET_IPV6; 407 | target.ipv6 = authority.host.ipv6; 408 | } else { 409 | CHTTP_UNREACHABLE; 410 | } 411 | 412 | return target; 413 | } 414 | 415 | int chttp_request_builder_send(CHTTP_RequestBuilder builder) 416 | { 417 | CHTTP_Client *client = builder.client; 418 | if (client == NULL) 419 | return CHTTP_ERROR_REQLIMIT; 420 | 421 | CHTTP_ClientConn *conn = request_builder_to_conn(builder); 422 | if (conn == NULL) 423 | return CHTTP_ERROR_BADHANDLE; 424 | 425 | if (conn->state == CHTTP_CLIENT_CONN_COMPLETE) 426 | goto error; // Early completion due to an error 427 | 428 | if (conn->state == CHTTP_CLIENT_CONN_WAIT_HEADER) { 429 | byte_queue_write(&conn->output, "\r\n", 2); 430 | conn->content_length_offset = byte_queue_offset(&conn->output); 431 | conn->state = CHTTP_CLIENT_CONN_WAIT_BODY; 432 | } 433 | 434 | if (conn->state != CHTTP_CLIENT_CONN_WAIT_BODY) 435 | goto error; 436 | 437 | if (byte_queue_error(&conn->output)) 438 | goto error; 439 | 440 | int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset); 441 | 442 | char tmp[11]; 443 | int len = snprintf(tmp, sizeof(tmp), "%d", content_length); 444 | assert(len > 0 && len < 11); 445 | 446 | byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len); 447 | 448 | ConnectTarget target = url_to_connect_target(conn->url); 449 | bool secure = chttp_streq(conn->url.scheme, CHTTP_STR("https")); 450 | if (socket_connect(&client->sockets, 1, &target, secure, conn->dont_verify_cert, conn) < 0) 451 | goto error; 452 | 453 | conn->state = CHTTP_CLIENT_CONN_FLUSHING; 454 | conn->gen++; 455 | return CHTTP_OK; 456 | 457 | error: 458 | conn->state = CHTTP_CLIENT_CONN_FREE; 459 | free(conn->url_buffer.ptr); 460 | byte_queue_free(&conn->input); 461 | byte_queue_free(&conn->output); 462 | client->num_conns--; 463 | return conn->result; 464 | } 465 | 466 | static void save_one_cookie(CHTTP_CookieJar *cookie_jar, 467 | CHTTP_Header set_cookie, CHTTP_String domain, CHTTP_String path) 468 | { 469 | if (cookie_jar->count == CHTTP_COOKIE_JAR_CAPACITY) 470 | return; // Cookie jar capacity reached 471 | 472 | CHTTP_SetCookie parsed; 473 | if (chttp_parse_set_cookie(set_cookie.value, &parsed) < 0) 474 | return; // Ignore invalid Set-Cookie headers 475 | 476 | CHTTP_CookieJarEntry entry; 477 | 478 | entry.name = parsed.name; 479 | entry.value = parsed.value; 480 | 481 | if (parsed.have_domain) { 482 | // TODO: Check that the server can set a cookie for this domain 483 | entry.exact_domain = false; 484 | entry.domain = parsed.domain; 485 | } else { 486 | entry.exact_domain = true; 487 | entry.domain = domain; 488 | } 489 | 490 | if (parsed.have_path) { 491 | entry.exact_path = false; 492 | entry.path = parsed.path; 493 | } else { 494 | // TODO: Set the path to the current endpoint minus one level 495 | entry.exact_path = true; 496 | entry.path = path; 497 | } 498 | 499 | entry.secure = parsed.secure; 500 | 501 | // Now copy all fields 502 | char *p = malloc(entry.name.len + entry.value.len + entry.domain.len + entry.path.len); 503 | if (p == NULL) 504 | return; 505 | 506 | memcpy(p, entry.name.ptr, entry.name.len); 507 | entry.name.ptr = p; 508 | p += entry.name.len; 509 | 510 | memcpy(p, entry.value.ptr, entry.value.len); 511 | entry.value.ptr = p; 512 | p += entry.value.len; 513 | 514 | memcpy(p, entry.domain.ptr, entry.domain.len); 515 | entry.domain.ptr = p; 516 | p += entry.domain.len; 517 | 518 | memcpy(p, entry.path.ptr, entry.path.len); 519 | entry.path.ptr = p; 520 | p += entry.path.len; 521 | 522 | cookie_jar->items[cookie_jar->count++] = entry; 523 | } 524 | 525 | static void save_cookies(CHTTP_CookieJar *cookie_jar, 526 | CHTTP_Header *headers, int num_headers, 527 | CHTTP_String domain, CHTTP_String path) 528 | { 529 | // TODO: remove expired cookies 530 | 531 | for (int i = 0; i < num_headers; i++) 532 | if (chttp_streqcase(headers[i].name, CHTTP_STR("Set-Cookie"))) // TODO: headers are case-insensitive, right? 533 | save_one_cookie(cookie_jar, headers[i], domain, path); 534 | } 535 | 536 | void chttp_client_register_events(CHTTP_Client *client, 537 | EventRegister *reg) 538 | { 539 | socket_manager_register_events(&client->sockets, reg); 540 | } 541 | 542 | void chttp_client_process_events(CHTTP_Client *client, 543 | EventRegister reg) 544 | { 545 | SocketEvent events[CHTTP_CLIENT_CAPACITY]; 546 | int num_events = socket_manager_translate_events(&client->sockets, events, reg); 547 | 548 | for (int i = 0; i < num_events; i++) { 549 | 550 | CHTTP_ClientConn *conn = events[i].user; 551 | if (conn == NULL) 552 | continue; // If a socket is not couple to a connection, 553 | // it means the response was already returned 554 | // to the user. 555 | 556 | if (events[i].type == SOCKET_EVENT_DISCONNECT) { 557 | 558 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 559 | conn->result = -1; 560 | 561 | } else if (events[i].type == SOCKET_EVENT_CREATION_TIMEOUT) { 562 | 563 | // TODO: This is too abrupt 564 | socket_close(&client->sockets, events[i].handle); 565 | 566 | } else if (events[i].type == SOCKET_EVENT_RECV_TIMEOUT) { 567 | 568 | // TODO: This is too abrupt 569 | socket_close(&client->sockets, events[i].handle); 570 | 571 | } else if (events[i].type == SOCKET_EVENT_READY) { 572 | 573 | // Store the handle if this is a new connection 574 | if (conn->handle == SOCKET_HANDLE_INVALID) 575 | conn->handle = events[i].handle; 576 | 577 | while (socket_ready(&client->sockets, conn->handle)) { 578 | 579 | if (conn->state == CHTTP_CLIENT_CONN_FLUSHING) { 580 | 581 | ByteView src = byte_queue_read_buf(&conn->output); 582 | 583 | int num = 0; 584 | if (src.len) 585 | num = socket_send(&client->sockets, conn->handle, src.ptr, src.len); 586 | 587 | if (conn->trace_bytes) 588 | print_bytes(CHTTP_STR("<< "), (CHTTP_String){src.ptr, num}); 589 | 590 | byte_queue_read_ack(&conn->output, num); 591 | 592 | if (byte_queue_error(&conn->output)) { 593 | socket_close(&client->sockets, conn->handle); 594 | continue; 595 | } 596 | 597 | // Request fully sent, now wait for response 598 | if (byte_queue_empty(&conn->output)) 599 | conn->state = CHTTP_CLIENT_CONN_BUFFERING; 600 | } 601 | 602 | if (conn->state == CHTTP_CLIENT_CONN_BUFFERING) { 603 | 604 | // Receive response data 605 | int min_recv = 1<<10; 606 | byte_queue_write_setmincap(&conn->input, min_recv); 607 | 608 | ByteView dst = byte_queue_write_buf(&conn->input); 609 | 610 | int num = 0; 611 | if (dst.len) 612 | num = socket_recv(&client->sockets, conn->handle, dst.ptr, dst.len); 613 | 614 | if (conn->trace_bytes) 615 | print_bytes(CHTTP_STR(">> "), (CHTTP_String){dst.ptr, num}); 616 | 617 | byte_queue_write_ack(&conn->input, num); 618 | 619 | if (byte_queue_error(&conn->input)) { 620 | socket_close(&client->sockets, conn->handle); 621 | continue; 622 | } 623 | 624 | ByteView src = byte_queue_read_buf(&conn->input); 625 | int ret = chttp_parse_response(src.ptr, src.len, &conn->response); 626 | 627 | if (ret == 0) { 628 | // Still waiting 629 | byte_queue_read_ack(&conn->input, 0); 630 | 631 | // If the queue reached its limit and we still didn't receive 632 | // a complete response, abort the exchange. 633 | if (byte_queue_full(&conn->input)) 634 | socket_close(&client->sockets, conn->handle); 635 | continue; 636 | } 637 | 638 | if (ret < 0) { 639 | // Invalid response 640 | byte_queue_read_ack(&conn->input, 0); 641 | socket_close(&client->sockets, conn->handle); 642 | continue; 643 | } 644 | 645 | // Ready 646 | assert(ret > 0); 647 | 648 | conn->state = CHTTP_CLIENT_CONN_COMPLETE; 649 | conn->result = 0; 650 | 651 | conn->response.context = client; 652 | 653 | // Store received cookies in the cookie jar 654 | save_cookies(&client->cookie_jar, 655 | conn->response.headers, 656 | conn->response.num_headers, 657 | conn->url.authority.host.text, 658 | conn->url.path); 659 | 660 | // TODO: Handle redirects here 661 | break; 662 | } 663 | } 664 | } 665 | 666 | if (conn->state == CHTTP_CLIENT_CONN_COMPLETE) { 667 | 668 | // Decouple from the socket 669 | socket_set_user(&client->sockets, events[i].handle, NULL); 670 | socket_close(&client->sockets, events[i].handle); 671 | 672 | // Push to the ready queue 673 | assert(client->num_ready < CHTTP_CLIENT_CAPACITY); 674 | int tail = (client->ready_head + client->num_ready) % CHTTP_CLIENT_CAPACITY; 675 | client->ready[tail] = conn - client->conns; 676 | client->num_ready++; 677 | } 678 | } 679 | } 680 | 681 | bool chttp_client_next_response(CHTTP_Client *client, 682 | int *result, void **user, CHTTP_Response **response) 683 | { 684 | if (client->num_ready == 0) 685 | return false; 686 | 687 | CHTTP_ClientConn *conn = &client->conns[client->ready[client->ready_head]]; 688 | client->ready_head = (client->ready_head + 1) % CHTTP_CLIENT_CAPACITY; 689 | client->num_ready--; 690 | 691 | assert(conn->state == CHTTP_CLIENT_CONN_COMPLETE); 692 | 693 | *result = conn->result; 694 | *user = conn->user; 695 | if (conn->result == CHTTP_OK) { 696 | *response = &conn->response; 697 | } else { 698 | *response = NULL; 699 | } 700 | 701 | return true; 702 | } 703 | 704 | void chttp_free_response(CHTTP_Response *response) 705 | { 706 | if (response == NULL || response->context == NULL) 707 | return; 708 | CHTTP_Client *client = response->context; 709 | response->context = NULL; 710 | 711 | // TODO: I'm positive there is a better way to do this. 712 | // It should just be a bouds check + subtraction. 713 | CHTTP_ClientConn *conn = NULL; 714 | for (int i = 0; i < CHTTP_CLIENT_CAPACITY; i++) 715 | if (&client->conns[i].response == response) { 716 | conn = &client->conns[i]; 717 | break; 718 | } 719 | if (conn == NULL) 720 | return; 721 | 722 | conn->state = CHTTP_CLIENT_CONN_FREE; 723 | free(conn->url_buffer.ptr); 724 | byte_queue_free(&conn->input); 725 | byte_queue_free(&conn->output); 726 | client->num_conns--; 727 | } 728 | 729 | #ifdef _WIN32 730 | #define POLL WSAPoll 731 | #else 732 | #define POLL poll 733 | #endif 734 | 735 | void chttp_client_wait_response(CHTTP_Client *client, 736 | int *result, void **user, CHTTP_Response **response) 737 | { 738 | for (;;) { 739 | 740 | void *ptrs[CHTTP_CLIENT_POLL_CAPACITY]; 741 | struct pollfd polled[CHTTP_CLIENT_POLL_CAPACITY]; 742 | 743 | EventRegister reg = { ptrs, polled, 0, -1 }; 744 | chttp_client_register_events(client, ®); 745 | 746 | POLL(reg.polled, reg.num_polled, reg.timeout); 747 | 748 | chttp_client_process_events(client, reg); 749 | 750 | if (chttp_client_next_response(client, result, user, response)) 751 | break; 752 | } 753 | } 754 | 755 | static _Thread_local CHTTP_Client *implicit_client; 756 | 757 | static int perform_request(CHTTP_Method method, 758 | CHTTP_String url, CHTTP_String *headers, 759 | int num_headers, CHTTP_String body, 760 | CHTTP_Response **response) 761 | { 762 | if (implicit_client == NULL) { 763 | 764 | implicit_client = malloc(sizeof(CHTTP_Client)); 765 | if (implicit_client == NULL) 766 | return CHTTP_ERROR_OOM; 767 | 768 | int ret = chttp_client_init(implicit_client); 769 | if (ret < 0) { 770 | free(implicit_client); 771 | implicit_client = NULL; 772 | return ret; 773 | } 774 | } 775 | CHTTP_Client *client = implicit_client; 776 | 777 | CHTTP_RequestBuilder builder = chttp_client_get_builder(client); 778 | chttp_request_builder_method(builder, method); 779 | chttp_request_builder_target(builder, url); 780 | for (int i = 0; i < num_headers; i++) 781 | chttp_request_builder_header(builder, headers[i]); 782 | chttp_request_builder_body(builder, body); 783 | int ret = chttp_request_builder_send(builder); 784 | if (ret < 0) return ret; 785 | 786 | int result; 787 | void *user; 788 | chttp_client_wait_response(client, &result, &user, response); 789 | return result; 790 | } 791 | 792 | int chttp_get(CHTTP_String url, CHTTP_String *headers, 793 | int num_headers, CHTTP_Response **response) 794 | { 795 | return perform_request(CHTTP_METHOD_GET, url, headers, num_headers, CHTTP_STR(""), response); 796 | } 797 | 798 | int chttp_post(CHTTP_String url, CHTTP_String *headers, 799 | int num_headers, CHTTP_String body, 800 | CHTTP_Response **response) 801 | { 802 | return perform_request(CHTTP_METHOD_POST, url, headers, num_headers, body, response); 803 | } 804 | 805 | int chttp_put(CHTTP_String url, CHTTP_String *headers, 806 | int num_headers, CHTTP_String body, 807 | CHTTP_Response **response) 808 | { 809 | return perform_request(CHTTP_METHOD_PUT, url, headers, num_headers, body, response); 810 | } 811 | 812 | int chttp_delete(CHTTP_String url, CHTTP_String *headers, 813 | int num_headers, CHTTP_Response **response) 814 | { 815 | return perform_request(CHTTP_METHOD_DELETE, url, headers, num_headers, CHTTP_STR(""), response); 816 | } 817 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | 2 | //#define TRACE_STATE_CHANGES 3 | 4 | #ifndef TRACE_STATE_CHANGES 5 | #define UPDATE_STATE(a, b) a = b 6 | #else 7 | static char *state_to_str(SocketState state) 8 | { 9 | switch (state) { 10 | case SOCKET_STATE_FREE : return "FREE"; 11 | case SOCKET_STATE_PENDING : return "PENDING"; 12 | case SOCKET_STATE_CONNECTING: return "CONNECTING"; 13 | case SOCKET_STATE_CONNECTED : return "CONNECTED"; 14 | case SOCKET_STATE_ACCEPTED : return "ACCEPTED"; 15 | case SOCKET_STATE_ESTABLISHED_WAIT : return "ESTABLISHED_WAIT"; 16 | case SOCKET_STATE_ESTABLISHED_READY: return "ESTABLISHED_READY"; 17 | case SOCKET_STATE_SHUTDOWN : return "SHUTDOWN"; 18 | case SOCKET_STATE_DIED : return "DIED"; 19 | } 20 | return "???"; 21 | } 22 | #define UPDATE_STATE(a, b) { \ 23 | printf("%s -> %s %s:%d\n", \ 24 | state_to_str(a), \ 25 | state_to_str(b), \ 26 | __FILE__, __LINE__); \ 27 | a = b; \ 28 | } 29 | #endif 30 | 31 | static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b, bool *global_cleanup) 32 | { 33 | #ifdef _WIN32 34 | SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 35 | 36 | *global_cleanup = false; 37 | if (sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) { 38 | 39 | WSADATA wsaData; 40 | WORD wVersionRequested = MAKEWORD(2, 2); 41 | if (WSAStartup(wVersionRequested, &wsaData)) 42 | return CHTTP_ERROR_UNSPECIFIED; 43 | 44 | sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 45 | if (sock == INVALID_SOCKET && *global_cleanup) 46 | WSACleanup(); 47 | } 48 | 49 | if (sock == INVALID_SOCKET) { 50 | if (*global_cleanup) 51 | WSACleanup(); 52 | return CHTTP_ERROR_UNSPECIFIED; 53 | } 54 | 55 | // Bind to loopback address with port 0 (dynamic port assignment) 56 | struct sockaddr_in addr; 57 | int addr_len = sizeof(addr); 58 | memset(&addr, 0, sizeof(addr)); 59 | addr.sin_family = AF_INET; 60 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1 61 | addr.sin_port = 0; // Let system choose port 62 | 63 | if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { 64 | closesocket(sock); 65 | if (*global_cleanup) 66 | WSACleanup(); 67 | return CHTTP_ERROR_UNSPECIFIED; 68 | } 69 | 70 | if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) { 71 | closesocket(sock); 72 | if (*global_cleanup) 73 | WSACleanup(); 74 | return CHTTP_ERROR_UNSPECIFIED; 75 | } 76 | 77 | if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { 78 | closesocket(sock); 79 | if (*global_cleanup) 80 | WSACleanup(); 81 | return CHTTP_ERROR_UNSPECIFIED; 82 | } 83 | 84 | // Optional: Set socket to non-blocking mode 85 | // This prevents send() from blocking if the receive buffer is full 86 | u_long mode = 1; 87 | if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) { 88 | closesocket(sock); 89 | if (*global_cleanup) 90 | WSACleanup(); 91 | return CHTTP_ERROR_UNSPECIFIED; 92 | } 93 | 94 | *a = sock; 95 | *b = sock; 96 | return CHTTP_OK; 97 | #else 98 | *global_cleanup = false; 99 | int fds[2]; 100 | if (pipe(fds) < 0) 101 | return CHTTP_ERROR_UNSPECIFIED; 102 | *a = fds[0]; 103 | *b = fds[1]; 104 | return CHTTP_OK; 105 | #endif 106 | } 107 | 108 | static int set_socket_blocking(NATIVE_SOCKET sock, bool value) 109 | { 110 | #ifdef _WIN32 111 | u_long mode = !value; 112 | if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) 113 | return CHTTP_ERROR_UNSPECIFIED; 114 | return CHTTP_OK; 115 | #endif 116 | 117 | #ifdef __linux__ 118 | int flags = fcntl(sock, F_GETFL, 0); 119 | if (flags < 0) 120 | return CHTTP_ERROR_UNSPECIFIED; 121 | if (value) flags &= ~O_NONBLOCK; 122 | else flags |= O_NONBLOCK; 123 | if (fcntl(sock, F_SETFL, flags) < 0) 124 | return CHTTP_ERROR_UNSPECIFIED; 125 | return CHTTP_OK; 126 | #endif 127 | } 128 | 129 | static NATIVE_SOCKET create_listen_socket(CHTTP_String addr, 130 | Port port, bool reuse_addr, int backlog) 131 | { 132 | NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); 133 | if (sock == NATIVE_SOCKET_INVALID) 134 | return NATIVE_SOCKET_INVALID; 135 | 136 | if (set_socket_blocking(sock, false) < 0) { 137 | CLOSE_NATIVE_SOCKET(sock); 138 | return NATIVE_SOCKET_INVALID; 139 | } 140 | 141 | if (reuse_addr) { 142 | int one = 1; 143 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); 144 | } 145 | 146 | struct in_addr addr_buf; 147 | if (addr.len == 0) 148 | addr_buf.s_addr = htonl(INADDR_ANY); 149 | else { 150 | 151 | char copy[100]; 152 | if (addr.len >= (int) sizeof(copy)) { 153 | CLOSE_NATIVE_SOCKET(sock); 154 | return NATIVE_SOCKET_INVALID; 155 | } 156 | memcpy(copy, addr.ptr, addr.len); 157 | copy[addr.len] = '\0'; 158 | 159 | if (inet_pton(AF_INET, copy, &addr_buf) < 0) { 160 | CLOSE_NATIVE_SOCKET(sock); 161 | return NATIVE_SOCKET_INVALID; 162 | } 163 | } 164 | 165 | struct sockaddr_in bind_buf; 166 | bind_buf.sin_family = AF_INET; 167 | bind_buf.sin_addr = addr_buf; 168 | bind_buf.sin_port = htons(port); 169 | if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { 170 | CLOSE_NATIVE_SOCKET(sock); 171 | return NATIVE_SOCKET_INVALID; 172 | } 173 | 174 | if (listen(sock, backlog) < 0) { 175 | CLOSE_NATIVE_SOCKET(sock); 176 | return NATIVE_SOCKET_INVALID; 177 | } 178 | 179 | return sock; 180 | } 181 | 182 | static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b) 183 | { 184 | #ifdef _WIN32 185 | closesocket(a); 186 | (void) b; 187 | #else 188 | close(a); 189 | close(b); 190 | #endif 191 | } 192 | 193 | int socket_manager_init(SocketManager *sm, Socket *socks, 194 | int num_socks) 195 | { 196 | sm->creation_timeout = 60000; 197 | sm->recv_timeout = 3000; 198 | 199 | sm->plain_sock = NATIVE_SOCKET_INVALID; 200 | sm->secure_sock = NATIVE_SOCKET_INVALID; 201 | 202 | int ret = create_socket_pair( 203 | &sm->wait_sock, 204 | &sm->signal_sock, 205 | &sm->global_cleanup); 206 | if (ret < 0) return ret; 207 | 208 | sm->at_least_one_secure_connect = false; 209 | 210 | sm->num_used = 0; 211 | sm->max_used = num_socks; 212 | sm->sockets = socks; 213 | 214 | for (int i = 0; i < num_socks; i++) { 215 | socks[i].state = SOCKET_STATE_FREE; 216 | socks[i].gen = 1; 217 | } 218 | return CHTTP_OK; 219 | } 220 | 221 | void socket_manager_free(SocketManager *sm) 222 | { 223 | close_socket_pair(sm->wait_sock, sm->signal_sock); 224 | 225 | if (sm->secure_sock != NATIVE_SOCKET_INVALID) 226 | server_secure_context_free(&sm->server_secure_context); 227 | 228 | if (sm->at_least_one_secure_connect) 229 | client_secure_context_free(&sm->client_secure_context); 230 | 231 | if (sm->plain_sock != NATIVE_SOCKET_INVALID) 232 | CLOSE_NATIVE_SOCKET(sm->plain_sock); 233 | 234 | if (sm->secure_sock != NATIVE_SOCKET_INVALID) 235 | CLOSE_NATIVE_SOCKET(sm->secure_sock); 236 | 237 | #ifdef _WIN32 238 | if (sm->global_cleanup) 239 | WSACleanup(); 240 | #endif 241 | } 242 | 243 | void socket_manager_set_creation_timeout(SocketManager *sm, int timeout) 244 | { 245 | sm->creation_timeout = (timeout < 0) ? INVALID_TIME : (Time) timeout; 246 | } 247 | 248 | void socket_manager_set_recv_timeout(SocketManager *sm, int timeout) 249 | { 250 | sm->recv_timeout = (timeout < 0) ? INVALID_TIME : (Time) timeout; 251 | } 252 | 253 | int socket_manager_listen_tcp(SocketManager *sm, 254 | CHTTP_String addr, Port port, int backlog, 255 | bool reuse_addr) 256 | { 257 | if (sm->plain_sock != NATIVE_SOCKET_INVALID) 258 | return CHTTP_ERROR_UNSPECIFIED; 259 | 260 | sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog); 261 | if (sm->plain_sock == NATIVE_SOCKET_INVALID) 262 | return CHTTP_ERROR_UNSPECIFIED; 263 | 264 | return CHTTP_OK; 265 | } 266 | 267 | int socket_manager_listen_tls(SocketManager *sm, 268 | CHTTP_String addr, Port port, int backlog, 269 | bool reuse_addr, CHTTP_String cert_file, 270 | CHTTP_String key_file) 271 | { 272 | #ifndef HTTPS_ENABLED 273 | return CHTTP_ERROR_NOTLS; 274 | #endif 275 | 276 | if (sm->secure_sock != NATIVE_SOCKET_INVALID) 277 | return CHTTP_ERROR_UNSPECIFIED; 278 | 279 | sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog); 280 | if (sm->secure_sock == NATIVE_SOCKET_INVALID) 281 | return CHTTP_ERROR_UNSPECIFIED; 282 | 283 | if (server_secure_context_init(&sm->server_secure_context, 284 | cert_file, key_file) < 0) { 285 | CLOSE_NATIVE_SOCKET(sm->secure_sock); 286 | sm->secure_sock = NATIVE_SOCKET_INVALID; 287 | return CHTTP_ERROR_UNSPECIFIED; 288 | } 289 | 290 | return CHTTP_OK; 291 | } 292 | 293 | int socket_manager_add_certificate(SocketManager *sm, 294 | CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file) 295 | { 296 | if (sm->secure_sock == NATIVE_SOCKET_INVALID) 297 | return CHTTP_ERROR_UNSPECIFIED; 298 | 299 | int ret = server_secure_context_add_certificate( 300 | &sm->server_secure_context, domain, cert_file, key_file); 301 | if (ret < 0) 302 | return ret; 303 | 304 | return CHTTP_OK; 305 | } 306 | 307 | static bool is_secure(Socket *s) 308 | { 309 | #ifdef HTTPS_ENABLED 310 | return s->server_secure_context != NULL 311 | || s->client_secure_context != NULL; 312 | #else 313 | (void) s; 314 | return false; 315 | #endif 316 | } 317 | 318 | static bool connect_pending(void) 319 | { 320 | #ifdef _WIN32 321 | return WSAGetLastError() == WSAEWOULDBLOCK; 322 | #else 323 | return errno == EINPROGRESS; 324 | #endif 325 | } 326 | 327 | static bool 328 | connect_failed_because_of_peer_2(int err) 329 | { 330 | #ifdef _WIN32 331 | return err == WSAECONNREFUSED 332 | || err == WSAETIMEDOUT 333 | || err == WSAENETUNREACH 334 | || err == WSAEHOSTUNREACH; 335 | #else 336 | return err == ECONNREFUSED 337 | || err == ETIMEDOUT 338 | || err == ENETUNREACH 339 | || err == EHOSTUNREACH; 340 | #endif 341 | } 342 | 343 | static bool 344 | connect_failed_because_of_peer(void) 345 | { 346 | #ifdef _WIN32 347 | int err = WSAGetLastError(); 348 | #else 349 | int err = errno; 350 | #endif 351 | return connect_failed_because_of_peer_2(err); 352 | } 353 | 354 | static void free_addr_list(AddressAndPort *addrs, int num_addr) 355 | { 356 | #ifdef HTTPS_ENABLED 357 | for (int i = 0; i < num_addr; i++) { 358 | RegisteredName *name = addrs[i].name; 359 | if (name) { 360 | assert(name->refs > 0); 361 | name->refs--; 362 | if (name->refs == 0) 363 | free(name); 364 | } 365 | } 366 | #else 367 | (void) addrs; 368 | (void) num_addr; 369 | #endif 370 | } 371 | 372 | // This function moves the socket state machine 373 | // to the next state until an I/O event would 374 | // be required to continue. 375 | static void socket_update(Socket *s) 376 | { 377 | // Each case of this switch encodes a state transition. 378 | // If the evaluated case requires a given I/O event to 379 | // continue, the loop will exit so that the caller can 380 | // wait for that event. If the case can continue to a 381 | // different case, the again flag is set, which causes 382 | // a different case to be evaluated. 383 | bool again; 384 | do { 385 | again = false; 386 | switch (s->state) { 387 | case SOCKET_STATE_PENDING: 388 | { 389 | // This point may be reached because 390 | // 1. The socket was just created by a connect 391 | // operation. 392 | // 2. Connecting to a host failed and now we 393 | // need to try the next one. 394 | // If (2) is true, we have some resources 395 | // to clean up. 396 | 397 | if (s->sock != NATIVE_SOCKET_INVALID) { 398 | // This is not the first attempt 399 | 400 | #ifdef HTTPS_ENABLED 401 | if (s->ssl) { 402 | SSL_free(s->ssl); 403 | s->ssl = NULL; 404 | } 405 | #endif 406 | 407 | CLOSE_NATIVE_SOCKET(s->sock); 408 | 409 | s->next_addr++; 410 | if (s->next_addr == s->num_addr) { 411 | // All addresses have been tried and failed 412 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 413 | s->events = 0; 414 | continue; 415 | } 416 | } 417 | 418 | AddressAndPort addr; 419 | if (s->num_addr == 1) 420 | addr = s->addr; 421 | else 422 | addr = s->addrs[s->next_addr]; 423 | 424 | int family = (addr.is_ipv4 ? AF_INET : AF_INET6); 425 | NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0); 426 | if (sock == NATIVE_SOCKET_INVALID) { 427 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 428 | s->events = 0; 429 | continue; 430 | } 431 | 432 | if (set_socket_blocking(sock, false) < 0) { 433 | CLOSE_NATIVE_SOCKET(sock); 434 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 435 | s->events = 0; 436 | continue; 437 | } 438 | 439 | int ret; 440 | if (addr.is_ipv4) { 441 | struct sockaddr_in buf; 442 | buf.sin_family = AF_INET; 443 | buf.sin_port = htons(addr.port); 444 | memcpy(&buf.sin_addr, &addr.ipv4, sizeof(CHTTP_IPv4)); 445 | ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); 446 | } else { 447 | struct sockaddr_in6 buf; 448 | buf.sin6_family = AF_INET6; 449 | buf.sin6_port = htons(addr.port); 450 | memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(CHTTP_IPv6)); 451 | ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); 452 | } 453 | 454 | if (ret == 0) { 455 | // Connect resolved immediately 456 | s->sock = sock; 457 | UPDATE_STATE(s->state, SOCKET_STATE_CONNECTED); 458 | s->events = 0; 459 | again = true; 460 | } else if (connect_pending()) { 461 | // Connect is pending, which is expected 462 | s->sock = sock; 463 | UPDATE_STATE(s->state, SOCKET_STATE_CONNECTING); 464 | s->events = POLLOUT; 465 | } else if (connect_failed_because_of_peer()) { 466 | // Conenct failed due to the peer host 467 | // We should try a different address. 468 | s->sock = sock; 469 | UPDATE_STATE(s->state, SOCKET_STATE_PENDING); 470 | s->events = 0; 471 | again = true; 472 | } else { 473 | // An error occurred that we can't recover from 474 | s->sock = sock; 475 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 476 | s->events = 0; 477 | again = true; 478 | } 479 | } 480 | break; 481 | 482 | case SOCKET_STATE_CONNECTING: 483 | { 484 | // This point is reached when a connect() 485 | // operation completes. 486 | 487 | int err = 0; 488 | socklen_t len = sizeof(err); 489 | if (getsockopt(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) { 490 | // Failed to get socket error status 491 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 492 | s->events = 0; 493 | continue; 494 | } 495 | 496 | if (err == 0) { 497 | // Connection succeded 498 | UPDATE_STATE(s->state, SOCKET_STATE_CONNECTED); 499 | s->events = 0; 500 | again = true; 501 | } else if (connect_failed_because_of_peer_2(err)) { 502 | // Try the next address 503 | UPDATE_STATE(s->state, SOCKET_STATE_PENDING); 504 | s->events = 0; 505 | again = true; 506 | } else { 507 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 508 | s->events = 0; 509 | } 510 | } 511 | break; 512 | 513 | case SOCKET_STATE_CONNECTED: 514 | { 515 | if (!is_secure(s)) { 516 | 517 | // We managed to connect to the peer. 518 | // We can free the target array if it 519 | // was allocated dynamically. 520 | if (s->num_addr > 1) 521 | free(s->addrs); 522 | 523 | s->events = 0; 524 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_READY); 525 | } else { 526 | #ifdef HTTPS_ENABLED 527 | if (s->ssl == NULL) { 528 | s->ssl = SSL_new(s->client_secure_context->p); 529 | if (s->ssl == NULL) { 530 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 531 | s->events = 0; 532 | break; 533 | } 534 | 535 | if (SSL_set_fd(s->ssl, s->sock) != 1) { 536 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 537 | s->events = 0; 538 | break; 539 | } 540 | 541 | SSL_set_verify(s->ssl, s->dont_verify_cert 542 | ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL); 543 | 544 | AddressAndPort addr; 545 | if (s->num_addr > 1) 546 | addr = s->addrs[s->next_addr]; 547 | else 548 | addr = s->addr; 549 | 550 | if (addr.name) { 551 | 552 | // Set expected hostname for verification 553 | if (SSL_set1_host(s->ssl, addr.name->data) != 1) { 554 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 555 | s->events = 0; 556 | break; 557 | } 558 | 559 | // Optional but recommended: be strict about wildcards 560 | SSL_set_hostflags(s->ssl, 561 | X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); 562 | 563 | // Also set for SNI (Server Name Indication) 564 | SSL_set_tlsext_host_name(s->ssl, addr.name->data); 565 | } 566 | } 567 | 568 | int ret = SSL_connect(s->ssl); 569 | if (ret == 1) { 570 | // Handshake done 571 | 572 | // We managed to connect to the peer. 573 | // We can free the target array if it 574 | // was allocated dynamically. 575 | if (s->num_addr == 1) 576 | free_addr_list(&s->addr, 1); 577 | else { 578 | assert(s->num_addr > 1); 579 | free_addr_list(s->addrs, s->num_addr); 580 | free(s->addrs); 581 | } 582 | 583 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_READY); 584 | s->events = 0; 585 | break; 586 | } 587 | 588 | int err = SSL_get_error(s->ssl, ret); 589 | if (err == SSL_ERROR_WANT_READ) { 590 | s->events = POLLIN; 591 | break; 592 | } 593 | 594 | if (err == SSL_ERROR_WANT_WRITE) { 595 | s->events = POLLOUT; 596 | break; 597 | } 598 | 599 | UPDATE_STATE(s->state, SOCKET_STATE_PENDING); 600 | s->events = 0; 601 | again = true; 602 | #endif 603 | } 604 | } 605 | break; 606 | 607 | case SOCKET_STATE_ACCEPTED: 608 | { 609 | if (!is_secure(s)) { 610 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_READY); 611 | s->events = 0; 612 | } else { 613 | #ifdef HTTPS_ENABLED 614 | // Start server-side SSL handshake 615 | if (!s->ssl) { 616 | 617 | s->ssl = SSL_new(s->server_secure_context->p); 618 | if (s->ssl == NULL) { 619 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 620 | s->events = 0; 621 | break; 622 | } 623 | 624 | if (SSL_set_fd(s->ssl, s->sock) != 1) { 625 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 626 | s->events = 0; 627 | break; 628 | } 629 | } 630 | 631 | int ret = SSL_accept(s->ssl); 632 | if (ret == 1) { 633 | // Handshake done 634 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_READY); 635 | s->events = 0; 636 | break; 637 | } 638 | 639 | int err = SSL_get_error(s->ssl, ret); 640 | if (err == SSL_ERROR_WANT_READ) { 641 | s->events = POLLIN; 642 | break; 643 | } 644 | 645 | if (err == SSL_ERROR_WANT_WRITE) { 646 | s->events = POLLOUT; 647 | break; 648 | } 649 | 650 | // Server socket error - close the connection 651 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 652 | s->events = 0; 653 | #endif 654 | } 655 | } 656 | break; 657 | 658 | case SOCKET_STATE_ESTABLISHED_WAIT: 659 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_READY); 660 | s->events = 0; 661 | break; 662 | 663 | case SOCKET_STATE_SHUTDOWN: 664 | { 665 | if (!is_secure(s)) { 666 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 667 | s->events = 0; 668 | } else { 669 | #ifdef HTTPS_ENABLED 670 | int ret = SSL_shutdown(s->ssl); 671 | if (ret == 1) { 672 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 673 | s->events = 0; 674 | break; 675 | } 676 | 677 | int err = SSL_get_error(s->ssl, ret); 678 | if (err == SSL_ERROR_WANT_READ) { 679 | s->events = POLLIN; 680 | break; 681 | } 682 | 683 | if (err == SSL_ERROR_WANT_WRITE) { 684 | s->events = POLLOUT; 685 | break; 686 | } 687 | 688 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 689 | s->events = 0; 690 | #endif 691 | } 692 | } 693 | break; 694 | 695 | default: 696 | // Do nothing 697 | break; 698 | } 699 | } while (again); 700 | } 701 | 702 | int socket_manager_wakeup(SocketManager *sm) 703 | { 704 | // NOTE: It's assumed send/write operate atomically 705 | // on The descriptor. 706 | char byte = 1; 707 | #ifdef _WIN32 708 | if (send(sm->signal_sock, &byte, 1, 0) < 0) 709 | return CHTTP_ERROR_UNSPECIFIED; 710 | #else 711 | if (write(sm->signal_sock, &byte, 1) < 0) 712 | return CHTTP_ERROR_UNSPECIFIED; 713 | #endif 714 | return CHTTP_OK; 715 | } 716 | 717 | void socket_manager_register_events( 718 | SocketManager *sm, EventRegister *reg) 719 | { 720 | reg->num_polled = 0; 721 | 722 | reg->polled[reg->num_polled].fd = sm->wait_sock; 723 | reg->polled[reg->num_polled].events = POLLIN; 724 | reg->polled[reg->num_polled].revents = 0; 725 | reg->ptrs[reg->num_polled] = NULL; 726 | reg->num_polled++; 727 | 728 | // If the manager isn't at full capacity, monitor 729 | // the listener sockets for incoming connections. 730 | if (sm->num_used < sm->max_used) { 731 | 732 | if (sm->plain_sock != NATIVE_SOCKET_INVALID) { 733 | reg->polled[reg->num_polled].fd = sm->plain_sock; 734 | reg->polled[reg->num_polled].events = POLLIN; 735 | reg->polled[reg->num_polled].revents = 0; 736 | reg->ptrs[reg->num_polled] = NULL; 737 | reg->num_polled++; 738 | } 739 | 740 | if (sm->secure_sock != NATIVE_SOCKET_INVALID) { 741 | reg->polled[reg->num_polled].fd = sm->secure_sock; 742 | reg->polled[reg->num_polled].events = POLLIN; 743 | reg->polled[reg->num_polled].revents = 0; 744 | reg->ptrs[reg->num_polled] = NULL; 745 | reg->num_polled++; 746 | } 747 | } 748 | 749 | // Iterate over each socket and register those that 750 | // are waiting for I/O. If at least one socket that 751 | // is ready to be processed exists, return an empty 752 | // event registration list so that those entries can 753 | // be processed immediately. 754 | // TODO: comment about deadline 755 | Time deadline = INVALID_TIME; 756 | for (int i = 0, j = 0; j < sm->num_used; i++) { 757 | Socket *s = &sm->sockets[i]; 758 | if (s->state == SOCKET_STATE_FREE) 759 | continue; 760 | j++; 761 | 762 | if (s->silent) 763 | continue; 764 | 765 | if (s->creation_timeout != INVALID_TIME) { 766 | Time creation_deadline = s->creation_time + s->creation_timeout; 767 | if (deadline == INVALID_TIME || creation_deadline < deadline) 768 | deadline = creation_deadline; 769 | } 770 | 771 | if (s->recv_timeout != INVALID_TIME) { 772 | Time recv_deadline = s->last_recv_time + s->recv_timeout; 773 | if (deadline == INVALID_TIME || recv_deadline < deadline) 774 | deadline = recv_deadline; 775 | } 776 | 777 | // If at least one socket can be processed, return an 778 | // empty list. 779 | if (s->state == SOCKET_STATE_DIED || 780 | s->state == SOCKET_STATE_ESTABLISHED_READY) { 781 | deadline = 0; 782 | } 783 | 784 | if (s->events) { 785 | reg->polled[reg->num_polled].fd = s->sock; 786 | reg->polled[reg->num_polled].events = s->events; 787 | reg->polled[reg->num_polled].revents = 0; 788 | reg->ptrs[reg->num_polled] = s; 789 | reg->num_polled++; 790 | } 791 | } 792 | 793 | if (deadline == INVALID_TIME) { 794 | reg->timeout = -1; 795 | } else { 796 | 797 | Time current_time = get_current_time(); 798 | if (current_time == INVALID_TIME) { 799 | reg->timeout = 1000; 800 | } else if (deadline < current_time) { 801 | reg->timeout = 0; 802 | } else { 803 | reg->timeout = deadline - current_time; 804 | } 805 | } 806 | } 807 | 808 | static SocketHandle 809 | socket_to_handle(SocketManager *sm, Socket *s) 810 | { 811 | return ((uint32_t) (s - sm->sockets) << 16) | s->gen; 812 | } 813 | 814 | static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle) 815 | { 816 | uint16_t gen = handle & 0xFFFF; 817 | uint16_t idx = handle >> 16; 818 | if (idx >= sm->max_used) 819 | return NULL; 820 | if (sm->sockets[idx].gen != gen) 821 | return NULL; 822 | return &sm->sockets[idx]; 823 | } 824 | 825 | int socket_manager_translate_events( 826 | SocketManager *sm, SocketEvent *events, 827 | EventRegister reg) 828 | { 829 | Time current_time = get_current_time(); 830 | 831 | int num_events = 0; 832 | for (int i = 0; i < reg.num_polled; i++) { 833 | 834 | if (!reg.polled[i].revents) 835 | continue; 836 | 837 | if (reg.polled[i].fd == sm->plain_sock || 838 | reg.polled[i].fd == sm->secure_sock) { 839 | 840 | // We only listen for input events from the listener 841 | // if the socket pool isn't fool. This ensures that 842 | // at least one socket struct is available. Note that 843 | // it's still possible that we were at capacity MAX-1 844 | // and then got events from both the TCP and TCP/TLS 845 | // listeners, causing one to be left witout a struct. 846 | // This means we still need to check for full capacity. 847 | // Fortunately, poll() is level-triggered, which means 848 | // we'll handle this at the next iteration. 849 | if (sm->num_used == sm->max_used) 850 | continue; 851 | 852 | Socket *s = sm->sockets; 853 | while (s->state != SOCKET_STATE_FREE) { 854 | s++; 855 | assert(s - sm->sockets < + sm->max_used); 856 | } 857 | 858 | NATIVE_SOCKET sock = accept(reg.polled[i].fd, NULL, NULL); 859 | if (sock == NATIVE_SOCKET_INVALID) 860 | continue; 861 | 862 | if (set_socket_blocking(sock, false) < 0) { 863 | CLOSE_NATIVE_SOCKET(sock); 864 | continue; 865 | } 866 | 867 | s->state = SOCKET_STATE_ACCEPTED; 868 | s->sock = sock; 869 | s->events = 0; 870 | s->user = NULL; 871 | s->silent = false; 872 | s->creation_time = current_time; 873 | s->last_recv_time = current_time; 874 | s->creation_timeout = sm->creation_timeout; 875 | s->recv_timeout = sm->recv_timeout; 876 | #ifdef HTTPS_ENABLED 877 | // Determine whether the event came from 878 | // the encrypted listener or not. 879 | bool secure = (reg.polled[i].fd == sm->secure_sock); 880 | 881 | s->ssl = NULL; 882 | s->server_secure_context = NULL; 883 | s->client_secure_context = NULL; 884 | if (secure) 885 | s->server_secure_context = &sm->server_secure_context; 886 | #endif 887 | 888 | socket_update(s); 889 | if (s->state == SOCKET_STATE_DIED) { 890 | CLOSE_NATIVE_SOCKET(sock); 891 | UPDATE_STATE(s->state, SOCKET_STATE_FREE); 892 | s->gen++; 893 | if (s->gen == 0) 894 | s->gen = 1; 895 | continue; 896 | } 897 | 898 | sm->num_used++; 899 | 900 | } else if (reg.polled[i].fd == sm->wait_sock) { 901 | 902 | // Consume one byte from the wakeup signal 903 | char byte; 904 | #ifdef _WIN32 905 | recv(sm->wait_sock, &byte, 1, 0); 906 | #else 907 | read(sm->wait_sock, &byte, 1); 908 | #endif 909 | 910 | } else { 911 | 912 | Socket *s = reg.ptrs[i]; 913 | assert(!s->silent); 914 | 915 | socket_update(s); 916 | } 917 | } 918 | 919 | for (int i = 0, j = 0; j < sm->num_used; i++) { 920 | Socket *s = &sm->sockets[i]; 921 | if (s->state == SOCKET_STATE_FREE) 922 | continue; 923 | j++; 924 | 925 | if (s->silent) 926 | continue; 927 | 928 | if (s->creation_timeout != INVALID_TIME 929 | && current_time != INVALID_TIME 930 | && current_time > s->creation_time + s->creation_timeout) { 931 | 932 | s->creation_time = INVALID_TIME; 933 | 934 | events[num_events++] = (SocketEvent) { 935 | SOCKET_EVENT_CREATION_TIMEOUT, 936 | socket_to_handle(sm, s), 937 | s->user 938 | }; 939 | 940 | } else if (s->recv_timeout != INVALID_TIME 941 | && current_time != INVALID_TIME 942 | && current_time > s->last_recv_time + s->recv_timeout) { 943 | 944 | s->recv_timeout = INVALID_TIME; 945 | 946 | events[num_events++] = (SocketEvent) { 947 | SOCKET_EVENT_RECV_TIMEOUT, 948 | socket_to_handle(sm, s), 949 | s->user 950 | }; 951 | 952 | } else if (s->state == SOCKET_STATE_DIED) { 953 | 954 | events[num_events++] = (SocketEvent) { 955 | SOCKET_EVENT_DISCONNECT, 956 | SOCKET_HANDLE_INVALID, 957 | s->user 958 | }; 959 | 960 | // Free resources associated to socket 961 | UPDATE_STATE(s->state, SOCKET_STATE_FREE); 962 | if (s->sock != NATIVE_SOCKET_INVALID) 963 | CLOSE_NATIVE_SOCKET(s->sock); 964 | if (s->sock == SOCKET_STATE_PENDING || 965 | s->sock == SOCKET_STATE_CONNECTING) { 966 | if (s->num_addr > 1) 967 | free(s->addrs); 968 | } 969 | #ifdef HTTPS_ENABLED 970 | if (s->ssl) 971 | SSL_free(s->ssl); 972 | #endif // HTTPS_ENABLED 973 | sm->num_used--; 974 | 975 | } else if (s->state == SOCKET_STATE_ESTABLISHED_READY) { 976 | 977 | events[num_events++] = (SocketEvent) { 978 | SOCKET_EVENT_READY, 979 | socket_to_handle(sm, s), 980 | s->user 981 | }; 982 | } 983 | } 984 | 985 | return num_events; 986 | } 987 | 988 | static int resolve_connect_targets(ConnectTarget *targets, 989 | int num_targets, AddressAndPort *resolved, int max_resolved) 990 | { 991 | int num_resolved = 0; 992 | for (int i = 0; i < num_targets; i++) { 993 | switch (targets[i].type) { 994 | case CONNECT_TARGET_NAME: 995 | { 996 | char portstr[16]; 997 | int len = snprintf(portstr, sizeof(portstr), "%u", targets[i].port); 998 | assert(len > 1 && len < (int) sizeof(portstr)); 999 | 1000 | struct addrinfo hints = {0}; 1001 | hints.ai_family = AF_UNSPEC; 1002 | hints.ai_socktype = SOCK_STREAM; 1003 | 1004 | #ifdef HTTPS_ENABLED 1005 | RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1); 1006 | if (name == NULL) { 1007 | free_addr_list(resolved, num_resolved); 1008 | return CHTTP_ERROR_OOM; 1009 | } 1010 | name->refs = 0; 1011 | memcpy(name->data, targets[i].name.ptr, targets[i].name.len); 1012 | name->data[targets[i].name.len] = '\0'; 1013 | char *hostname = name->data; 1014 | #else 1015 | // 512 bytes is more than enough for a DNS hostname (max 253 chars) 1016 | char hostname[1<<9]; 1017 | if (targets[i].name.len >= (int) sizeof(hostname)) 1018 | return CHTTP_ERROR_OOM; 1019 | memcpy(hostname, targets[i].name.ptr, targets[i].name.len); 1020 | hostname[targets[i].name.len] = '\0'; 1021 | #endif 1022 | struct addrinfo *res = NULL; 1023 | int ret = getaddrinfo(hostname, portstr, &hints, &res); 1024 | if (ret != 0) { 1025 | #ifdef HTTPS_ENABLED 1026 | // Free the name allocated for this target 1027 | free(name); 1028 | #endif 1029 | free_addr_list(resolved, num_resolved); 1030 | return CHTTP_ERROR_UNSPECIFIED; 1031 | } 1032 | 1033 | for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { 1034 | if (rp->ai_family == AF_INET) { 1035 | CHTTP_IPv4 ipv4 = *(CHTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; 1036 | if (num_resolved < max_resolved) { 1037 | resolved[num_resolved].is_ipv4 = true; 1038 | resolved[num_resolved].ipv4 = ipv4; 1039 | resolved[num_resolved].port = targets[i].port; 1040 | #ifdef HTTPS_ENABLED 1041 | resolved[num_resolved].name = name; 1042 | name->refs++; 1043 | #endif 1044 | num_resolved++; 1045 | } 1046 | } else if (rp->ai_family == AF_INET6) { 1047 | CHTTP_IPv6 ipv6 = *(CHTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; 1048 | if (num_resolved < max_resolved) { 1049 | resolved[num_resolved].is_ipv4 = false; 1050 | resolved[num_resolved].ipv6 = ipv6; 1051 | resolved[num_resolved].port = targets[i].port; 1052 | #ifdef HTTPS_ENABLED 1053 | resolved[num_resolved].name = name; 1054 | name->refs++; 1055 | #endif 1056 | num_resolved++; 1057 | } 1058 | } 1059 | } 1060 | 1061 | #ifdef HTTPS_ENABLED 1062 | if (name->refs == 0) 1063 | free(name); 1064 | #endif 1065 | 1066 | freeaddrinfo(res); 1067 | } 1068 | break; 1069 | case CONNECT_TARGET_IPV4: 1070 | if (num_resolved < max_resolved) { 1071 | resolved[num_resolved].is_ipv4 = true; 1072 | resolved[num_resolved].ipv4 = targets[i].ipv4; 1073 | resolved[num_resolved].port = targets[i].port; 1074 | #ifdef HTTPS_ENABLED 1075 | resolved[num_resolved].name = NULL; 1076 | #endif 1077 | num_resolved++; 1078 | } 1079 | break; 1080 | case CONNECT_TARGET_IPV6: 1081 | if (num_resolved < max_resolved) { 1082 | resolved[num_resolved].is_ipv4 = false; 1083 | resolved[num_resolved].ipv6 = targets[i].ipv6; 1084 | resolved[num_resolved].port = targets[i].port; 1085 | #ifdef HTTPS_ENABLED 1086 | resolved[num_resolved].name = NULL; 1087 | #endif 1088 | num_resolved++; 1089 | } 1090 | break; 1091 | } 1092 | } 1093 | return num_resolved; 1094 | } 1095 | 1096 | #define MAX_CONNECT_TARGETS 16 1097 | 1098 | int socket_connect(SocketManager *sm, int num_targets, 1099 | ConnectTarget *targets, bool secure, bool dont_verify_cert, 1100 | void *user) 1101 | { 1102 | Time current_time = get_current_time(); 1103 | if (current_time == INVALID_TIME) 1104 | return CHTTP_ERROR_UNSPECIFIED; 1105 | 1106 | if (sm->num_used == sm->max_used) 1107 | return CHTTP_ERROR_UNSPECIFIED; 1108 | 1109 | #ifdef HTTPS_ENABLED 1110 | if (!sm->at_least_one_secure_connect) { 1111 | if (client_secure_context_init(&sm->client_secure_context) < 0) 1112 | return CHTTP_ERROR_UNSPECIFIED; 1113 | sm->at_least_one_secure_connect = true; 1114 | } 1115 | #else 1116 | if (secure) 1117 | return CHTTP_ERROR_NOTLS; 1118 | #endif 1119 | 1120 | AddressAndPort resolved[MAX_CONNECT_TARGETS]; 1121 | int num_resolved = resolve_connect_targets( 1122 | targets, num_targets, resolved, MAX_CONNECT_TARGETS); 1123 | 1124 | if (num_resolved <= 0) 1125 | return CHTTP_ERROR_UNSPECIFIED; 1126 | 1127 | Socket *s = sm->sockets; 1128 | while (s->state != SOCKET_STATE_FREE) { 1129 | s++; 1130 | assert(s - sm->sockets < + sm->max_used); 1131 | } 1132 | 1133 | if (num_resolved == 1) { 1134 | s->num_addr = 1; 1135 | s->next_addr = 0; 1136 | s->addr = resolved[0]; 1137 | } else { 1138 | s->num_addr = num_resolved; 1139 | s->next_addr = 0; 1140 | s->addrs = malloc(num_resolved * sizeof(AddressAndPort)); 1141 | if (s->addrs == NULL) 1142 | return CHTTP_ERROR_OOM; 1143 | for (int i = 0; i < num_resolved; i++) 1144 | s->addrs[i] = resolved[i]; 1145 | } 1146 | 1147 | UPDATE_STATE(s->state, SOCKET_STATE_PENDING); 1148 | s->sock = NATIVE_SOCKET_INVALID; 1149 | s->user = user; 1150 | s->silent = false; 1151 | s->creation_time = current_time; 1152 | s->last_recv_time = current_time; 1153 | s->creation_timeout = sm->creation_timeout; 1154 | s->recv_timeout = sm->recv_timeout; 1155 | #ifdef HTTPS_ENABLED 1156 | s->server_secure_context = NULL; 1157 | s->client_secure_context = NULL; 1158 | s->ssl = NULL; 1159 | s->dont_verify_cert = false; 1160 | if (secure) { 1161 | s->client_secure_context = &sm->client_secure_context; 1162 | s->dont_verify_cert = dont_verify_cert; 1163 | } 1164 | #else 1165 | (void) dont_verify_cert; 1166 | #endif 1167 | sm->num_used++; 1168 | 1169 | socket_update(s); 1170 | return CHTTP_OK; 1171 | } 1172 | 1173 | static bool would_block(void) 1174 | { 1175 | #ifdef _WIN32 1176 | int err = WSAGetLastError(); 1177 | return err == WSAEWOULDBLOCK; 1178 | #else 1179 | return errno == EAGAIN || errno == EWOULDBLOCK; 1180 | #endif 1181 | } 1182 | 1183 | static bool interrupted(void) 1184 | { 1185 | #ifdef _WIN32 1186 | return false; 1187 | #else 1188 | return errno == EINTR; 1189 | #endif 1190 | } 1191 | 1192 | int socket_recv(SocketManager *sm, SocketHandle handle, 1193 | char *dst, int max) 1194 | { 1195 | Socket *s = handle_to_socket(sm, handle); 1196 | if (s == NULL) 1197 | return 0; 1198 | 1199 | if (s->state != SOCKET_STATE_ESTABLISHED_READY) { 1200 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 1201 | s->events = 0; 1202 | return 0; 1203 | } 1204 | 1205 | int ret; 1206 | if (!is_secure(s)) { 1207 | ret = recv(s->sock, dst, max, 0); 1208 | if (ret == 0) { 1209 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 1210 | s->events = 0; 1211 | } else if (ret < 0) { 1212 | if (would_block()) { 1213 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_WAIT); 1214 | s->events = POLLIN; 1215 | } else if (!interrupted()) { 1216 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 1217 | s->events = 0; 1218 | } 1219 | ret = 0; 1220 | } 1221 | } else { 1222 | #ifdef HTTPS_ENABLED 1223 | ret = SSL_read(s->ssl, dst, max); 1224 | if (ret <= 0) { 1225 | int err = SSL_get_error(s->ssl, ret); 1226 | if (err == SSL_ERROR_WANT_READ) { 1227 | s->state = SOCKET_STATE_ESTABLISHED_WAIT; 1228 | s->events = POLLIN; 1229 | } else if (err == SSL_ERROR_WANT_WRITE) { 1230 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_WAIT); 1231 | s->events = POLLOUT; 1232 | } else { 1233 | s->state = SOCKET_STATE_DIED; 1234 | s->events = 0; 1235 | } 1236 | ret = 0; 1237 | } 1238 | #else 1239 | // Unreachable 1240 | ret = 0; 1241 | #endif 1242 | } 1243 | 1244 | if (ret > 0 && s->recv_timeout != INVALID_TIME) { 1245 | Time current_time = get_current_time(); 1246 | if (current_time != INVALID_TIME) 1247 | s->last_recv_time = current_time; 1248 | } 1249 | return ret; 1250 | } 1251 | 1252 | int socket_send(SocketManager *sm, SocketHandle handle, 1253 | char *src, int len) 1254 | { 1255 | Socket *s = handle_to_socket(sm, handle); 1256 | if (s == NULL) 1257 | return 0; 1258 | 1259 | if (s->state != SOCKET_STATE_ESTABLISHED_READY) { 1260 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 1261 | s->events = 0; 1262 | return 0; 1263 | } 1264 | 1265 | if (!is_secure(s)) { 1266 | int ret = send(s->sock, src, len, 0); 1267 | if (ret < 0) { 1268 | if (would_block()) { 1269 | UPDATE_STATE(s->state, SOCKET_STATE_ESTABLISHED_WAIT); 1270 | s->events = POLLOUT; 1271 | } else if (!interrupted()) { 1272 | UPDATE_STATE(s->state, SOCKET_STATE_DIED); 1273 | s->events = 0; 1274 | } 1275 | ret = 0; 1276 | } 1277 | return ret; 1278 | } else { 1279 | #ifdef HTTPS_ENABLED 1280 | int ret = SSL_write(s->ssl, src, len); 1281 | if (ret <= 0) { 1282 | int err = SSL_get_error(s->ssl, ret); 1283 | if (err == SSL_ERROR_WANT_READ) { 1284 | s->state = SOCKET_STATE_ESTABLISHED_WAIT; 1285 | s->events = POLLIN; 1286 | } else if (err == SSL_ERROR_WANT_WRITE) { 1287 | s->state = SOCKET_STATE_ESTABLISHED_WAIT; 1288 | s->events = POLLOUT; 1289 | } else { 1290 | s->state = SOCKET_STATE_DIED; 1291 | s->events = 0; 1292 | } 1293 | ret = 0; 1294 | } 1295 | return ret; 1296 | #else 1297 | // Unreachable 1298 | return 0; 1299 | #endif 1300 | } 1301 | } 1302 | 1303 | void socket_close(SocketManager *sm, SocketHandle handle) 1304 | { 1305 | Socket *s = handle_to_socket(sm, handle); 1306 | if (s == NULL) 1307 | return; 1308 | 1309 | if (s->state != SOCKET_STATE_DIED) { 1310 | UPDATE_STATE(s->state, SOCKET_STATE_SHUTDOWN); 1311 | s->events = 0; 1312 | socket_update(s); 1313 | } 1314 | } 1315 | 1316 | bool socket_is_secure(SocketManager *sm, SocketHandle handle) 1317 | { 1318 | Socket *s = handle_to_socket(sm, handle); 1319 | if (s == NULL) 1320 | return false; 1321 | return is_secure(s); 1322 | } 1323 | 1324 | void socket_set_user(SocketManager *sm, SocketHandle handle, void *user) 1325 | { 1326 | Socket *s = handle_to_socket(sm, handle); 1327 | if (s == NULL) 1328 | return; 1329 | 1330 | s->user = user; 1331 | } 1332 | 1333 | bool socket_ready(SocketManager *sm, SocketHandle handle) 1334 | { 1335 | Socket *s = handle_to_socket(sm, handle); 1336 | if (s == NULL) 1337 | return false; 1338 | 1339 | if (s->events == 0 && s->state != SOCKET_STATE_DIED) 1340 | return true; 1341 | 1342 | return false; 1343 | } 1344 | 1345 | void socket_silent(SocketManager *sm, SocketHandle handle, bool value) 1346 | { 1347 | Socket *s = handle_to_socket(sm, handle); 1348 | if (s == NULL) 1349 | return; 1350 | 1351 | s->silent = value; 1352 | } 1353 | --------------------------------------------------------------------------------