diff --git a/.gitignore b/.gitignore index 7d1f1d4..aeb6744 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -misc coverage_report # Coverage stuff diff --git a/Makefile b/Makefile index 570c28e..bdcd5d8 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,21 @@ -.PHONY: all report -#OSTAG = LINUX -OSTAG = WINDOWS +CC = gcc +CFLAGS = -I. -Wall -Wextra -O0 -g3 +LFLAGS = -lssl -lcrypto -EXT_WINDOWS = .exe -EXT_LINUX = .out +CFILES = $(shell find src -name "*.c") +HFILES = $(shell find src -name "*.h") -LFLAGS_WINDOWS = -lws2_32 -LFLAGS_LINUX = -lssl -lcrypto -ggdb +all: client_example server_example http.c http.h -LFLAGS = ${LFLAGS_${OSTAG}} -EXT = ${EXT_${OSTAG}} +http.c http.h: $(HFILES) $(CFILES) + python misc/amalg.py -all: - gcc -o simple_server$(EXT) examples/simple_server.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) - gcc -o blocking_server_with_engine$(EXT) examples/blocking_server_with_engine.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) -g3 - gcc -o iocp_server_with_engine.exe examples/iocp_server_with_engine.c tinyhttp.c -Wall -Wextra -lws2_32 - gcc -o test$(EXT) tests/test.c tests/test_branch_coverage_parse.c tests/test_branch_coverage_engine.c tests/test_fuzz_engine.c tinyhttp.c -fprofile-arcs -ftest-coverage $(LFLAGS) - -report: - lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1 - genhtml coverage.info --output-directory coverage_report --rc lcov_branch_coverage=1 --rc genhtml_branch_coverage=1 +client_example: examples/client_example.c http.c http.h + $(CC) examples/client_example.c http.c $(CFLAGS) -o $@ $(LFLAGS) + +server_example: examples/server_example.c http.c http.h + $(CC) examples/server_example.c http.c $(CFLAGS) -o $@ $(LFLAGS) clean: - rm *.gcda *.gcno coverage.info test$(EXT_WINDOWS) test$(EXT_LINUX) + rm -f client_example server_example diff --git a/README.md b/README.md index 909053c..2e9f782 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,4 @@ -# TinyHTTP +# cozis/http +This is an HTTP client and server library for C. -TinyHTTP is a library for building cross-platform and fully non-blocking HTTP 1.1 clients and servers in C. - -## Roadmap and status - -The project is still in the prototyping phase. I'm working on testing for robustness and compliance to RFC 9110, 9111, 9112, and 3986. The server is missing timers and HTTPS. The client is just a proof of concept at this point. - -## Overview - -The architecture looks like this: - -``` - +--------+ - | ROUTER | -+--------+--------+ -| CLIENT | SERVER | -+--------+--------+ -| ENGINE | -+-----------------+ -| PARSER | -+-----------------+ -``` - -At the lowest level there are HTTP request, HTTP response, and URI parser. Then comes the HTTP "engine", which contains the HTTP 1.1 state machine. These two layers don't depend on basically anything. Probably only freestanding libc headers. The engine is designed in such a way that it does not perform I/O. Instead, applications feed it bytes from the network and eventually get a request or response object from it. When data needs to be output, the engine lets that know to the application. An HTTP engine represents the communication between one server and one client, so a non-blocking server would typically use an array of engines. - -To give you the general idea, a simple blocking server using the engine would look somewhat like this: -```c -int main(void) -{ - SOCKET listen_fd = start_server("127.0.0.1", 8080); - - for (;;) { - - SOCKET client_fd = accept(listen_fd, NULL, NULL); - - int is_client = 0; // If this were true, the engine would behave as the client instead - - HTTP_Engine eng; - http_engine_init(&eng, is_client, memfunc, NULL); - - // Loop until the engine is in the CLOSED state - for (int closed = 0; !closed; ) { - - // The engine may be in one of 4 states: - // RECV_BUF -> Bytes are expected from the network - // SEND_BUF -> Bytes need to be sent on the network - // PREP_STATUS -> A request is available - // CLOSED -> The connection was shut down at the HTTP level - switch (http_engine_state(&eng)) { - - case HTTP_ENGINE_STATE_SERVER_CLOSED: - closed = 1; - break; - - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: - { - int cap; - char *dst = http_engine_recvbuf(&eng, &cap); - int ret = recv(client_fd, dst, cap, 0); - if (ret <= 0) { - http_engine_close(&eng); - break; - } - http_engine_recvack(&eng, ret); - } - break; - - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: - { - int len; - char *src = http_engine_sendbuf(&eng, &len); - int ret = send(client_fd, src, len, 0); - if (ret <= 0) { - http_engine_close(&eng); - break; - } - http_engine_sendack(&eng, ret); - } - break; - - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: - { - HTTP_Request *req = http_engine_getreq(&eng); - - http_engine_status(&eng, 200); - http_engine_header(&eng, "Server: tinyhttp", 16); - http_engine_body(&eng, "Hello, world!", 13); - http_engine_done(&eng); - } - break; - } - } - - http_engine_free(&eng); - close(client_fd); - } - - close(listen_fd); - return 0; -} -``` -This interface allows TinyHTTP to decouple the HTTP logic from the I/O. This has many advantages such as simplifying testing and decoupling TLS from the HTTP logic. - -On top of the engine, TinyHTTP implements a fully functional and easy to use client and server. Both use `poll()` to handle non-blocking operations. The server API looks like this: - -```c -#include "tinyhttp.h" - -int main(void) -{ - int ret; - HTTP_Server server; - - ret = http_server_init(&server, "127.0.0.1", 8080); - if (ret < 0) return -1; - - for (;;) { - HTTP_Request *req; - HTTP_ResponseHandle res; - - ret = http_server_wait(&server, &req, &res, -1); - if (ret < 0) - return -1; - if (ret == 0) - continue; - - http_response_status(res, 200); - http_response_header(res, "Server: tinyhttp"); - http_response_body(res, "Hello, world!", 13); - http_response_done(res); - } - - http_server_free(&server); - return 0; -} -``` - -while the client interface looks like this (note that I omitted error checking for making easier to digest) - -```c -#include -#include "tinyhttp.h" - -int main(void) -{ - HTTP_Client client; - HTTP_TLSContext tls; - - // Initialize the TLS stuff - http_tls_global_init(); - http_tls_init(&tls); - - // Initialize the client context - http_client_init(&client); - - // Start the request - http_client_startreq(&client, HTTP_METHOD_GET, "https://coz.is/hello.html", NULL, 0, NULL, 0, &tls); - - // Wait for the request to complete - // (you could wait for more multiple request at once) - HTTP_Client *wait_list[] = { &client }; - http_client_waitall(wait_list, 1, -1); - - // Read the response - HTTP_Response *res; - http_client_result(&client, &res); - fwrite(res->body.ptr, 1, res->body.len, stdout); - - // Free the client context - http_client_free(&client); - - // Free the TLS stuff - http_tls_free(&tls); - http_tls_global_free(); - return 0; -} -``` - -The client supports HTTPS by using OpenSSL, but the implementation is incomplete. You can get basic requests going but most things are not working yet. The server only supports plain text HTTP, but the interface is more mature. I think it's a great choice for small to medium websites (up to 500 concurrent users I'd say). - -The last layer is the router, which sits on top of the HTTP server. This simplifies the work of serving files from disk or setting up routes with dynamic content in an easy and safe way. Here's an example: - -```c -#include "tinyhttp.h" - -void callback(HTTP_Request *req, HTTP_ResponseHandle res, void *ctx) -{ - http_response_status(res, 200); - http_response_body(res, "Hello!", -1); - http_response_done(res); -} - -int main(void) -{ - HTTP_Router *router = http_router_init(); - - // Make /say_hello a dynamic resource - http_router_func(router, HTTP_METHOD_POST, HTTP_STR("/say_hello"), callback, NULL); - - // Requests to resources in the root folder are served from the examples folder - http_router_dir(router, HTTP_STR("/"), HTTP_STR("/examples")); - - return http_serve("127.0.0.1", 8080, router); -} -``` +This is my attempt at solving the "HTTP problem" for the C language. Writing C programs that behave as or interact with web services is always more painful than necessary in C. You either need to use `libcurl` which is overkill in most situations or link a large scale web servers to serve simple pages. This library targets smaller scale use-cases and tries to be as nice as possible to work with. Even then, it is fast. No performance is left on the table unless there is a specific reason. And if you do want to work at larger scales by using more sophisticate I/O systems (io_uring, I/O completion ports, etc) you can reuse the core state machine of the library that is I/O independant. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3e9c936 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +Allow no TLS builds +make sure no TLS builds work on windows +Allow resolving requests from different threads +Find a way to make sure http.h and http.c are always up to date \ No newline at end of file diff --git a/examples/client/client_example.c b/examples/client/client_example.c new file mode 100644 index 0000000..064415f --- /dev/null +++ b/examples/client/client_example.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include "../http.h" + +int main(int argc, char **argv) { + if (argc < 2) { + printf("Usage: %s [url2 ...]\n", argv[0]); + return 1; + } + + http_global_init(); + HTTP_Client *client = http_client_init(); + + HTTP_RequestHandle reqs[100]; + + for (int i = 1; i < argc; i++) { + int k = i-1; + if (http_client_request(client, &reqs[k]) < 0) { + printf("request creation error\n"); + return -1; + } + http_request_line(reqs[k], HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) }); + http_request_submit(reqs[k]); + printf("request submitted\n"); + } + + for (int i = 1; i < argc; i++) + if (http_client_wait(client, NULL) < 0) { + printf("request wait error\n"); + return -1; + } + + printf("all requests completed\n"); + + for (int i = 1; i < argc; i++) { + + HTTP_Response *result = http_request_result(reqs[i-1]); + if (!result) { + fprintf(stderr, "No result from HTTP request\n"); + http_request_free(reqs[i-1]); + return 1; + } + + printf("Status: %d\n", result->status); + printf("Body: %.*s\n", (int)result->body.len, result->body.ptr); + + http_request_free(reqs[i-1]); + } + + http_client_free(client); + http_global_free(); + return 0; +} \ No newline at end of file diff --git a/examples/client/web_crawler.c b/examples/client/web_crawler.c new file mode 100644 index 0000000..2e531fb --- /dev/null +++ b/examples/client/web_crawler.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include + +#define COUNT(X) (sizeof(X) / sizeof((X)[0])) + +#define BLK "\e[0;30m" +#define RED "\e[0;31m" +#define GRN "\e[0;32m" +#define YEL "\e[0;33m" +#define BLU "\e[0;34m" +#define MAG "\e[0;35m" +#define CYN "\e[0;36m" +#define WHT "\e[0;37m" +#define RST "\e[0m" + +HTTP_String *already_crawled = NULL; +int num_already_crawled = 0; +int cap_already_crawled = 0; + +HTTP_String copystr(HTTP_String str) +{ + char *copy = malloc(str.len); + if (copy == NULL) + abort(); + memcpy(copy, str.ptr, str.len); + return (HTTP_String) { copy, str.len }; +} + +int normalize_url(HTTP_String url, char *dst, int max) +{ + HTTP_URL parsed_url; + if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0) + return -1; + + int len = snprintf(dst, max, "http://%.*s%.*s", + (int) parsed_url.authority.host.text.len, + parsed_url.authority.host.text.ptr, + (int) parsed_url.path.len, + parsed_url.path.ptr + ); + if (len < 0 || len >= max) + return -1; + + return len; +} + +void add_to_crawled_list(HTTP_String url) +{ + if (num_already_crawled == cap_already_crawled) { + + int new_cap = 2 * cap_already_crawled; + if (new_cap == 0) + new_cap = 8; + + HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String)); + if (new_ptr == NULL) + abort(); + + if (cap_already_crawled > 0) { + for (int i = 0; i < num_already_crawled; i++) + new_ptr[i] = already_crawled[i]; + free(already_crawled); + } + + already_crawled = new_ptr; + cap_already_crawled = new_cap; + } + + char buf[1<<10]; + int len = normalize_url(url, buf, (int) sizeof(buf)); + if (len < 0) return; + + already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len }); +} + +bool is_already_crawled(HTTP_String url) +{ + char buf[1<<10]; + int len = normalize_url(url, buf, (int) sizeof(buf)); + if (len < 0 || len >= (int) sizeof(buf)) + return false; + + for (int i = 0; i < num_already_crawled; i++) + if (http_streq(already_crawled[i], (HTTP_String) { buf, len })) + return true; + + return false; +} + +HTTP_String next_link(HTTP_String src, int *pcur) +{ + int cur = *pcur; + + for (;;) { + + while (cur < src.len && src.ptr[cur] != 'h') + cur++; + + if (cur == src.len) + break; + + int off = cur; + + HTTP_URL parsed_url; + int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url); + if (len <= 0) { + cur++; + continue; + } + + cur += len; + + if (!http_streq(parsed_url.scheme, HTTP_STR("http")) && + !http_streq(parsed_url.scheme, HTTP_STR("https"))) + continue; + + *pcur = cur; + return (HTTP_String) { src.ptr + off, len }; + } + + *pcur = cur; + return (HTTP_String) { NULL, 0 }; +} + +int main(int argc, char **argv) +{ + http_global_init(); + + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return -1; + } + HTTP_String start_url = { argv[1], strlen(argv[1]) }; + + HTTP_Client *client = http_client_init(); + if (client == NULL) { + printf("Couldn't initialize HTTP client object\n"); + return -1; + } + + HTTP_RequestHandle req; + int ret = http_client_request(client, &req); + if (ret < 0) { + printf("Couldn't start request\n"); + http_client_free(client); + return -1; + } + http_request_line(req, HTTP_METHOD_GET, start_url); + http_request_header(req, "User-Agent: Simple crawler", -1); + http_request_submit(req); + + for (;;) { + + HTTP_RequestHandle req; + ret = http_client_wait(client, &req); + if (ret < 0) { + // TODO + return -1; + } + + HTTP_Response *res = http_request_result(req); + if (res == NULL) { + http_request_free(req); + continue; // Request didn't complete + } + HTTP_String body = res->body; + + int cursor = 0; + for (;;) { + + HTTP_String url = next_link(body, &cursor); + if (url.len == 0) break; + + if (is_already_crawled(url)) { + printf("Ignoring " RED "%.*s" RST "\n", (int) url.len, url.ptr); + continue; + } + + printf("Fetching " GRN "%.*s" RST "\n", (int) url.len, url.ptr); + add_to_crawled_list(url); + + HTTP_RequestHandle req; + ret = http_client_request(client, &req); + if (ret < 0) + continue; + + http_request_line(req, HTTP_METHOD_GET, url); + http_request_header(req, "User-Agent: Simple crawler", -1); + http_request_submit(req); + } + } + + http_client_free(client); + http_global_free(); + return 0; +} diff --git a/examples/blocking_server_with_engine.c b/examples/engine/blocking_server.c similarity index 99% rename from examples/blocking_server_with_engine.c rename to examples/engine/blocking_server.c index 6b62a02..df75f9f 100644 --- a/examples/blocking_server_with_engine.c +++ b/examples/engine/blocking_server.c @@ -18,7 +18,7 @@ #define CLOSE_SOCKET close #endif -#include "../tinyhttp.h" +#include // This example showcases how to use the engine interface // to build a blocking HTTP server that works on Windows diff --git a/examples/iocp_server_with_engine.c b/examples/engine/iocp_server.c similarity index 99% rename from examples/iocp_server_with_engine.c rename to examples/engine/iocp_server.c index 8f4d7a0..84de3cd 100644 --- a/examples/iocp_server_with_engine.c +++ b/examples/engine/iocp_server.c @@ -1,8 +1,7 @@ #include #include #include - -#include "../tinyhttp.h" +#include #define MAX_CLIENTS (1<<10) diff --git a/examples/server/000_http_server.c b/examples/server/000_http_server.c new file mode 100644 index 0000000..82bf942 --- /dev/null +++ b/examples/server/000_http_server.c @@ -0,0 +1,95 @@ +#include +#include + +// This example shows how to set up a basic HTTP server + +int main(void) +{ + // Choose the interface to listen on and the port. + // Currently, servers can only bind to IPv4 addresses. + HTTP_String addr = HTTP_STR("127.0.0.1"); + uint16_t port = 8080; + + bool all_interfaces = false; + + // If you want to bind to all interfaces, you can + // set the address to an empty string. + if (all_interfaces) + addr = HTTP_STR(""); + + // Instanciate the HTTP server object + HTTP_Server *server = http_server_init(addr, port); + if (server == NULL) + return -1; + + // Now we loop forever. Every iteration will serve + // a single HTTP request + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + + // Block until a request is available + int ret = http_server_wait(server, &res, &res); + + // The wait functions returns 0 on success and -1 + // on error. By "error" I mean an unrecoverable + // condition. There is no other option than kill + // the process. + if (ret < 0) + return -1; + + // The request information is accessible from + // the [req] variable. Most fields in the request + // struct are reference to the original request + // string. They use type HTTP_String and are not + // null-terminated. This means you'll have to make + // sure to express the length when interacting with + // libc: + HTTP_String path = req->url.path; + printf("requested path [%.*s]\n", (int) path.len, path.ptr); + + // To find a specific header value, you can either + // iterate over the [req->headers] array or use + // a helper function. Note that this compares header + // names case-insensitively. + int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name")); + if (idx == -1) { + // Header wasn't found + } else { + // Found + HTTP_String value = req->headers[idx].value; + printf("Header has value [%.*s]\n", (int) value.len, value.ptr); + } + + // To create a response, you will need to specify + // status code, headers, and content in the proper + // order. + + // First the status code + http_response_status(res, 200); + + // Then zero or more headers + http_response_header(res, "Content-Type: text/plain"); + + // Then you can write zero or more chunks of the response body + http_response_body(res, HTTP_STR("Hello")); + http_response_body(res, HTTP_STR(", world!")); + + // Then, mark the request as complete (Very important or the server will hang!) + http_response_done(res); + + // Note that none of the http_response_* functions return errors. + // This is by design to simplify user endpoint code. If at any point + // something goes wrong, the server will send a code 4xx or 5xx to + // the client or abort the TCP connection entirely. + } + + // This program will loop forever, but if you write + // your server in a way to exit gracefully, this is + // you the server object is freed: + http_server_free(server); + + // Have fun. Bye! + return 0; +} \ No newline at end of file diff --git a/examples/server/010_zero_copy_api.c b/examples/server/010_zero_copy_api.c new file mode 100644 index 0000000..ad442e7 --- /dev/null +++ b/examples/server/010_zero_copy_api.c @@ -0,0 +1,93 @@ +#include + +// This example shows how to generate response bodies +// using the zero-copy API. + +int main(void) +{ + // All the setup is identical to the previous example. + // The only thing that changes where "http_response_body" + // is called. + + HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); + if (server == NULL) + return -1; + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + + int ret = http_server_wait(server, &res, &res); + if (ret < 0) return -1; + + http_response_status(res, 200); + http_response_header(res, "Content-Type: text/plain"); + + // The previous example used the *_body function to + // write the response body in chunks: + // + // http_response_body(res, HTTP_STR("Hello")); + // http_response_body(res, HTTP_STR(", world!")); + // + // This function reads from an user buffer and copies + // the data in the connection's output buffer. If the + // data is not in a contiguous region that's fine as + // the function can be called repeatedly on separate + // chunks. + // + // This function assumes the user is holding in memory + // the data to be sent beforehand, but this may not + // be true. If for instance the data comes from a file, + // the user will need to read from the file, copy in + // memory and then write to the response body. + // + // The zero-copy API allows copying directly from the + // source of the data (such as the read() system call + // on a file descriptor) to the server's output buffer + + char example_data[] = "I'm some example data!"; + int example_data_len = sizeof(example_data)-1; + + // Tell the server how much data we are going to write + http_response_bodycap(res, example_data_len); + + int cap; + char *dst; + + // Get a pointer to the server's output buffer. The + // output parameter [cap] is the capacity of the region + // and is equal or larger than the data we requested + // with *_bodycap + dst = http_response_bodybuf(res, &cap); + + // Write the data directly into the output buffer. In + // this example we are copying from memory, but you could + // read from a file or a socket + if (dst) { + memcpy(dst, example_data, example_data_len); + } + + // Tell the server how much bytes we have written to + // the provided region. + http_response_bodyack(res, example_data_len); + + // The reason we had to guard the [memcpy] by checking the + // [dst] pointer is that if an error occurred internally + // then *_bodybuf will return NULL. This will cause the + // server to either return an internally generated error + // response or drop the connection. The correct thing to + // do in that situation is not access the pointer and do + // as nothing bad happened. + + // As usual, mark the response as complete + http_response_done(res); + + // If we're being being honest, this is not a zero-copy + // interface. It's more like an N-1 copy interface as in + // it just avoids one copy from userspace to userspace! + } + + http_server_free(server); + return 0; +} diff --git a/examples/server/020_response_undo.c b/examples/server/020_response_undo.c new file mode 100644 index 0000000..b468293 --- /dev/null +++ b/examples/server/020_response_undo.c @@ -0,0 +1,55 @@ +#include + +// This example shows how undo a response that is being built +// when an error occurs. + +int main(void) +{ + HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); + if (server == NULL) + return -1; + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + + int ret = http_server_wait(server, &res, &res); + if (ret < 0) return -1; + + // Say we are building a request.. + + http_response_status(res, 200); + http_response_header(res, "Content-Type: text/plain"); + + // .. and in the middle of building an error condition + // occurs. Maybe a file was missing or an allocation fails. + // The proper response in this case would be a code 500 + // with an error message, but we already wrote the first + // part of the response assuming the operation would succede. + // + // You can use the *_undo function to reset the response + // building process + + bool error_occurred = true; + if (error_occurred) { + + http_response_undo(res); + + // Now we are back to setting the status code + http_response_status(res, 500); + http_response_header(res, "Content-Type: text/plain"); + http_response_body(res, HTTP_STR("An error occurred!")); + http_response_done(res); + + } else { + + // If no error occures, we finish as planned + http_response_body(res, HTTP_STR("Hello, world!")); + http_response_done(res); + } + } + + http_server_free(server); + return 0; +} diff --git a/examples/server/030_using_workers.c b/examples/server/030_using_workers.c new file mode 100644 index 0000000..22a4176 --- /dev/null +++ b/examples/server/030_using_workers.c @@ -0,0 +1,213 @@ +#include +#include + +// NOTE: This example doesn't work yet! + +// This example shows how to delegate the response creation +// process to other threads. +// +// Your server may have some endpoints that require considerable +// computation or may be waiting for some external system to +// complete. If we used the current pattern we've been using for +// generating requests, following request will have to wait until +// this processing has concluded. +// +// One solution for this situation is to create a separate thread +// to do the waiting or processing. When a request is received +// that requires processing, it is passed to the second thread. +// In the mean time, the main thread can process the next request. +// When the thread has finished, it can just call the usual +// functions to produce a response. + +// The following types are used to describe a job the worker +// needs to work on. +typedef enum { + + // Special value used to tell the worker the program is terminating + NO_JOB, + + // We assume jobs may be of two different types we call A and B + JOB_A, + JOB_B, + +} JobType; + +typedef struct { + JobType type; + HTTP_ResponseHandle res; +} Job; + +// Maximum number of jobs that can be buffered at once +#define MAX_JOBS 100 + +void init_job_queue(void); +void free_job_queue(void); + +// This function pops an item from the job queue. If the +// queue is empty, the thread will block until one is +// available. +Job pop_job(void); + +// This function adds a job to the queue. The block argument +// changes the behavior when the queue is full and there is +// no space for a new job. If the block argument is true and +// there is no space, the thread waits. If the argument is +// false the function exits immediately by returning false +// with no new job pushed. +bool push_job(Job job, bool block); + +void *worker(void*) +{ + for (bool exit = false; !exit; ) { + + Job job = pop_job(); + + switch (job.type) { + + case NO_JOB: + exit = true; + break; + + case JOB_A: + http_response_status(job.res, 200); + http_response_body(job.res, HTTP_STR("Job A completed")); + http_response_done(job.res); + break; + + case JOB_B: + http_response_status(job.res, 200); + http_response_body(job.res, HTTP_STR("Job B completed")); + http_response_done(job.res); + break; + } + } + + return NULL; +} + +int main(void) +{ + init_job_queue(); + + HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); + if (server == NULL) + return -1; + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + + int ret = http_server_wait(server, &res, &res); + if (ret < 0) return -1; + + if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) { + + // Endpoint A sends the job to the worker. + // If too many jobs are queued, it blocks + + Job job; + job.type = JOB_A; + job.res = res; + push_job(job, true); + + } else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) { + + // Endpoint B sends the job to the worker + // but fails if the queue is full, in which + // case the "503 Service Unavailable" response + // is generated. + + Job job; + job.type = JOB_B; + job.res = res; + if (!push_job(job, false)) { + http_response_status(res, 503); + http_response_done(res); + } + + } else { + + // Other endpoints may resolve immediately + + http_response_status(res, 404); + http_response_done(res); + } + } + + // Stop the worker by sending an empty job + Job job; + job.type = NO_JOB; + push_job(job, true); + + http_server_free(server); + free_job_queue(); + return 0; +} + +////////////////////////////////////////////// + +// This is a pretty standard condition variable-based +// producer-consumer queue. In this example we are using +// one worker, but we could easily have more than that. + +Job queue[MAX_JOBS]; +int queue_head = 0; +int queue_count = 0; +Mutex queue_lock; +Condvar queue_consume_event; +Condvar queue_produce_event; + +void init_job_queue(void) +{ + mutex_init(&queue_lock); + condvar_init(&queue_consume_event); + condvar_init(&queue_produce_event); +} + +void free_job_queue(void) +{ + condvar_free(&queue_produce_event); + condvar_free(&queue_consume_event); + mutex_free(&queue_lock); +} + +Job pop_job(void) +{ + mutex_lock(&queue_lock); + + while (queue_count == 0); + condvar_wait(&queue_produce_event, &queue_lock, -1); + + Job job = queue[queue_head]; + queue_head = (queue_head + 1) % MAX_JOBS; + queue_count--; + + condvar_signal(&queue_consume_event); + mutex_unlock(&queue_lock); + return job; +} + +bool push_job(Job job, bool block) +{ + mutex_lock(&queue_lock); + if (queue_count == 0) { + + if (!block) { + mutex_unlock(&queue_lock); + return false; + } + + do + condvar_wait(&queue_consume_event, &queue_lock, -1); + while (queue_count == 0); + } + + int tail = (queue_head + queue_count) % MAX_JOBS; + queue[tail] = job; + queue_count++; + + condvar_signal(&queue_produce_event); + mutex_unlock(&queue_lock); + return true; +} diff --git a/examples/server/040_virtual_hosts.c b/examples/server/040_virtual_hosts.c new file mode 100644 index 0000000..e69de29 diff --git a/examples/server/050_https_server.c b/examples/server/050_https_server.c new file mode 100644 index 0000000..9fd333d --- /dev/null +++ b/examples/server/050_https_server.c @@ -0,0 +1,74 @@ +#include +#include + +// This example shows how to set up an HTTPS (HTTP over TLS) +// server. + +int main(void) +{ + // To setup an HTTPS server, we need to use the *_ex variant + // of the server initialization function as it offers more + // control. Server objects can serve HTTP traffic, HTTPS + // traffic, or both at the same time. The init_ex function + // allows us to control this behavior. + + // The first argument is the local interface address. It + // works just as the other examples but is shared between + // HTTP and HTTPS. Then come the HTTP port and HTTPS port + // arguments. If you want to disable HTTP or HTTPS you can + // pass zero to its port argument. If the HTTPS port is + // not zero, you need to pass the file names of the server's + // certificate and private key. + HTTP_Server *server = http_server_init_ex( + HTTP_STR("127.0.0.1"), // HTTP and HTTPS port + 8080, // HTTP port + 8443, // HTTPS port + HTTP_STR("cert.pem"), + HTTP_STR("privkey.pem") + ); + if (server == NULL) + return -1; + + // Just to be clear, to initialize a plain HTTP server + // using the *_ex function we would do this: + // + // HTTP_Server *server = http_server_init_ex( + // HTTP_STR("127.0.0.1"), // HTTP and HTTPS port + // 8080, // HTTP port + // 0, // HTTPS disabled + // HTTP_STR(""), // ignore + // HTTP_STR("") // ignore + // ); + // + // and if we wanted and HTTPS-only server we would + // do this: + // + // HTTP_Server *server = http_server_init_ex( + // HTTP_STR("127.0.0.1"), // HTTP and HTTPS port + // 0, // HTTP disabled + // 8443, // HTTPS port + // HTTP_STR("cert.pem"), + // HTTP_STR("privkey.pem") + // ); + + // Everything else is identical to the simple HTTP server + // example. + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + + int ret = http_server_wait(server, &res, &res); + if (ret < 0) return -1; + + http_response_status(res, 200); + http_response_header(res, "Content-Type: text/plain"); + http_response_body(res, HTTP_STR("Hello")); + http_response_body(res, HTTP_STR(", world!")); + http_response_done(res); + } + + http_server_free(server); + return 0; +} \ No newline at end of file diff --git a/examples/server/060_virtual_hosts_over_https.c b/examples/server/060_virtual_hosts_over_https.c new file mode 100644 index 0000000..44d2520 --- /dev/null +++ b/examples/server/060_virtual_hosts_over_https.c @@ -0,0 +1,154 @@ +#include + +// This is an example of how to serve different websites +// over a single HTTPS server instance. + +int setup_test_certificates(void); + +int main(void) +{ + // First, create three certificates for the domains: + // + // websiteA.com + // websiteB.com + // websiteC.com + // + // This will create a number of certificate files + // and private key files + // + // websiteA_cert.pem websiteA_key.pem + // websiteB_cert.pem websiteB_key.pem + // websiteC_cert.pem websiteC_key.pem + // + // Of course this is just for testing. It is expected + // you have your own. + int ret = setup_test_certificates(); + if (ret < 0) + return -1; + + // First, set up an HTTPS server instance with one + // of the certificate. This will act as default certificate + // when ecrypted connections don't target a specific domain. + HTTP_Server *server = http_server_init( + HTTP_STR("127.0.0.1"), 8080, 8443, + HTTP_STR("websiteA_cert.pem"), + HTTP_STR("websiteA_key.pem") + ); + if (server == NULL) + return -1; + + // Then we can add an arbitrary number of additional + // certificates using the add_website function + + ret = http_server_add_website(server, + HTTP_STR("websiteB_cert.pem"), + HTTP_STR("websiteB_key.pem") + ); + if (ret < 0) + return -1; + + ret = http_server_add_website(server, + HTTP_STR("websiteC_cert.pem"), + HTTP_STR("websiteC_key.pem") + ); + if (ret < 0) + return -1; + + // Now the server is ready to accept incoming HTTP + // or HTTPS connections. + // + // Note that the add_website function is only used + // to serve the correct certificate to the client. + // The HTTP request itself may very well hold a + // different domain name in the host header: + // + // [client] [server] + // | | + // | TLS hanshake to domain1.com | + // | -------------------------------> | + // | | + // | cert for domain1.com | + // | <------------------------------- | + // | | + // | HTTP request to domain2.com | + // | over the encrypted connection | + // | established with domain1.com | + // | -------------------------------> | + // | | + // | response as domain2.com | + // | <------------------------------- | + // | | + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseHandle res; + ret = http_server_wait(server, &req, &res); + if (ret < 0) + break; + + int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); + HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected + HTTP_String host = req->headers[idx].value; + + if (http_streq(host, HTTP_STR("websiteB.com"))) { + + http_response_status(res, 200); + http_response_body(res, "Hello from websiteB.com!"); + http_response_done(res); + + } else if (http_streq(host, HTTP_STR("websiteC.com"))) { + + http_response_status(res, 200); + http_response_body(res, "Hello from websiteC.com!"); + http_response_done(res); + + } else { + + // Serve websiteA.com by default to be consistent + // with the certificate setup + + http_response_status(res, 200); + http_response_body(res, "Hello from websiteA.com!"); + http_response_done(res); + } + } + + http_server_free(server); + return 0; +} + +int setup_test_certificates(void) +{ + int ret = http_create_test_certificate( + HTTP_STR("IT"), + HTTP_STR("Organization A"), + HTTP_STR("websiteA.com"), + HTTP_STR("websiteA_cert.pem"), + HTTP_STR("websiteA_key.pem") + ); + if (ret < 0) + return -1; + + ret = http_create_test_certificate( + HTTP_STR("IT"), + HTTP_STR("Organization B"), + HTTP_STR("websiteB.com"), + HTTP_STR("websiteB_cert.pem"), + HTTP_STR("websiteB_key.pem") + ); + if (ret < 0) + return -1; + + ret = http_create_test_certificate( + HTTP_STR("IT"), + HTTP_STR("Organization C"), + HTTP_STR("websiteC.com"), + HTTP_STR("websiteC_cert.pem"), + HTTP_STR("websiteC_key.pem") + ); + if (ret < 0) + return -1; + + return 0; +} diff --git a/examples/simple_proxy.c b/examples/simple_proxy.c deleted file mode 100644 index 342e967..0000000 --- a/examples/simple_proxy.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include "../tinyhttp.h" - -int main(void) -{ - HTTP_Proxy proxy; - int ret = http_proxy_init(&proxy, "127.0.0.1", 8080); - if (ret < 0) { - printf("http_proxy_init failed\n"); - return -1; - } - - for (;;) - http_proxy_wait(&proxy, -1); - - http_proxy_free(&proxy); - return 0; -} diff --git a/examples/simple_server.c b/examples/simple_server.c deleted file mode 100644 index 9485d1f..0000000 --- a/examples/simple_server.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include "../tinyhttp.h" - -// This is an example of how to use the TinyHTTP simplified -// server API to build a cross-platform HTTP server. - -int main(void) -{ - HTTP_Server server; - - // Initialize a server listening on the given interface - // and with the given port. - int ret = http_server_init(&server, "127.0.0.1", 8080); - if (ret < 0) { - printf("http_server_init failed\n"); - return -1; - } - - for (;;) { - - // Set for how many milliseconds the server will wait for - // requests this iteration. If -1, the function will not - // timeout. - int timeout_ms = 1000; - - HTTP_Request *req; - HTTP_ResponseHandle res; - ret = http_server_wait(&server, &req, &res, timeout_ms); - - if (ret == 0) - continue; // Timeout - - if (ret < 0) - break; // An unrecoverable error occurred - - // You can access the request data through the - // "req" pointer. For this example, we only allow - // GET requests to the "/hello" endpoint - - if (req->method != HTTP_METHOD_GET) { - - // Respond with the status code 405 - http_response_status(res, 405); - - // Mark the response as complete. If you don't - // call this, the client will just hang! - http_response_done(res); - - // Go back to waiting - continue; - } - - // Compare the requested resource with "/hello". - // The HTTP_STR macro can be used on string literals to - // get the length automatically. It is equivalent to: - // - // (HTTP_String) { literal, sizeof(literal)-1 } - // - if (!http_streq(req->url.path, HTTP_STR("/hello"))) { - - // Some other resource was requested - http_response_status(res, 404); - http_response_done(res); - continue; - } - - // Now we send the success response - http_response_status(res, 200); - - // Set zero or more headers - // You must pass a string in the form: - // - // : - // - // It's important you don't use the \r character - // and there are no spaces before the ':' character. - // - // You should avoid adding the "Connection", - // "Transfer-Encoding", or "Content-Length" headers - // since they are added automatically. - http_response_header(res, "first-header: %d", 99); - http_response_header(res, "second-header: %s", "Some string"); - - // After having set any headers, we can optionally - // add some content to the request - - // Add some bytes to the payload in terms of a pointer - // and length pair. If the length is -1, the bytes are - // assumed to be null-terminated. - http_response_body(res, "Hello, world!", -1); - - // Now let's say we are in the middle of building a - // response and an error occurres. In that case, we - // can undo all the progress since the first status - // call and start all over: - int error = rand() & 1; - if (error) { - http_response_undo(res); - http_response_status(res, 500); - http_response_done(res); - continue; - } - - // Let's add some more bytes - http_response_body(res, " How's it going??", -1); - - // Ok. Done! - http_response_done(res); - } - - http_server_free(&server); - return 0; -} \ No newline at end of file diff --git a/tinyhttp.c b/http.c similarity index 52% rename from tinyhttp.c rename to http.c index 37f419b..9b3f54f 100644 --- a/tinyhttp.c +++ b/http.c @@ -1,14 +1,160 @@ -#include "tinyhttp.h" +/* + * HTTP Library - Amalgamated Source + * Generated automatically - do not edit manually + */ -#include +#include "http.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include #include -#include +#include +#include +#include -#define ASSERT(X) {if (!(X)) __builtin_trap();} -#define UNREACHABLE __builtin_trap(); +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file); + +// This is a socket abstraction module for non-blocking TCP and TLS sockets. +// +// Sockets may be in a number of states based on if they are plain TCP or TLS +// sockets. Users generally only care about when the connection is established +// or is terminated. +// +// Sockets can be created by connecting to a server using one of these: +// +// socket_connect +// socket_connect_ipv4 +// socket_connect_ipv6 +// +// They allow connecting to a remote host by specifying its name, of IP address. +// Or by interning a socket accepted by a listening socket: +// +// socket_accept +// +// after creation, the event field will hold one of the values: +// +// SOCKET_WANT_READ +// SOCKET_WANT_WRITE +// +// Which respectively mean that the socket object needs to read or write +// from the underlying socket, and to do so non-blockingly, the caller needs +// to wait for the socket being ready for that operation. This is one way +// to do it: +// +// // Translate the socket event field to poll() flags +// int events; +// if (sock.event == SOCKET_WANT_READ) +// events = POLLIN; +// else if (sock.event == SOCKET_WANT_WRITE) +// events = POLLOUT; +// +// // block until the socket is ready +// struct pollfd buf; +// buf.fd = sock.fd; +// buf.events = events; +// buf.revents = 0; +// poll(&buf, 1, -1); +// +// whenever a socket is ready, the user must call the socket_update +// function. Then, if the socket is in the SOCKET_STATE_ESTABLISHED_READY +// state, the user can call one of +// +// socket_close +// socket_read +// socket_write +// +// At any point the socket could reach the SOCKET_STATE_DIED state, +// which means the user needs to call socket_free to free the socket +// as it's not unusable. + + + +typedef struct { + int is_ipv6; + union { + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + } addr; +} AddrInfo; + +typedef enum { + SOCKET_STATE_PENDING, + SOCKET_STATE_CONNECTING, + SOCKET_STATE_CONNECTED, + SOCKET_STATE_ACCEPTED, + SOCKET_STATE_ESTABLISHED_WAIT, + SOCKET_STATE_ESTABLISHED_READY, + SOCKET_STATE_SHUTDOWN, + SOCKET_STATE_DIED +} SocketState; + +typedef enum { + SOCKET_WANT_NONE, + SOCKET_WANT_READ, + SOCKET_WANT_WRITE, +} SocketWantEvent; + +typedef struct { + SocketState state; + SocketWantEvent event; + int fd; + SSL *ssl; + SSL_CTX *ssl_ctx; + AddrInfo *addr_list; + int addr_count; + int addr_cursor; + char *hostname; + uint16_t port; +} Socket; + +typedef struct { + char name[128]; + SSL_CTX *ssl_ctx; +} Domain; + +typedef struct { + SSL_CTX *ssl_ctx; + int num_domains; + int max_domains; + Domain *domains; +} SocketGroup; + +void socket_global_init (void); +void socket_global_free (void); +int socket_group_init (SocketGroup *group); +int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file); +int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_key, HTTP_String private_key); +void socket_group_free (SocketGroup *group); +SocketState socket_state (Socket *sock); +void socket_accept (Socket *sock, SocketGroup *group, int fd); +void socket_connect (Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port); +void socket_connect_ipv4 (Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port); +void socket_connect_ipv6 (Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port); +void socket_update (Socket *sock); +int socket_read (Socket *sock, char *dst, int max); +int socket_write (Socket *sock, char *src, int len); +void socket_close (Socket *sock); +void socket_free (Socket *sock); +int socket_wait (Socket **socks, int num_socks); int http_streq(HTTP_String s1, HTTP_String s2) { @@ -56,11 +202,52 @@ HTTP_String http_trim(HTTP_String s) return s; } -///////////////////////////////////////////////////////////////////// -// HTTP PARSER -///////////////////////////////////////////////////////////////////// +static bool is_printable(char c) +{ + return c >= ' ' && c <= '~'; +} - // From RFC 9112 +void print_bytes(HTTP_String prefix, HTTP_String src) +{ + if (src.len == 0) + return; + + FILE *stream = stdout; + + bool new_line = true; + int cur = 0; + for (;;) { + int start = cur; + + while (cur < src.len && is_printable(src.ptr[cur])) + cur++; + + if (new_line) { + fwrite(prefix.ptr, 1, prefix.len, stream); + new_line = false; + } + + fwrite(src.ptr + start, 1, cur - start, stream); + + if (cur == src.len) + break; + + if (src.ptr[cur] == '\n') { + putc('\\', stream); + putc('n', stream); + putc('\n', stream); + new_line = true; + } else if (src.ptr[cur] == '\r') { + putc('\\', stream); + putc('r', stream); + } else { + putc('.', stream); + } + cur++; + } + putc('\n', stream); +} +// From RFC 9112 // request-target = origin-form // / absolute-form // / authority-form @@ -936,7 +1123,7 @@ static int parse_request(Scanner *s, HTTP_Request *req) return 1; } -static int find_header(HTTP_Header *headers, int num_headers, HTTP_String name) +int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name) { for (int i = 0; i < num_headers; i++) if (http_streqcase(name, headers[i].name)) @@ -1000,7 +1187,7 @@ static int parse_response(Scanner *s, HTTP_Response *res) return num_headers; res->num_headers = num_headers; - int content_length_index = find_header( + int content_length_index = http_find_header( res->headers, res->num_headers, HTTP_STR("Content-Length")); if (content_length_index == -1) { @@ -1015,11 +1202,11 @@ static int parse_response(Scanner *s, HTTP_Response *res) unsigned long long content_length; if (parse_content_length(content_length_str.ptr, content_length_str.len, &content_length) < 0) { - ASSERT(0); // TODO + HTTP_ASSERT(0); // TODO } if (content_length > 1<<20) { - ASSERT(0); // TODO + HTTP_ASSERT(0); // TODO } if (content_length > (unsigned long long) (s->len - s->cur)) @@ -1073,6 +1260,12 @@ int http_parse_response(char *src, int len, HTTP_Response *res) return ret; } +HTTP_String http_getqueryparam(HTTP_Request *req, HTTP_String name) +{ + // TODO + return (HTTP_String) {NULL, 0}; +} + HTTP_String http_getbodyparam(HTTP_Request *req, HTTP_String name) { // TODO @@ -1084,12 +1277,6 @@ HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) // TODO return (HTTP_String) {NULL, 0}; } - -///////////////////////////////////////////////////////////////////// -// HTTP BYTE QUEUE -///////////////////////////////////////////////////////////////////// -#if HTTP_ENGINE - // This is the implementation of a byte queue useful // for systems that need to process engs of bytes. // @@ -1183,7 +1370,7 @@ byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) return NULL; } - ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); + HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); queue->flags |= BYTE_QUEUE_READ; queue->read_target = queue->data; queue->read_target_size = queue->size; @@ -1198,7 +1385,7 @@ byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) static void byte_queue_read_ack(HTTP_ByteQueue *queue, int num) { - ASSERT(num >= 0); + HTTP_ASSERT(num >= 0); if (queue->flags & BYTE_QUEUE_ERROR) return; @@ -1208,7 +1395,7 @@ byte_queue_read_ack(HTTP_ByteQueue *queue, int num) queue->flags &= ~BYTE_QUEUE_READ; - ASSERT((unsigned int) num <= queue->used); + HTTP_ASSERT((unsigned int) num <= queue->used); queue->head += (unsigned int) num; queue->used -= (unsigned int) num; queue->curs += (unsigned int) num; @@ -1229,7 +1416,7 @@ byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) return NULL; } - ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); + HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); queue->flags |= BYTE_QUEUE_WRITE; unsigned int ucap = queue->size - (queue->head + queue->used); @@ -1242,7 +1429,7 @@ byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) static void byte_queue_write_ack(HTTP_ByteQueue *queue, int num) { - ASSERT(num >= 0); + HTTP_ASSERT(num >= 0); if (queue->flags & BYTE_QUEUE_ERROR) return; @@ -1276,7 +1463,7 @@ byte_queue_write_ack(HTTP_ByteQueue *queue, int num) static int byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) { - ASSERT(mincap >= 0); + HTTP_ASSERT(mincap >= 0); unsigned int umincap = (unsigned int) mincap; // Sticky error @@ -1323,7 +1510,7 @@ byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) // we just avoid that. Even if there is enough memory considering // left and right free regions, we allocate a new buffer. - ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); + HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); unsigned int total_free_space = queue->size - queue->used; unsigned int free_space_after_data = queue->size - queue->used - queue->head; @@ -1401,10 +1588,10 @@ byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, return; // Check that the offset is in range - ASSERT(off >= queue->curs && off - queue->curs < queue->used); + HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used); // Check that the length is in range - ASSERT(len <= queue->used - (off - queue->curs)); + HTTP_ASSERT(len <= queue->used - (off - queue->curs)); // Perform the patch char *dst = queue->data + queue->head + (off - queue->curs); @@ -1418,7 +1605,7 @@ byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset return; unsigned long long num = (queue->curs + queue->used) - offset; - ASSERT(num <= queue->used); + HTTP_ASSERT(num <= queue->used); queue->used -= num; } @@ -1426,6 +1613,7 @@ byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset static void byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len) { + if (str == NULL) str = ""; if (len < 0) len = strlen(str); int cap; @@ -1478,16 +1666,6 @@ byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) va_end(args); } -#endif // HTTP_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP ENGINE -///////////////////////////////////////////////////////////////////// -#if HTTP_ENGINE - -#if !HTTP_PARSE -#error "HTTP_ENGINE depends on HTTP_PARSE" -#endif - #define TEN_SPACES " " void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) @@ -1524,6 +1702,34 @@ HTTP_EngineState http_engine_state(HTTP_Engine *eng) return eng->state; } +const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove + switch (state) { + case HTTP_ENGINE_STATE_NONE: return "NONE"; + case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL"; + case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR"; + case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF"; + case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK"; + case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF"; + case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK"; + case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY"; + case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED"; + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF"; + case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS"; + case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR"; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF"; + case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK"; + case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED"; + default: return "UNKNOWN"; + } +} + char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) { if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) { @@ -1550,7 +1756,7 @@ char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) static int should_keep_alive(HTTP_Engine *eng) { - ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); + HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); #if 0 // If the parent system doesn't want us to reuse @@ -1571,7 +1777,7 @@ should_keep_alive(HTTP_Engine *eng) // TODO: This assumes "Connection" can only hold a single token, // but this is not true. - int i = find_header(req->headers, req->num_headers, HTTP_STR("Connection")); + int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection")); if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) return 0; @@ -1580,7 +1786,7 @@ should_keep_alive(HTTP_Engine *eng) static void process_incoming_request(HTTP_Engine *eng) { - ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK + HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); @@ -1614,7 +1820,7 @@ static void process_incoming_request(HTTP_Engine *eng) return; } - ASSERT(ret > 0); + HTTP_ASSERT(ret > 0); eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; eng->reqsize = ret; @@ -1648,7 +1854,7 @@ void http_engine_recvack(HTTP_Engine *eng, int num) return; } - ASSERT(ret > 0); + HTTP_ASSERT(ret > 0); eng->state = HTTP_ENGINE_STATE_CLIENT_READY; @@ -1708,18 +1914,16 @@ HTTP_Response *http_engine_getres(HTTP_Engine *eng) return &eng->result.res; } -void http_engine_url(HTTP_Engine *eng, HTTP_Method method, char *url, int minor) +void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor) { if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) return; eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients - int len = strlen(url); - HTTP_URL parsed_url; - int ret = http_parse_url(url, len, &parsed_url); - if (ret != len) { + int ret = http_parse_url(url.ptr, url.len, &parsed_url); + if (ret != url.len) { eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR; return; } @@ -2057,1523 +2261,1572 @@ void http_engine_undo(HTTP_Engine *eng) else eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; } - -#endif // HTTP_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP PROXY ENGINE -///////////////////////////////////////////////////////////////////// -#if HTTP_PROXY_ENGINE - -#if !HTTP_ENGINE -#error "HTTP_PROXY_ENGINE depends on HTTP_ENGINE" -#endif - -void http_proxyengine_init(HTTP_ProxyEngine *eng, HTTP_MemoryFunc memfunc, void *memfuncdata) +void socket_global_init(void) { - http_engine_init(&eng->in, 0, memfunc, memfuncdata); - http_engine_init(&eng->out, 1, memfunc, memfuncdata); - - ASSERT(http_engine_state(&eng->in) == HTTP_ENGINE_STATE_SERVER_RECV_BUF); - ASSERT(http_engine_state(&eng->out) == HTTP_ENGINE_STATE_CLIENT_PREP_URL); + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); } -void http_proxyengine_free(HTTP_ProxyEngine *eng) +void socket_global_free(void) { - http_engine_free(&eng->in); - http_engine_free(&eng->out); + EVP_cleanup(); + ERR_free_strings(); } -HTTP_ProxyEngineState http_proxyengine_state(HTTP_ProxyEngine *eng) +int socket_group_init(SocketGroup *group) { - HTTP_EngineState istate = http_engine_state(&eng->in); - HTTP_EngineState ostate = http_engine_state(&eng->out); - if (istate == HTTP_ENGINE_STATE_SERVER_RECV_BUF && ostate == HTTP_ENGINE_STATE_CLIENT_PREP_URL) return HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_BUF; - if (istate == HTTP_ENGINE_STATE_SERVER_RECV_ACK && ostate == HTTP_ENGINE_STATE_CLIENT_PREP_URL) return HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_ACK; - if (istate == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && ostate == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) return HTTP_PROXY_ENGINE_STATE_SERVER_SEND_BUF; - if (istate == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && ostate == HTTP_ENGINE_STATE_CLIENT_SEND_ACK) return HTTP_PROXY_ENGINE_STATE_SERVER_SEND_ACK; - if (istate == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && ostate == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) return HTTP_PROXY_ENGINE_STATE_SERVER_RECV_BUF; - if (istate == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && ostate == HTTP_ENGINE_STATE_CLIENT_RECV_ACK) return HTTP_PROXY_ENGINE_STATE_SERVER_RECV_ACK; - if (istate == HTTP_ENGINE_STATE_SERVER_SEND_BUF && ostate == HTTP_ENGINE_STATE_CLIENT_PREP_URL) return HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_BUF; - if (istate == HTTP_ENGINE_STATE_SERVER_SEND_ACK && ostate == HTTP_ENGINE_STATE_CLIENT_PREP_URL) return HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_ACK; - ASSERT(istate == HTTP_ENGINE_STATE_SERVER_CLOSED || ostate == HTTP_ENGINE_STATE_CLIENT_CLOSED); - return HTTP_PROXY_ENGINE_STATE_CLOSED; + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Set certificate verification mode + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + + // Load default trusted certificate store + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + fprintf(stderr, "Failed to set default verify paths\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + group->ssl_ctx = ssl_ctx; + group->domains = NULL; + group->num_domains = 0; + group->max_domains = 0; + return 0; } -void http_proxyengine_close(HTTP_ProxyEngine *eng) +static int servername_callback(SSL *ssl, int *ad, void *arg) { - http_engine_close(&eng->in); - http_engine_close(&eng->out); + SocketGroup *group = arg; + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL) + return SSL_TLSEXT_ERR_NOACK; + + for (int i = 0; i < group->num_domains; i++) { + Domain *domain = &group->domains[i]; + if (!strcmp(domain->name, servername)) { + SSL_set_SSL_CTX(ssl, domain->ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_NOACK; } -char *http_proxyengine_serverrecvbuf(HTTP_ProxyEngine *eng, int *cap) +int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file) { - return http_engine_recvbuf(&eng->in, cap); + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create server SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Copy certificate file path to static buffer + static char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + fprintf(stderr, "Certificate file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + static char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + fprintf(stderr, "Private key file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load private key file: %s\n", key_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + fprintf(stderr, "Private key does not match certificate\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + SSL_CTX_set_tlsext_servername_callback(group->ssl_ctx, servername_callback); + SSL_CTX_set_tlsext_servername_arg(group->ssl_ctx, group); + + group->ssl_ctx = ssl_ctx; + group->domains = NULL; + group->num_domains = 0; + group->max_domains = 0; + return 0; } -static void forward_request(HTTP_ProxyEngine *eng) +void socket_group_free(SocketGroup *group) { - if (http_engine_state(&eng->in) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && - http_engine_state(&eng->out) == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { - - HTTP_Request *req = http_engine_getreq(&eng->in); - - char url[] = "???"; - http_engine_url(&eng->out, req->method, url, 1); - - for (int i = 0; i < req->num_headers; i++) - http_engine_header_fmt(&eng->out, "%.*s: %.*s", - req->headers[i].name.len, req->headers[i].name.ptr, - req->headers[i].value.len, req->headers[i].value.ptr - ); - - http_engine_body(&eng->out, req->body.ptr, req->body.len); - http_engine_done(&eng->out); - } + SSL_CTX_free(group->ssl_ctx); } -static void forward_response(HTTP_ProxyEngine *eng) +int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { - if (http_engine_state(&eng->in) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS && - http_engine_state(&eng->out) == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { - - HTTP_Response *res = http_engine_getres(&eng->out); + if (group->num_domains == group->max_domains) { - http_engine_status(&eng->in, res->status); + int new_max_domains = 2 * group->max_domains; + if (new_max_domains == 0) + new_max_domains = 4; - for (int i = 0; i < res->num_headers; i++) - http_engine_header_fmt(&eng->in, "%.*s: %.*s", - res->headers[i].name.len, res->headers[i].name.ptr, - res->headers[i].value.len, res->headers[i].value.ptr - ); + Domain *new_domains = malloc(new_max_domains * sizeof(Domain)); + if (new_domains == NULL) + return -1; - http_engine_body(&eng->in, res->body.ptr, res->body.len); - http_engine_done(&eng->in); - } + if (group->max_domains > 0) { + for (int i = 0; i < group->num_domains; i++) + new_domains[i] = group->domains[i]; + free(group->domains); + } + + group->domains = new_domains; + group->max_domains = new_max_domains; + } + + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create server SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Copy certificate file path to static buffer + static char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + fprintf(stderr, "Certificate file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + static char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + fprintf(stderr, "Private key file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load private key file: %s\n", key_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + fprintf(stderr, "Private key does not match certificate\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + Domain *domain_info = &group->domains[group->num_domains]; + if (domain.len >= (int) sizeof(domain_info->name)) { + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(domain_info->name, domain.ptr, domain.len); + domain_info->name[domain.len] = '\0'; + domain_info->ssl_ctx = ssl_ctx; + group->num_domains++; + return 0; } -void http_proxyengine_serverrecvack(HTTP_ProxyEngine *eng, int num) +SocketState socket_state(Socket *sock) { - http_engine_recvack(&eng->in, num); - forward_request(eng); + return sock->state; } -char *http_proxyengine_serversendbuf(HTTP_ProxyEngine *eng, int *len) +void socket_accept(Socket *sock, SocketGroup *group, int fd) { - return http_engine_sendbuf(&eng->in, len); + // Initialize socket for server-side TLS handshake + sock->state = SOCKET_STATE_ACCEPTED; // TCP connection already established + sock->event = SOCKET_WANT_NONE; + sock->fd = fd; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = 0; + + // Set non-blocking mode for the accepted socket + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) { + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + + // Start the TLS handshake process + socket_update(sock); } -void http_proxyengine_serversendack(HTTP_ProxyEngine *eng, int num) -{ - http_engine_sendack(&eng->in, num); - forward_request(eng); +void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->port = port; + sock->hostname = (char*)malloc(host.len + 1); + memcpy(sock->hostname, host.ptr, host.len); + sock->hostname[host.len] = '\0'; + // DNS query + struct addrinfo hints = {0}, *res = NULL, *rp = NULL; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%u", port); + if (getaddrinfo(sock->hostname, portstr, &hints, &res) != 0) { + sock->state = SOCKET_STATE_DIED; + return; + } + // Count addresses + int count = 0; + for (rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) count++; + } + if (count == 0) { + freeaddrinfo(res); + sock->state = SOCKET_STATE_DIED; + return; + } + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo) * count); + sock->addr_count = count; + sock->addr_cursor = 0; + int i = 0; + for (rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + sock->addr_list[i].is_ipv6 = 0; + memcpy(&sock->addr_list[i].addr.ipv4, &((struct sockaddr_in*)rp->ai_addr)->sin_addr, sizeof(HTTP_IPv4)); + i++; + } else if (rp->ai_family == AF_INET6) { + sock->addr_list[i].is_ipv6 = 1; + memcpy(&sock->addr_list[i].addr.ipv6, &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr, sizeof(HTTP_IPv6)); + i++; + } + } + freeaddrinfo(res); + // Set event/state and call update + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); } -char *http_proxyengine_clientrecvbuf(HTTP_ProxyEngine *eng, int *cap) -{ - return http_engine_recvbuf(&eng->out, cap); +void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = port; + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo)); + sock->addr_list[0].is_ipv6 = 0; + memcpy(&sock->addr_list[0].addr.ipv4, &addr, sizeof(HTTP_IPv4)); + sock->addr_count = 1; + sock->addr_cursor = 0; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); } -void http_proxyengine_clientrecvack(HTTP_ProxyEngine *eng, int num) -{ - http_engine_recvack(&eng->out, num); - forward_response(eng); +void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = port; + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo)); + sock->addr_list[0].is_ipv6 = 1; + memcpy(&sock->addr_list[0].addr.ipv6, &addr, sizeof(HTTP_IPv6)); + sock->addr_count = 1; + sock->addr_cursor = 0; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); } -char *http_proxyengine_clientsendbuf(HTTP_ProxyEngine *eng, int *len) +void socket_update(Socket *sock) { - return http_engine_sendbuf(&eng->out, len); + sock->event = SOCKET_WANT_NONE; + + bool again; + do { + + again = false; + + switch (sock->state) { + case SOCKET_STATE_PENDING: + { + if (sock->ssl) { + SSL_free(sock->ssl); + sock->ssl = NULL; + } + + if (sock->fd != -1) + close(sock->fd); + + // If cursor reached the end, die + if (sock->addr_cursor >= sock->addr_count) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + break; + } + + // Take current address + AddrInfo *ai = &sock->addr_list[sock->addr_cursor]; + int family = ai->is_ipv6 ? AF_INET6 : AF_INET; + int fd = socket(family, SOCK_STREAM, 0); + if (fd < 0) { + // Try next address + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + break; + } + + // Set non-blocking + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) fcntl(fd, F_SETFL, flags | O_NONBLOCK); // TODO: Handle error by setting the socket to DIED + + // Prepare sockaddr + int ret; + if (ai->is_ipv6) { + struct sockaddr_in6 sa6 = {0}; + sa6.sin6_family = AF_INET6; + memcpy(&sa6.sin6_addr, &ai->addr.ipv6, sizeof(HTTP_IPv6)); + sa6.sin6_port = htons(sock->port); + ret = connect(fd, (struct sockaddr*)&sa6, sizeof(sa6)); + } else { + struct sockaddr_in sa4 = {0}; + sa4.sin_family = AF_INET; + memcpy(&sa4.sin_addr, &ai->addr.ipv4, sizeof(HTTP_IPv4)); + sa4.sin_port = htons(sock->port); + ret = connect(fd, (struct sockaddr*)&sa4, sizeof(sa4)); + } + + if (ret == 0) { + // Connected immediately + sock->fd = fd; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_CONNECTED; + again = true; + break; + } + + if (ret < 0 && errno == EINPROGRESS) { + // Connection pending + sock->fd = fd; + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_CONNECTING; + break; + } + + // Connect failed + // If remote peer not working, try next address + if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == ENETUNREACH || errno == EHOSTUNREACH) { + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } else { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + case SOCKET_STATE_CONNECTING: + { + // Check connect result + int err = 0; + socklen_t len = sizeof(err); + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0 || err != 0) { + close(sock->fd); + // If remote peer not working, try next address + if (err == ECONNREFUSED || err == ETIMEDOUT || err == ENETUNREACH || err == EHOSTUNREACH) { + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } else { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + break; + } + + // Connect succeeded + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_CONNECTED; + again = true; + break; + } + break; + + case SOCKET_STATE_CONNECTED: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } else { + + // Start SSL handshake + if (!sock->ssl) { + sock->ssl = SSL_new(sock->ssl_ctx); + SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error? + if (sock->hostname) SSL_set_tlsext_host_name(sock->ssl, sock->hostname); + } + + int ret = SSL_connect(sock->ssl); + if (ret == 1) { + // Handshake done + free(sock->addr_list); sock->addr_list = NULL; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } + } + break; + + case SOCKET_STATE_ACCEPTED: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } else { + + // Start server-side SSL handshake + if (!sock->ssl) { + sock->ssl = SSL_new(sock->ssl_ctx); + SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error? + } + + int ret = SSL_accept(sock->ssl); + if (ret == 1) { + // Handshake done + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + // Server socket error - close the connection + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + case SOCKET_STATE_ESTABLISHED_WAIT: + { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } + break; + + case SOCKET_STATE_SHUTDOWN: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } else { + + int ret = SSL_shutdown(sock->ssl); + if (ret == 1) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + default: + // Do nothing + break; + } + + } while (again); } -void http_proxyengine_clientsendack(HTTP_ProxyEngine *eng, int num) -{ - http_engine_sendack(&eng->out, num); - forward_response(eng); +int socket_read(Socket *sock, char *dst, int max) { + // If not ESTABLISHED, set state to DIED and return + if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + return -1; + } + + if (sock->ssl_ctx == NULL) { + int ret = read(sock->fd, dst, max); + if (ret == 0) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } else { + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + if (errno != EINTR) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + ret = 0; + } + } + return ret; + } else { + int ret = SSL_read(sock->ssl, dst, max); + if (ret <= 0) { + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + fprintf(stderr, "OpenSSL error in socket_read: "); + ERR_print_errors_fp(stderr); + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + ret = 0; + } + return ret; + } } -#endif // HTTP_PROXY_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP CLIENT AND SERVER -///////////////////////////////////////////////////////////////////// -#if HTTP_CLIENT || HTTP_SERVER +int socket_write(Socket *sock, char *src, int len) { + // If not ESTABLISHED, set state to DIED and return + if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + return 0; + } -#ifdef _WIN32 -#include -#include -#include -#define POLL WSAPoll -#define CLOSE_SOCKET closesocket -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define POLL poll -#define SOCKET int -#define INVALID_SOCKET -1 -#define CLOSE_SOCKET close -#endif - -static void *memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) -{ - (void) data; - switch (tag) { - - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; + if (sock->ssl_ctx == NULL) { + int ret = write(sock->fd, src, len); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + if (errno != EINTR) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + ret = 0; + } + return ret; + } else { + int ret = SSL_write(sock->ssl, src, len); + if (ret <= 0) { + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + fprintf(stderr, "OpenSSL error in socket_write: "); + ERR_print_errors_fp(stderr); + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + ret = 0; + } + return ret; + } } -static int set_socket_blocking(SOCKET fd, int blocking) -{ -#ifdef _WIN32 - unsigned long mode = blocking ? 0 : 1; - return ioctlsocket(fd, FIONBIO, &mode) ? -1 : 0; -#else - int flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -1; - if (blocking) flags &= ~O_NONBLOCK; - else flags |= O_NONBLOCK; - if (fcntl(fd, F_SETFL, flags) < 0) - return -1; - return 0; -#endif +void socket_close(Socket *sock) { + // Set state to SHUTDOWN and call update + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_SHUTDOWN; + socket_update(sock); } -static unsigned long long -get_current_time_ms(void) -{ -#if defined(__linux__) - - struct timespec ts; - int result = clock_gettime(CLOCK_REALTIME, &ts); - if (result) - return UINT64_MAX; - return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; - -#elif defined(_WIN32) - - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - - ULARGE_INTEGER uli; - uli.LowPart = ft.dwLowDateTime; - uli.HighPart = ft.dwHighDateTime; - - // Convert Windows file time (100ns since 1601-01-01) to - // Unix epoch time (seconds since 1970-01-01) - // 116444736000000000 = number of 100ns intervals from 1601 to 1970 - return (uli.QuadPart - 116444736000000000ULL) / 10000ULL; // TODO: Make sure this is returning miliseconds -#endif +void socket_free(Socket *sock) { + // Release all resources associated to the socket + if (sock->ssl) { + SSL_free(sock->ssl); + sock->ssl = NULL; + } + if (sock->fd >= 0) { + close(sock->fd); + sock->fd = -1; + } + if (sock->hostname) { + free(sock->hostname); + sock->hostname = NULL; + } + if (sock->addr_list) { + free(sock->addr_list); + sock->addr_list = NULL; + } } -#endif // HTTP_CLIENT || HTTP_SERVER -///////////////////////////////////////////////////////////////////// -// HTTP CLIENT -///////////////////////////////////////////////////////////////////// -#if HTTP_CLIENT +#define COUNT(X) (sizeof(X) / sizeof((X)[0])) -#if !HTTP_ENGINE -#error "HTTP_CLIENT depends on HTTP_SERVER" -#endif +int socket_wait(Socket **socks, int num_socks) +{ + if (num_socks <= 0) + return -1; -#if HTTP_CLIENT_TLS + struct pollfd polled[100]; // TODO: make this value configurable + if (num_socks > (int) COUNT(polled)) + return -1; -#include -#include + for (;;) { + + for (int i = 0; i < num_socks; i++) { + + int events = 0; + switch (socks[i]->event) { + case SOCKET_WANT_READ : events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; + case SOCKET_WANT_NONE : return i; + default: HTTP_ASSERT(0); break; + } + + polled[i].fd = socks[i]->fd; + polled[i].events = events; + polled[i].revents = 0; + } + + int ret = poll(polled, num_socks, -1); + if (ret < 0) + return -1; + + // Update socket states based on poll results + for (int i = 0; i < num_socks; i++) { + + if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + socks[i]->event = SOCKET_WANT_NONE; + socks[i]->state = SOCKET_STATE_DIED; + return i; + } + + if (polled[i].revents & (POLLIN | POLLOUT)) { + socks[i]->event = SOCKET_WANT_NONE; + socket_update(socks[i]); + } + } + } + + return -1; +} +// TODO +#define ERROR printf("error at %s:%d\n", __FILE__, __LINE__); + +#define CLIENT_MAX_CONNS 256 + +typedef enum { + CLIENT_CONNECTION_FREE, + CLIENT_CONNECTION_INIT, + CLIENT_CONNECTION_WAIT, + CLIENT_CONNECTION_DONE, +} ClientConnectionState; typedef struct { - SSL_CTX *ctx; -} HTTP_TLSContext_; -_Static_assert(sizeof(HTTP_TLSContext) >= sizeof(HTTP_TLSContext_)); -_Static_assert(_Alignof(HTTP_TLSContext) >= _Alignof(HTTP_TLSContext_)); + ClientConnectionState state; + uint16_t gen; + Socket socket; + HTTP_Engine engine; + bool trace; +} ClientConnection; -typedef struct { - SSL *ssl; -} HTTP_TLSClientContext_; -_Static_assert(sizeof(HTTP_TLSClientContext) >= sizeof(HTTP_TLSClientContext_)); -_Static_assert(_Alignof(HTTP_TLSClientContext) >= _Alignof(HTTP_TLSClientContext_)); +struct HTTP_Client { + SocketGroup group; + int num_conns; + ClientConnection conns[CLIENT_MAX_CONNS]; -void http_tls_global_init(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); + int ready_head; + int ready_count; + int ready[CLIENT_MAX_CONNS]; +}; + +// Rename the memory function +static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { + (void)data; + switch (tag) { + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; } -void http_tls_global_free(void) +void http_global_init(void) { - EVP_cleanup(); - ERR_free_strings(); + socket_global_init(); } -int http_tls_init(HTTP_TLSContext *tls) +void http_global_free(void) { - HTTP_TLSContext_ *tls_ = (void*) tls; - - tls_->ctx = SSL_CTX_new(TLS_client_method()); - if (!tls_->ctx) - return -1; - - SSL_CTX_set_verify(tls_->ctx, SSL_VERIFY_PEER, NULL); - SSL_CTX_set_default_verify_paths(tls_->ctx); - return 0; + socket_global_free(); } -void http_tls_free(HTTP_TLSContext *tls) +HTTP_Client *http_client_init(void) { - HTTP_TLSContext_ *tls_ = (void*) tls; + HTTP_Client *client = malloc(sizeof(HTTP_Client)); + if (client == NULL) + return NULL; - SSL_CTX_free(tls_->ctx); - tls_->ctx = NULL; -} + if (socket_group_init(&client->group) < 0) { + free(client); + return NULL; + } -#else // HTTP_CLIENT_TLS + for (int i = 0; i < CLIENT_MAX_CONNS; i++) { + client->conns[i].state = CLIENT_CONNECTION_FREE; + client->conns[i].gen = 1; + } -void http_tls_global_init(void) {} -void http_tls_global_free(void) {} -int http_tls_init(HTTP_TLSContext *tls) { (void) tls; return 0; } -void http_tls_free(HTTP_TLSContext *tls) { (void) tls; } + client->num_conns = 0; + client->ready_head = 0; + client->ready_count = 0; -#endif // !HTTP_CLIENT_TLS - -void http_client_init(HTTP_Client *client) -{ - client->state = HTTP_STATE_CLIENT_IDLE; + return client; } void http_client_free(HTTP_Client *client) { - if (client->state != HTTP_STATE_CLIENT_IDLE) { - // TODO - } + for (int i = 0, j = 0; j < client->num_conns; i++) { + + if (client->conns[i].state == CLIENT_CONNECTION_FREE) + continue; + j++; + + // TODO + } + + socket_group_free(&client->group); + free(client); } -static void client_connect(HTTP_Client *client, struct sockaddr *addr, int addrlen) +int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle) { - int ret = connect((SOCKET) client->fd, addr, addrlen); - if (ret == 0) { - if (client->secure) - client->state = HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND; - else - client->state = HTTP_STATE_CLIENT_SEND; - } else { - if (errno == EINPROGRESS) - client->state = HTTP_STATE_CLIENT_CONNECT; - else { - client->code = HTTP_CLIENT_ERROR_FCONNECT; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - } + if (client->num_conns == CLIENT_MAX_CONNS) + return -1; + + int i = 0; + while (client->conns[i].state != CLIENT_CONNECTION_FREE) + i++; + + client->conns[i].trace = false; + client->conns[i].state = CLIENT_CONNECTION_INIT; + http_engine_init(&client->conns[i].engine, 1, client_memfunc, NULL); + + client->num_conns++; + + *handle = (HTTP_RequestHandle) { client, i, client->conns[i].gen }; + return 0; } -void http_client_startreq( - HTTP_Client *client, HTTP_Method method, - const char *url, HTTP_String *headers, - int num_headers, char *body, int body_len, - HTTP_TLSContext *tls) +static void client_connection_update(ClientConnection *conn) { -#ifdef _WIN32 - WSADATA wd; - if (WSAStartup(MAKEWORD(2, 2), &wd)) - return; -#endif + HTTP_ASSERT(conn->state == CLIENT_CONNECTION_WAIT); - HTTP_URL parsed_url; - int ret = http_parse_url(url, strlen(url), &parsed_url); - if (ret != strlen(url)) { - client->code = HTTP_CLIENT_ERROR_INVURL; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } + socket_update(&conn->socket); - if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { + while (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) { -#if !HTTP_CLIENT_TLS - client->code = HTTP_CLIENT_ERROR_NOSYS; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; -#else - client->secure = 1; -#endif + HTTP_EngineState engine_state; + + engine_state = http_engine_state(&conn->engine); - } else if (http_streq(parsed_url.scheme, HTTP_STR("http"))) { - client->secure = 0; - } else { - client->code = HTTP_CLIENT_ERROR_INVPROTO; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } + if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { + int len; + char *buf; + buf = http_engine_recvbuf(&conn->engine, &len); + if (buf) { + int ret = socket_read(&conn->socket, buf, len); + if (conn->trace) + print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret }); + http_engine_recvack(&conn->engine, ret); + } + } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { + int len; + char *buf; + buf = http_engine_sendbuf(&conn->engine, &len); + if (buf) { + int ret = socket_write(&conn->socket, buf, len); + if (conn->trace) + print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret }); + http_engine_sendack(&conn->engine, ret); + } + } - int port = parsed_url.authority.port; - if (port == 0) { - if (client->secure) - port = 443; - else - port = 80; - } + engine_state = http_engine_state(&conn->engine); - client->fd = (HTTP_Socket) socket(AF_INET, SOCK_STREAM, 0); - if ((SOCKET) client->fd == INVALID_SOCKET) { - client->code = HTTP_CLIENT_ERROR_FSOCK; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } + if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED || + engine_state == HTTP_ENGINE_STATE_CLIENT_READY) + socket_close(&conn->socket); + } - if (set_socket_blocking((SOCKET) client->fd, 0) < 0) { - client->code = -100000; // TODO - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - - switch (parsed_url.authority.host.mode) { - - case HTTP_HOST_MODE_VOID: - client->code = HTTP_CLIENT_ERROR_INVURL; - client->state = HTTP_STATE_CLIENT_CLOSED; - break; - - case HTTP_HOST_MODE_IPV4: - { - struct sockaddr_in addr_ipv4; - addr_ipv4.sin_family = AF_INET; - addr_ipv4.sin_port = htons(port); - memcpy(&addr_ipv4.sin_addr, &parsed_url.authority.host.ipv4, 4); - memset(&addr_ipv4.sin_zero, 0, sizeof(addr_ipv4.sin_zero)); - client_connect(client, (struct sockaddr*) &addr_ipv4, sizeof(addr_ipv4)); - } - break; - - case HTTP_HOST_MODE_IPV6: - { - struct sockaddr_in6 addr_ipv6; - addr_ipv6.sin6_family = AF_INET6; - addr_ipv6.sin6_port = htons(port); - memcpy(&addr_ipv6.sin6_addr, &parsed_url.authority.host.ipv6, 16); - // TODO: Should the other fields be initialized? - client_connect(client, (struct sockaddr*) &addr_ipv6, sizeof(addr_ipv6)); - } - break; - - case HTTP_HOST_MODE_NAME: - { - char namestr[1<<10]; // TODO: Assuming this won't overflow - memcpy(namestr, - parsed_url.authority.host.name.ptr, - parsed_url.authority.host.name.len); - namestr[parsed_url.authority.host.name.len] = '\0'; - - char portstr[1<<7]; - snprintf(portstr, sizeof(portstr), "%d", port); - - struct addrinfo *res; - - struct addrinfo hints; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - int ret = getaddrinfo(namestr, portstr, &hints, &res); - if (ret) { - client->code = HTTP_CLIENT_ERROR_DNS; - client->state = HTTP_STATE_CLIENT_CLOSED; - } else { - for (struct addrinfo *p = res; p != NULL; p = p->ai_next) { - client_connect(client, p->ai_addr, p->ai_addrlen); - if (client->state != HTTP_STATE_CLIENT_CLOSED) - break; - } - freeaddrinfo(res); - } - } - break; - } - - if (client->state == HTTP_STATE_CLIENT_CLOSED) { - // TODO - return; - } - -#if HTTP_CLIENT_TLS - if (client->secure) { - - HTTP_TLSContext_ *glbtls = (void*) tls; - HTTP_TLSClientContext_ *clitls = (void*) &client->tls; - - clitls->ssl = SSL_new(glbtls->ctx); - if (clitls->ssl == NULL) { - // TODO - client->code = HTTP_CLIENT_ERROR_FSSLNEW; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - - SSL_set_fd(clitls->ssl, (SOCKET) client->fd); // TODO: Error - } -#endif // HTTP_CLIENT_TLS - - http_engine_init(&client->eng, 1, memfunc, NULL); - http_engine_url(&client->eng, method, url, 1); - for (int i = 0; i < num_headers; i++) - http_engine_header(&client->eng, headers[i].ptr, headers[i].len); - if (body_len > 0) - http_engine_body(&client->eng, body, body_len); - http_engine_done(&client->eng); + if (socket_state(&conn->socket) == SOCKET_STATE_DIED) + conn->state = CLIENT_CONNECTION_DONE; } -static void client_recv_plain(HTTP_Client *client) +int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle) { - ASSERT(!client->secure); + while (client->ready_count == 0) { - int cap; - char *buf = http_engine_recvbuf(&client->eng, &cap); + int num_polled = 0; + int indices[CLIENT_MAX_CONNS]; + struct pollfd polled[CLIENT_MAX_CONNS]; - int ret = recv((SOCKET) client->fd, buf, cap, 0); - if (ret < 0) { - http_engine_recvack(&client->eng, 0); - client->code = HTTP_CLIENT_ERROR_FRECV; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - if (ret == 0) - http_engine_close(&client->eng); + for (int i = 0, j = 0; j < client->num_conns; i++) { - http_engine_recvack(&client->eng, ret); + HTTP_ASSERT(i < CLIENT_MAX_CONNS); + ClientConnection *conn = &client->conns[i]; + + if (conn->state == CLIENT_CONNECTION_FREE) + continue; + j++; + + int events = 0; + if (conn->state == CLIENT_CONNECTION_WAIT) { + switch (conn->socket.event) { + case SOCKET_WANT_READ : events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; + case SOCKET_WANT_NONE : events = 0; break; + } + } + + if (events) { + indices[num_polled] = i; + polled[num_polled].fd = conn->socket.fd; + polled[num_polled].events = events; + polled[num_polled].revents = 0; + num_polled++; + } + } + + if (num_polled == 0) + return -1; + + poll(polled, num_polled, -1); + + for (int i = 0; i < num_polled; i++) { + + int connidx = indices[i]; + ClientConnection *conn = &client->conns[connidx]; + + if (conn->state != CLIENT_CONNECTION_WAIT) + continue; + + if (polled[i].revents == 0) + continue; + + // TODO: handle error revents + + client_connection_update(conn); + + if (conn->state == CLIENT_CONNECTION_DONE) { + int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; + client->ready[tail] = connidx; + client->ready_count++; + } + } + } + + int index = client->ready[client->ready_head]; + client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS; + client->ready_count--; + *handle = (HTTP_RequestHandle) { client, index, client->conns[index].gen }; + return 0; } -static void client_recv_secure(HTTP_Client *client) +static ClientConnection *handle2clientconn(HTTP_RequestHandle handle) { - ASSERT(client->secure); + if (handle.data0 == NULL) + return NULL; -#if !HTTP_CLIENT_TLS - // TODO -#else - HTTP_TLSClientContext_ *tls = (void*) &client->tls; - SSL *ssl = tls->ssl; + HTTP_Client *client = handle.data0; - int cap; - char *buf = http_engine_recvbuf(&client->eng, &cap); - if (buf) { - int ret = SSL_read(ssl, buf, cap); - if (ret <= 0) { - http_engine_recvack(&client->eng, 0); - int err = SSL_get_error(ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - client->state = HTTP_STATE_CLIENT_RECV; - return; - } - if (err == SSL_ERROR_WANT_WRITE) { - client->state = HTTP_STATE_CLIENT_SEND; - return; - } - client->code = HTTP_CLIENT_ERROR_FSSLREAD; - client->state = HTTP_STATE_CLIENT_CLOSED; - http_engine_close(&client->eng); - return; - } - http_engine_recvack(&client->eng, ret); - } -#endif // HTTP_CLIENT_TLS + if (handle.data1 >= CLIENT_MAX_CONNS) + return NULL; + + ClientConnection *conn = &client->conns[handle.data1]; + + if (handle.data2 != conn->gen) + return NULL; + + return conn; } -static void client_recv(HTTP_Client *client) +void http_request_trace(HTTP_RequestHandle handle, bool trace) { - if (client->secure) { - client_recv_secure(client); - } else { - client_recv_plain(client); - } + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + conn->trace = trace; } -static void client_send_plain(HTTP_Client *client) +void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url) { - int len; - char *buf = http_engine_sendbuf(&client->eng, &len); - if (buf) { - int ret = send((SOCKET) client->fd, buf, len, 0); - if (ret < 0) { - http_engine_sendack(&client->eng, 0); - client->code = HTTP_CLIENT_ERROR_FSEND; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - http_engine_sendack(&client->eng, ret); - } + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + HTTP_Client *client = handle.data0; + + HTTP_URL parsed_url; + int ret = http_parse_url(url.ptr, url.len, &parsed_url); + if (ret != url.len) { + // TODO + ERROR; + return; + } + + bool secure = false; + if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { + secure = true; + } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { + // TODO + ERROR; + return; + } + + int port = parsed_url.authority.port; + if (port == 0) { + if (secure) + port = 443; + else + port = 80; + } + + SocketGroup *group = secure ? &client->group : NULL; + switch (parsed_url.authority.host.mode) { + case HTTP_HOST_MODE_IPV4: socket_connect_ipv4(&conn->socket, group, parsed_url.authority.host.ipv4, port); break; + case HTTP_HOST_MODE_IPV6: socket_connect_ipv6(&conn->socket, group, parsed_url.authority.host.ipv6, port); break; + case HTTP_HOST_MODE_NAME: socket_connect (&conn->socket, group, parsed_url.authority.host.name, port); break; + + case HTTP_HOST_MODE_VOID: + // TODO + ERROR; + return; + } + + http_engine_url(&conn->engine, method, url, 1); } -static void client_send_secure(HTTP_Client *client) +void http_request_header(HTTP_RequestHandle handle, char *header, int len) { -#if !HTTP_CLIENT_TLS - // TODO -#else - HTTP_TLSClientContext_ *tls = (void*) &client->tls; - SSL *ssl = tls->ssl; + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; - int len; - char *buf = http_engine_sendbuf(&client->eng, &len); - if (buf == NULL) return; - - int ret = SSL_write(ssl, buf, len); - if (ret <= 0) { - http_engine_sendack(&client->eng, 0); - int err = SSL_get_error(ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - client->state = HTTP_STATE_CLIENT_RECV; - return; - } - if (err == SSL_ERROR_WANT_WRITE) { - client->state = HTTP_STATE_CLIENT_SEND; - return; - } - client->code = HTTP_CLIENT_ERROR_FSSLWRITE; - client->state = HTTP_STATE_CLIENT_CLOSED; - http_engine_close(&client->eng); - return; - } - - http_engine_sendack(&client->eng, ret); -#endif // HTTP_CLIENT_TLS + http_engine_header(&conn->engine, header, len); } -static void client_send(HTTP_Client *client) +void http_request_body(HTTP_RequestHandle handle, char *body, int len) { - if (client->secure) - client_send_secure(client); - else - client_send_plain(client); + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + http_engine_body(&conn->engine, body, len); } -static void client_update(HTTP_Client *client) +void http_request_submit(HTTP_RequestHandle handle) { -#if HTTP_CLIENT_TLS - HTTP_TLSClientContext_ *tlsclient_ = (void*) &client->tls; - SSL *ssl = tlsclient_->ssl; -#endif + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; - if (client->state == HTTP_STATE_CLIENT_CONNECT) { - - int error; - socklen_t errlen = sizeof(error); - if (getsockopt((SOCKET) client->fd, SOL_SOCKET, SO_ERROR, (void*) &error, &errlen) < 0) { - client->code = HTTP_CLIENT_ERROR_FGETSOCKOPT; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - if (error) { - client->code = HTTP_CLIENT_ERROR_FCONNECT; - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - } - - if (client->secure) - client->state = HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND; - else - client->state = HTTP_STATE_CLIENT_SEND; - } - -#if HTTP_CLIENT_TLS - if (client->state == HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV || - client->state == HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND) { - - int ret = SSL_connect(ssl); - if (ret <= 0) { - int err = SSL_get_error(ssl, ret); - if (0) {} - else if (err == SSL_ERROR_WANT_READ) client->state = HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV; - else if (err == SSL_ERROR_WANT_WRITE) client->state = HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND; - else { - client->code = HTTP_CLIENT_ERROR_FSSLCONNECT; - client->state = HTTP_STATE_CLIENT_CLOSED; - } - return; - } - - client->state = HTTP_STATE_CLIENT_SEND; - } -#endif // HTTP_CLIENT_TLS - - for (;;) { - - HTTP_EngineState engstate = http_engine_state(&client->eng); - - if (engstate == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { - client_send(client); - continue; - } - - if (engstate == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { - client_recv(client); - continue; - } - - switch (http_engine_state(&client->eng)) { - - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: - client->state = HTTP_STATE_CLIENT_SEND; - break; - - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: - client->state = HTTP_STATE_CLIENT_RECV; - break; - - case HTTP_ENGINE_STATE_CLIENT_READY: - client->state = HTTP_STATE_CLIENT_READY; - return; - - case HTTP_ENGINE_STATE_CLIENT_CLOSED: - client->state = HTTP_STATE_CLIENT_CLOSED; - return; - - default: - UNREACHABLE; - break; - } - } + http_engine_done(&conn->engine); + conn->state = CLIENT_CONNECTION_WAIT; } -int http_client_waitall(HTTP_Client **clients, int num_clients, int timeout) +HTTP_Response *http_request_result(HTTP_RequestHandle handle) { - if (num_clients < 0) { - num_clients = 0; - while (clients[num_clients]) - num_clients++; - } - - if (num_clients == 0 || num_clients > HTTP_CLIENT_WAIT_LIMIT) - return -1; - - unsigned long long start_time; - if (timeout < 0) - start_time = -1ULL; - else { - start_time = get_current_time_ms(); - if (start_time == -1ULL) - return -1; - } - - HTTP_Client *remain[HTTP_CLIENT_WAIT_LIMIT]; - for (int i = 0; i < num_clients; i++) - remain[i] = clients[i]; - int num_remain = num_clients; - - do { - int timeout2; - if (timeout < 0) - timeout2 = -1; - else { - unsigned long long current_time = get_current_time_ms(); - if (current_time == -1ULL) - return -1; - ASSERT(current_time >= start_time); - if (current_time - start_time > (unsigned long long) timeout) - return 0; - timeout2 = (int) (current_time - start_time); - } - - int ret = http_client_waitany(remain, num_remain, timeout2); - if (ret < 0) return -1; - - remain[ret] = remain[--num_remain]; - } while (num_remain > 0); - - return 0; + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return NULL; + if (conn->state != CLIENT_CONNECTION_DONE) + return NULL; + HTTP_EngineState engine_state = http_engine_state(&conn->engine); + if (engine_state != HTTP_ENGINE_STATE_CLIENT_READY) + return NULL; + return http_engine_getres(&conn->engine); } -int http_client_waitany(HTTP_Client **clients, int num_clients, int timeout) +void http_request_free(HTTP_RequestHandle handle) { - if (num_clients < 0) { - num_clients = 0; - while (clients[num_clients]) - num_clients++; - } + HTTP_Client *client = handle.data0; + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_DONE) + return; + http_engine_free(&conn->engine); + socket_free(&conn->socket); + conn->state = CLIENT_CONNECTION_FREE; + client->num_conns--; +} +#define MAX_CONNS (1<<10) - if (num_clients == 0 || num_clients > HTTP_CLIENT_WAIT_LIMIT) - return -1; +typedef struct { + bool used; + uint16_t gen; + Socket socket; + HTTP_Engine engine; +} Connection; - unsigned long long start_time; - if (timeout < 0) - start_time = -1ULL; - else { - start_time = get_current_time_ms(); - if (start_time == -1ULL) - return -1; - } +struct HTTP_Server { + SocketGroup group; - for (;;) { + int listen_fd; + int secure_fd; - struct pollfd poll_array[HTTP_CLIENT_WAIT_LIMIT]; - int poll_count = 0; + int num_conns; + Connection conns[MAX_CONNS]; - for (int i = 0; i < num_clients; i++) { + int ready_head; + int ready_count; + int ready[MAX_CONNS]; +}; - int events = 0; - switch (clients[i]->state) { - case HTTP_STATE_CLIENT_CONNECT: - events = POLLOUT; - break; +static int listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog) +{ + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) + return -1; - case HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV: - events = POLLIN; - break; + { + int flags = fcntl(listen_fd, F_GETFL, 0); + if (flags < 0) { + close(listen_fd); + return -1; + } - case HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND: - events = POLLOUT; - break; + if (fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK) < 0) { + close(listen_fd); + return -1; + } + } - case HTTP_STATE_CLIENT_RECV: - events = POLLIN; - break; + if (reuse_addr) { + int one = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + } - case HTTP_STATE_CLIENT_SEND: - events = POLLOUT; - break; + struct in_addr addr_buf; + if (addr.len == 0) + addr_buf.s_addr = htonl(INADDR_ANY); + else { - case HTTP_STATE_CLIENT_READY: - case HTTP_STATE_CLIENT_CLOSED: - return i; + _Static_assert(sizeof(struct in_addr) == sizeof(HTTP_IPv4)); + if (http_parse_ipv4(addr.ptr, addr.len, (HTTP_IPv4*) &addr_buf) < 0) { + close(listen_fd); + return -1; + } + } - default: - return -1; - } + struct sockaddr_in bind_buf; + bind_buf.sin_family = AF_INET; + bind_buf.sin_addr = addr_buf; + bind_buf.sin_port = htons(port); + if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { + close(listen_fd); + return -1; + } - poll_array[poll_count].fd = clients[i]->fd; - poll_array[poll_count].events = events; - poll_array[poll_count].revents = 0; - poll_count++; - } + if (listen(listen_fd, backlog) < 0) { + close(listen_fd); + return -1; + } - int timeout2; - if (timeout < 0) - timeout2 = -1; - else { - unsigned long long current_time = get_current_time_ms(); - if (current_time == -1ULL) - return -1; - ASSERT(current_time >= start_time); - if (current_time - start_time > (unsigned long long) timeout) - return 0; - timeout2 = (int) (current_time - start_time); - } - - int num = POLL(poll_array, poll_count, timeout2); - // TODO: Handle error - - for (int i = 0; i < num_clients; i++) - if (poll_array[i].revents) client_update(clients[i]); - } - - return -1; // UNREACHABLE + return listen_fd; } -int http_client_result(HTTP_Client *client, HTTP_Response **res) +HTTP_Server *http_server_init(HTTP_String addr, uint16_t port) { - if (client->state != HTTP_STATE_CLIENT_READY) { - *res = NULL; - return client->code; - } - - *res = http_engine_getres(&client->eng); - return HTTP_CLIENT_OK; + return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); } -const char *http_client_strerror(int code) +HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, + uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key) { - switch (code) { - case HTTP_CLIENT_OK: return "OK"; - case HTTP_CLIENT_ERROR_INVURL: return "Invalid URL"; - case HTTP_CLIENT_ERROR_NOSYS: return "Not compiled in"; - case HTTP_CLIENT_ERROR_INVPROTO: return "Invalid protocol"; - case HTTP_CLIENT_ERROR_FSOCK: return "socket() error"; - case HTTP_CLIENT_ERROR_FCONNECT: return "connect() error"; - case HTTP_CLIENT_ERROR_DNS: return "DNS resolution error"; - case HTTP_CLIENT_ERROR_FSSLNEW: return "SSL_new() error"; - case HTTP_CLIENT_ERROR_FRECV: return "recv() error"; - case HTTP_CLIENT_ERROR_FSSLREAD: return "SSL_read() error"; - case HTTP_CLIENT_ERROR_FSEND: return "send() error"; - case HTTP_CLIENT_ERROR_FSSLWRITE: return "SSL_write() error"; - case HTTP_CLIENT_ERROR_FGETSOCKOPT: return "getsockopt() error"; - case HTTP_CLIENT_ERROR_FSSLCONNECT: return "SSL_connect() error"; - } - return "???"; -} + HTTP_Server *server = malloc(sizeof(HTTP_Server)); + if (server == NULL) + return NULL; -#endif // HTTP_CLIENT -///////////////////////////////////////////////////////////////////// -// HTTP SERVER -///////////////////////////////////////////////////////////////////// -#if HTTP_SERVER + int backlog = 32; + bool reuse_addr = true; -#if !HTTP_ENGINE -#error "HTTP_SERVER depends on HTTP_ENGINE" -#endif + if (port == 0 && secure_port == 0) { + // You must have at least one! + free(server); + return NULL; + } -static void bitset_init(HTTP_Bitset *set) -{ - memset(set, 0, sizeof(HTTP_Bitset)); -} + if (port == 0) + server->listen_fd = -1; + else { + server->listen_fd = listen_socket(addr, port, reuse_addr, backlog); + if (server->listen_fd < 0) { + free(server); + return NULL; + } + } -static void bitset_set(HTTP_Bitset *set, int idx, int val) -{ - HTTP_BitsetWord *word = &set->data[idx / sizeof(HTTP_BitsetWord)]; - HTTP_BitsetWord mask = (HTTP_BitsetWord) 1 << (idx % sizeof(HTTP_BitsetWord)); - if (val) - *word |= mask; - else - *word &= ~mask; -} + if (secure_port == 0) + server->secure_fd = -1; + else { -static int bitset_get(HTTP_Bitset *set, int idx) -{ - HTTP_BitsetWord word = set->data[idx / sizeof(HTTP_BitsetWord)]; - HTTP_BitsetWord mask = (HTTP_BitsetWord) 1 << (idx % sizeof(HTTP_BitsetWord)); - return (word & mask) == mask; -} + if (socket_group_init_server(&server->group, cert_key, private_key) < 0) { + close(server->listen_fd); + free(server); + return NULL; + } -static void int_queue_init(HTTP_IntQueue *q) -{ - q->head = 0; - q->count = 0; - bitset_init(&q->set); -} + server->secure_fd = listen_socket(addr, secure_port, reuse_addr, backlog); + if (server->secure_fd < 0) { + socket_group_free(&server->group); + close(server->listen_fd); + free(server); + return NULL; + } + } -static int int_queue_contains(HTTP_IntQueue *q, int val) -{ - return bitset_get(&q->set, val); -} + server->num_websites = 0; + server->num_conns = 0; + server->ready_head = 0; + server->ready_count = 0; -static void int_queue_push(HTTP_IntQueue *q, int val) -{ - if (int_queue_contains(q, val)) - return; + for (int i = 0; i < MAX_CONNS; i++) { + server->conns[i].used = false; + server->conns[i].gen = 1; + } - q->items[(q->head + q->count) % HTTP_MAX_CLIENTS_PER_SERVER] = val; - q->count++; - - bitset_set(&q->set, val, 1); -} - -static int int_queue_pop(HTTP_IntQueue *q) -{ - if (q->count == 0) - return -1; - - int val = q->items[q->head % HTTP_MAX_CLIENTS_PER_SERVER]; - q->head = (q->head + 1) % HTTP_MAX_CLIENTS_PER_SERVER; - - q->count--; - bitset_set(&q->set, val, 0); - return val; -} - -static void int_queue_remove(HTTP_IntQueue *q, int val) -{ - if (!int_queue_contains(q, val)) - return; - - int i = 0; - while (q->items[(q->head + i) % HTTP_MAX_CLIENTS_PER_SERVER] != val) - i++; - - while (i < q->count-1) { - q->items[(q->head + i) % HTTP_MAX_CLIENTS_PER_SERVER] - = q->items[(q->head + i + 1) % HTTP_MAX_CLIENTS_PER_SERVER]; - i++; - } - - q->count--; - bitset_set(&q->set, val, 0); -} - -int http_server_init(HTTP_Server *server, const char *addr, int port) -{ -#ifdef _WIN32 - WSADATA wd; - if (WSAStartup(MAKEWORD(2, 2), &wd)) - return -1; -#endif - - SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (listen_fd == INVALID_SOCKET) - return -1; - - if (set_socket_blocking(listen_fd, 0) < 0) { - CLOSE_SOCKET(listen_fd); - return -1; - } - - int reuse = 1; - setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse)); - - struct in_addr bind_addr_buf; - if (inet_pton(AF_INET, addr, &bind_addr_buf) != 1) { - CLOSE_SOCKET(listen_fd); - return -1; - } - - struct sockaddr_in bind_all_buf; - bind_all_buf.sin_family = AF_INET; - bind_all_buf.sin_port = htons(port); - bind_all_buf.sin_addr = bind_addr_buf; - if (bind(listen_fd, (struct sockaddr*) &bind_all_buf, sizeof(bind_all_buf)) < 0) { - CLOSE_SOCKET(listen_fd); - return -1; - } - - if (listen(listen_fd, 32) < 0) { - CLOSE_SOCKET(listen_fd); - return -1; - } - - int_queue_init(&server->ready); - server->listen_fd = listen_fd; - server->num_conns = 0; - for (int i = 0; i < HTTP_MAX_CLIENTS_PER_SERVER; i++) { - server->conns[i].fd = INVALID_SOCKET; - server->conns[i].gen = 1; - } - - return 0; + return server; } void http_server_free(HTTP_Server *server) { - for (int i = 0; i < HTTP_MAX_CLIENTS_PER_SERVER; i++) - if ((SOCKET) server->conns[i].fd != INVALID_SOCKET) { - http_engine_free(&server->conns[i].eng); - CLOSE_SOCKET((SOCKET) server->conns[i].fd); - } - CLOSE_SOCKET((SOCKET) server->listen_fd); + for (int i = 0, j = 0; j < server->num_conns; i++) { + + if (!server->conns[i].used) + continue; + j++; + + // TODO + } + + close(server->secure_fd); + close(server->listen_fd); + if (server->secure_fd != -1) + socket_group_free(&server->group); + free(server); } -static HTTP_ResponseHandle -conn2handle(HTTP_Server *server, HTTP_ServerConnection *conn) +int http_server_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { - return (HTTP_ResponseHandle) { server, conn - server->conns, conn->gen }; + return socket_group_add_domain(&server->group, domain, cert_file, key_file); } -static HTTP_ServerConnection* +static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { + (void)data; + switch (tag) { + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandle *handle) +{ + while (server->ready_count == 0) { + + int num_polled = 0; + struct pollfd polled[MAX_CONNS+2]; + int indices[MAX_CONNS+2]; + + if (server->num_conns < MAX_CONNS) { + + if (server->listen_fd != -1) { + polled[num_polled].fd = server->listen_fd; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + indices[num_polled] = -1; + num_polled++; + } + + if (server->secure_fd != -1) { + polled[num_polled].fd = server->secure_fd; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + indices[num_polled] = -1; + num_polled++; + } + } + + for (int i = 0, j = 0; i < server->num_conns; i++) { + + if (!server->conns[i].used) + continue; + j++; + + int events = 0; + + if (server->conns[i].socket.ssl_ctx) + events = server->conns[i].socket.event; + else { + switch (http_engine_state(&server->conns[i].engine)) { + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: events = POLLIN; break; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: events = POLLOUT; break; + default:break; + } + } + + if (events) { + polled[num_polled].fd = server->conns[i].socket.fd; + polled[num_polled].events = events; + polled[num_polled].revents = 0; + indices[num_polled] = i; + num_polled++; + } + } + + int timeout = -1; + poll(polled, num_polled, timeout); + + for (int i = 0; i < num_polled; i++) { + + if (polled[i].fd == server->listen_fd || polled[i].fd == server->secure_fd) { + + bool secure = false; + if (polled[i].fd == server->secure_fd) + secure = true; + + if ((polled[i].revents & POLLIN) && server->num_conns < MAX_CONNS) { + + int new_fd = accept(polled[i].fd, NULL, NULL); + + int k = 0; + while (server->conns[k].used) + k++; + + server->conns[k].used = true; + socket_accept(&server->conns[k].socket, secure ? &server->group : NULL, new_fd); + http_engine_init(&server->conns[k].engine, 0, server_memfunc, NULL); + server->num_conns++; + } + + } else { + + int connidx = indices[i]; + Connection *conn = &server->conns[connidx]; + + socket_update(&conn->socket); + + if (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) { + + switch (http_engine_state(&conn->engine)) { + + int len; + char *buf; + + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: + buf = http_engine_recvbuf(&conn->engine, &len); + if (buf) { + int ret = socket_read(&conn->socket, buf, len); + http_engine_recvack(&conn->engine, ret); + } + break; + + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: + buf = http_engine_sendbuf(&conn->engine, &len); + if (buf) { + int ret = socket_write(&conn->socket, buf, len); + http_engine_sendack(&conn->engine, ret); + } + break; + + default: + break; + } + + switch (http_engine_state(&conn->engine)) { + + int tail; + + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: + tail = (server->ready_head + server->ready_count) % MAX_CONNS; + server->ready[tail] = connidx; + server->ready_count++; + break; + + case HTTP_ENGINE_STATE_SERVER_CLOSED: + socket_close(&conn->socket); + break; + + default: + break; + + } + } + + if (socket_state(&conn->socket) == SOCKET_STATE_DIED) { + socket_free(&conn->socket); + http_engine_free(&conn->engine); + conn->used = false; + server->num_conns--; + } + } + } + } + + int index = server->ready[server->ready_head]; + server->ready_head = (server->ready_head + 1) % MAX_CONNS; + server->ready_count--; + + *req = http_engine_getreq(&server->conns[index].engine); + *handle = (HTTP_ResponseHandle) { server, index, server->conns[index].gen }; + return 0; +} + +static Connection* handle2conn(HTTP_ResponseHandle handle) { - HTTP_Server *server = handle.ptr; - if (handle.idx >= HTTP_MAX_CLIENTS_PER_SERVER) + HTTP_Server *server = handle.data0; + if (handle.data1 >= MAX_CONNS) return NULL; - HTTP_ServerConnection *conn = &server->conns[handle.idx]; - if (conn->gen != handle.gen) + + Connection *conn = &server->conns[handle.data1]; + if (conn->gen != handle.data2) return NULL; + return conn; } -int http_server_wait(HTTP_Server *server, HTTP_Request **req, - HTTP_ResponseHandle *res, int timeout) -{ - unsigned long long start_time; - if (timeout < 0) - start_time = -1ULL; - else { - start_time = get_current_time_ms(); - if (start_time == -1ULL) - return -1; - } - - int popped; - while ((popped = int_queue_pop(&server->ready)) < 0) { - - int poll_count = 0; - int poll_indices[HTTP_MAX_CLIENTS_PER_SERVER]; - struct pollfd poll_array[HTTP_MAX_CLIENTS_PER_SERVER + 1]; - - for (int i = 0, j = 0; j < server->num_conns; i++) { - - HTTP_ServerConnection *conn = &server->conns[i]; - if ((SOCKET) conn->fd == INVALID_SOCKET) - continue; - - HTTP_EngineState state = http_engine_state(&conn->eng); - - int events = 0; - if (0) {} - else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) events = POLLIN; - else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) events = POLLOUT; - - if (events) { - poll_array[poll_count].fd = conn->fd; - poll_array[poll_count].events = events; - poll_array[poll_count].revents = 0; - poll_indices[poll_count] = i; - poll_count++; - } - - j++; - } - - if (server->num_conns < HTTP_MAX_CLIENTS_PER_SERVER) { - poll_array[poll_count].fd = server->listen_fd; - poll_array[poll_count].events = POLLIN; - poll_array[poll_count].revents = 0; - poll_count++; - } - - int timeout2; - if (timeout < 0) - timeout2 = -1; - else { - unsigned long long current_time = get_current_time_ms(); - if (current_time == -1ULL) - return -1; - ASSERT(current_time >= start_time); - if (current_time - start_time > (unsigned long long) timeout) - return 0; - timeout2 = (int) (current_time - start_time); - } - - int num = POLL(poll_array, poll_count, timeout2); - if (num < 0) { - // TODO - } - - if (server->num_conns < HTTP_MAX_CLIENTS_PER_SERVER) { - if (poll_array[poll_count-1].revents) do { - SOCKET accepted_fd = accept(server->listen_fd, NULL, NULL); - if (accepted_fd == INVALID_SOCKET) - break; - - int i = 0; - while ((SOCKET) server->conns[i].fd != INVALID_SOCKET) - i++; - HTTP_ServerConnection *conn = &server->conns[i]; - - conn->fd = accepted_fd; - http_engine_init(&conn->eng, 0, memfunc, NULL); - - server->num_conns++; - - } while (server->num_conns < HTTP_MAX_CLIENTS_PER_SERVER); - poll_count--; - } - - for (int i = 0; i < poll_count; i++) { - - int j = poll_indices[i]; - int revents = poll_array[i].revents; - - HTTP_ServerConnection *conn = &server->conns[j]; - ASSERT((SOCKET) conn->fd != INVALID_SOCKET); - - HTTP_EngineState state; - for (;;) { - state = http_engine_state(&conn->eng); - - if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF && (revents & POLLIN)) { - int max; - char *buf = http_engine_recvbuf(&conn->eng, &max); - int ret = recv(conn->fd, buf, max, 0); - if (ret <= 0) { - if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) - revents = ~POLLIN; - else - http_engine_close(&conn->eng); - ret = 0; - } - http_engine_recvack(&conn->eng, ret); - continue; - } - - if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF && (revents & POLLOUT)) { - int max; - char *buf = http_engine_sendbuf(&conn->eng, &max); - int ret = send(conn->fd, buf, max, 0); - if (ret <= 0) { - if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) - revents = ~POLLOUT; - else - http_engine_close(&conn->eng); - ret = 0; - } - http_engine_sendack(&conn->eng, ret); - continue; - } - - break; - } - - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) - int_queue_push(&server->ready, j); - - if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) { - - http_engine_free(&conn->eng); - - CLOSE_SOCKET(conn->fd); - conn->fd = INVALID_SOCKET; - - conn->gen++; - if (conn->gen == 0 || conn->gen == UINT16_MAX) - conn->gen = 1; - - int_queue_remove(&server->ready, j); - - server->num_conns--; - } - } - } - - HTTP_ServerConnection *conn = &server->conns[popped]; - *req = http_engine_getreq(&conn->eng); - *res = conn2handle(server, conn); - return 1; -} - void http_response_status(HTTP_ResponseHandle res, int status) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; - http_engine_status(&conn->eng, status); + http_engine_status(&conn->engine, status); } void http_response_header(HTTP_ResponseHandle res, const char *fmt, ...) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; va_list args; va_start(args, fmt); - http_engine_header_fmt2(&conn->eng, fmt, args); + http_engine_header_fmt2(&conn->engine, fmt, args); va_end(args); } void http_response_body(HTTP_ResponseHandle res, char *src, int len) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; if (len < 0) len = strlen(src); - http_engine_body(&conn->eng, src, len); + http_engine_body(&conn->engine, src, len); } void http_response_bodycap(HTTP_ResponseHandle res, int mincap) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; - http_engine_bodycap(&conn->eng, mincap); + http_engine_bodycap(&conn->engine, mincap); } char *http_response_bodybuf(HTTP_ResponseHandle res, int *cap) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) { *cap = 0; return NULL; } - return http_engine_bodybuf(&conn->eng, cap); + return http_engine_bodybuf(&conn->engine, cap); } void http_response_bodyack(HTTP_ResponseHandle res, int num) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; - http_engine_bodyack(&conn->eng, num); + http_engine_bodyack(&conn->engine, num); } void http_response_undo(HTTP_ResponseHandle res) { - HTTP_ServerConnection *conn = handle2conn(res); + Connection *conn = handle2conn(res); if (conn == NULL) return; - http_engine_undo(&conn->eng); + http_engine_undo(&conn->engine); } void http_response_done(HTTP_ResponseHandle res) { - HTTP_Server *server = res.ptr; - HTTP_ServerConnection *conn = handle2conn(res); - if (conn == NULL) - return; + HTTP_Server *server = res.data0; + Connection *conn = handle2conn(res); + if (conn == NULL) + return; - http_engine_done(&conn->eng); + http_engine_done(&conn->engine); - conn->gen++; - if (conn->gen == 0 || conn->gen == UINT16_MAX) - conn->gen = 1; + conn->gen++; + if (conn->gen == 0 || conn->gen == UINT16_MAX) + conn->gen = 1; - HTTP_EngineState state = http_engine_state(&conn->eng); + HTTP_EngineState state = http_engine_state(&conn->engine); - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) - int_queue_push(&server->ready, res.idx); + if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { + int tail = (server->ready_head + server->ready_count) % MAX_CONNS; + server->ready[tail] = res.data1; + server->ready_count++; + } - if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) { - - http_engine_free(&conn->eng); - - CLOSE_SOCKET(conn->fd); - conn->fd = INVALID_SOCKET; - - int_queue_remove(&server->ready, res.idx); - - server->num_conns--; - } + if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) { + socket_close(&conn->socket); + http_engine_free(&conn->engine); + server->num_conns--; + } } - -#endif // HTTP_SERVER -///////////////////////////////////////////////////////////////////// -// HTTP PROXY -///////////////////////////////////////////////////////////////////// -#if HTTP_PROXY - -#if !HTTP_PROXY_ENGINE -#error "HTTP_PROXY depends on HTTP_PROXY_ENGINE" -#endif - -int http_proxy_init(HTTP_Proxy *proxy, const char *addr, int port) -{ - struct sockaddr_in buf; - buf.sin_family = AF_INET; - buf.sin_port = htons(port); - memset(&buf.sin_zero, 0, sizeof(buf.sin_zero)); - if (inet_pton(AF_INET, addr, &buf.sin_addr) != 1) - return -1; - - SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0); - -#ifdef _WIN32 - if (listen_sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) { - WSADATA wd; - if (WSAStartup(MAKEWORD(2, 2), &wd)) - return -1; - listen_sock = socket(AF_INET, SOCK_STREAM, 0); - } -#endif - - if (listen_sock == INVALID_SOCKET) - return -1; - - if (set_socket_blocking(listen_sock, 0) < 0) { - CLOSE_SOCKET(listen_sock); - return -1; - } - - if (bind(listen_sock, (struct sockaddr*) &buf, sizeof(buf)) < 0) { - CLOSE_SOCKET(listen_sock); - return -1; - } - - if (listen(listen_sock, 32) < 0) { - CLOSE_SOCKET(listen_sock); - return -1; - } - - proxy->num_conns = 0; - proxy->listen_sock = listen_sock; - for (int i = 0; i < HTTP_MAX_CONNS_PER_PROXY; i++) { - proxy->conns[i].client_sock = INVALID_SOCKET; - proxy->conns[i].server_sock = INVALID_SOCKET; - } - - return 0; -} - -void http_proxy_free(HTTP_Proxy *proxy) -{ - for (int i = 0, j = 0; j < proxy->num_conns; i++) { - - if (proxy->conns[i].client_sock == INVALID_SOCKET) - continue; - j++; - - CLOSE_SOCKET(proxy->conns[i].client_sock); - CLOSE_SOCKET(proxy->conns[i].server_sock); - http_proxyengine_free(&proxy->conns[i].eng); - } - - CLOSE_SOCKET(proxy->listen_sock); -} - -int http_proxy_wait(HTTP_Proxy *proxy, int timeout) -{ - for (;;) { - - int poll_indices[HTTP_MAX_CONNS_PER_PROXY+1]; - struct pollfd poll_array[HTTP_MAX_CONNS_PER_PROXY+1]; - int poll_count = 0; - - if (proxy->num_conns < HTTP_MAX_CONNS_PER_PROXY) { - poll_indices[poll_count] = -1; - poll_array[poll_count].fd = proxy->listen_sock; - poll_array[poll_count].events = POLLIN; - poll_array[poll_count].revents = 0; - poll_count++; - } - - for (int i=0, j=0; jnum_conns; i++) { - - if (proxy->conns[i].client_sock == INVALID_SOCKET) - continue; - j++; - - SOCKET fd; - int events = 0; - switch (http_proxyengine_state(&proxy->conns[i].eng)) { - case HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_BUF: fd = proxy->conns[i].client_sock; events = POLLIN; break; - case HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_BUF: fd = proxy->conns[i].client_sock; events = POLLOUT; break; - case HTTP_PROXY_ENGINE_STATE_SERVER_RECV_BUF: fd = proxy->conns[i].server_sock; events = POLLIN; break; - case HTTP_PROXY_ENGINE_STATE_SERVER_SEND_BUF: fd = proxy->conns[i].server_sock; events = POLLOUT; break; - } - ASSERT(events); - - poll_indices[poll_count] = i; - poll_array[poll_count].fd = proxy->conns[i].client_sock; - poll_array[poll_count].events = events; - poll_array[poll_count].revents = 0; - poll_count++; - } - - int num = POLL(poll_array, poll_count, timeout); - - for (int i = 0; i < poll_count; i++) { - - if (!poll_array[i].revents) - continue; - - int j = poll_indices[i]; - if (j == -1) { - - SOCKET accepted_sock = accept(proxy->listen_sock, NULL, NULL); - if (accepted_sock == INVALID_SOCKET) { - ASSERT(0); // TODO - } - - int i = 0; - while (proxy->conns[i].client_sock != INVALID_SOCKET) - i++; - - HTTP_ProxyConnection *conn = &proxy->conns[i]; - conn->client_sock = accepted_sock; - conn->server_sock = xxx; - http_proxyengine_init(&conn->eng, memfunc, NULL); - continue; - } - - HTTP_ProxyConnection *conn = &proxy->conns[j]; - HTTP_ProxyEngine *eng = &conn->eng; - - switch (http_proxyengine_state(eng)) { - - case HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_BUF: - { - int cap; - char *dst = http_proxyengine_clientrecvbuf(eng, &cap); - if (dst) { - int ret = recv(conn->client_sock, dst, cap, 0); - if (ret == 0) - http_proxyengine_close(eng); - else if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) - http_proxyengine_close(eng); - ret = 0; - } - http_proxyengine_clientrecvack(eng, ret); - } - } - break; - - case HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_BUF: - { - int len; - char *src = http_proxyengine_clientsendbuf(eng, &len); - if (src) { - int ret = send(conn->client_sock, src, len, 0); - if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) - http_proxyengine_close(eng); - ret = 0; - } - http_proxyengine_clientsendack(eng, ret); - } - } - break; - - case HTTP_PROXY_ENGINE_STATE_SERVER_RECV_BUF: - { - int cap; - char *dst = http_proxyengine_serverrecvbuf(eng, &cap); - if (dst) { - int ret = recv(conn->server_sock, dst, cap, 0); - if (ret == 0) - http_proxyengine_close(eng); - else if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) - http_proxyengine_close(eng); - ret = 0; - } - http_proxyengine_serverrecvack(eng, ret); - } - } - break; - - case HTTP_PROXY_ENGINE_STATE_SERVER_SEND_BUF: - { - int len; - char *src = http_proxyengine_serversendbuf(eng, &len); - if (src) { - int ret = send(conn->server_sock, src, len, 0); - if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) - http_proxyengine_close(eng); - ret = 0; - } - http_proxyengine_serversendack(eng, ret); - } - } - break; - } - - if (http_proxyengine_state(eng) == HTTP_PROXY_ENGINE_STATE_CLOSED) { - http_proxyengine_free(eng); - CLOSE_SOCKET(conn->client_sock); - CLOSE_SOCKET(conn->server_sock); - conn->client_sock = INVALID_SOCKET; - conn->server_sock = INVALID_SOCKET; - } - } - } - - return 0; -} - -#endif // HTTP_PROXY -///////////////////////////////////////////////////////////////////// -// HTTP ROUTER -///////////////////////////////////////////////////////////////////// -#if HTTP_ROUTER - -#if !HTTP_SERVER -#error "HTTP_ROUTER depends on HTTP_SERVER" -#endif - #ifndef _WIN32 -#include #endif typedef enum { @@ -3888,7 +4141,7 @@ static int serve_file_or_index(HTTP_ResponseHandle res, HTTP_String base_endpoin if (ret == 1) return 0; // File missing } - ASSERT(ret == 0); + HTTP_ASSERT(ret == 0); int cap; char *dst; @@ -3965,13 +4218,12 @@ void http_router_resolve(HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHa http_response_done(res); } -int http_serve(const char *addr, int port, HTTP_Router *router) +int http_serve(char *addr, int port, HTTP_Router *router) { int ret; - HTTP_Server server; - ret = http_server_init(&server, addr, port); - if (ret < 0) { + HTTP_Server *server = http_server_init((HTTP_String) { addr, strlen(addr) }, port, 0, (HTTP_String) {}, (HTTP_String) {}); + if (server == NULL) { http_router_free(router); return -1; } @@ -3979,9 +4231,9 @@ int http_serve(const char *addr, int port, HTTP_Router *router) for (;;) { HTTP_Request *req; HTTP_ResponseHandle res; - ret = http_server_wait(&server, &req, &res, -1); + ret = http_server_wait(server, &req, &res); if (ret < 0) { - http_server_free(&server); + http_server_free(server); http_router_free(router); return -1; } @@ -3990,12 +4242,7 @@ int http_serve(const char *addr, int port, HTTP_Router *router) http_router_resolve(router, req, res); } - http_server_free(&server); + http_server_free(server); http_router_free(router); return 0; -} - -#endif // HTTP_ROUTER -///////////////////////////////////////////////////////////////////// -// THE END -///////////////////////////////////////////////////////////////////// \ No newline at end of file +} \ No newline at end of file diff --git a/http.h b/http.h new file mode 100644 index 0000000..4bf69d7 --- /dev/null +++ b/http.h @@ -0,0 +1,298 @@ +/* + * HTTP Library - Amalgamated Header + * Generated automatically - do not edit manually + */ + +#ifndef HTTP_AMALGAMATION_H +#define HTTP_AMALGAMATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1}) +#define HTTP_CEIL(X, Y) (((X) + (Y) - 1) / (Y)) + +typedef struct { + char *ptr; + long len; +} HTTP_String; + +int http_streq (HTTP_String s1, HTTP_String s2); +int http_streqcase (HTTP_String s1, HTTP_String s2); +HTTP_String http_trim (HTTP_String s); + +#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0])) +#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} + +#define HTTP_MAX_HEADERS 32 + +typedef struct { + unsigned int data; +} HTTP_IPv4; + +typedef struct { + unsigned short data[8]; +} HTTP_IPv6; + +typedef enum { + HTTP_HOST_MODE_VOID = 0, + HTTP_HOST_MODE_NAME, + HTTP_HOST_MODE_IPV4, + HTTP_HOST_MODE_IPV6, +} HTTP_HostMode; + +typedef struct { + HTTP_HostMode mode; + HTTP_String text; + union { + HTTP_String name; + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + }; +} HTTP_Host; + +typedef struct { + HTTP_String userinfo; + HTTP_Host host; + int port; +} HTTP_Authority; + +// ZII +typedef struct { + HTTP_String scheme; + HTTP_Authority authority; + HTTP_String path; + HTTP_String query; + HTTP_String fragment; +} HTTP_URL; + +typedef enum { + HTTP_METHOD_GET, + HTTP_METHOD_HEAD, + HTTP_METHOD_POST, + HTTP_METHOD_PUT, + HTTP_METHOD_DELETE, + HTTP_METHOD_CONNECT, + HTTP_METHOD_OPTIONS, + HTTP_METHOD_TRACE, + HTTP_METHOD_PATCH, +} HTTP_Method; + +typedef struct { + HTTP_String name; + HTTP_String value; +} HTTP_Header; + +typedef struct { + HTTP_Method method; + HTTP_URL url; + int minor; + int num_headers; + HTTP_Header headers[HTTP_MAX_HEADERS]; + HTTP_String body; +} HTTP_Request; + +typedef struct { + int minor; + int status; + HTTP_String reason; + int num_headers; + HTTP_Header headers[HTTP_MAX_HEADERS]; + HTTP_String body; +} HTTP_Response; + +int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4); +int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6); +int http_parse_url (char *src, int len, HTTP_URL *url); +int http_parse_request (char *src, int len, HTTP_Request *req); +int http_parse_response (char *src, int len, HTTP_Response *res); + +int http_find_header (HTTP_Header *headers, int num_headers, HTTP_String name); +HTTP_String http_getqueryparam (HTTP_Request *req, HTTP_String name); +HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name); +HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name); + +typedef enum { + HTTP_MEMFUNC_MALLOC, + HTTP_MEMFUNC_FREE, +} HTTP_MemoryFuncTag; + +typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data); + +typedef struct { + + HTTP_MemoryFunc memfunc; + void *memfuncdata; + + unsigned long long curs; + + char* data; + unsigned int head; + unsigned int size; + unsigned int used; + unsigned int limit; + + char* read_target; + unsigned int read_target_size; + + int flags; +} HTTP_ByteQueue; + +typedef unsigned long long HTTP_ByteQueueOffset; + +#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) +#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) +#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) +#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) +#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) +#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) +#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) +#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) +#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) +#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) +#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) +#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) +#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) +#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) + +typedef enum { + HTTP_ENGINE_STATE_NONE = 0, + HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, + HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, + HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, + HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, + HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, +} HTTP_EngineState; + +typedef struct { + HTTP_EngineState state; + HTTP_ByteQueue input; + HTTP_ByteQueue output; + int numexch; + int reqsize; + int closing; + int keepalive; + HTTP_ByteQueueOffset response_offset; + HTTP_ByteQueueOffset content_length_offset; + HTTP_ByteQueueOffset content_length_value_offset; + union { + HTTP_Request req; + HTTP_Response res; + } result; +} HTTP_Engine; + +void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); +void http_engine_free (HTTP_Engine *eng); + +void http_engine_close (HTTP_Engine *eng); +HTTP_EngineState http_engine_state (HTTP_Engine *eng); + +const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove + +char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); +void http_engine_recvack (HTTP_Engine *eng, int num); +char* http_engine_sendbuf (HTTP_Engine *eng, int *len); +void http_engine_sendack (HTTP_Engine *eng, int num); + +HTTP_Request* http_engine_getreq (HTTP_Engine *eng); +HTTP_Response* http_engine_getres (HTTP_Engine *eng); + +void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor); +void http_engine_status (HTTP_Engine *eng, int status); +void http_engine_header (HTTP_Engine *eng, const char *src, int len); +void http_engine_body (HTTP_Engine *eng, void *src, int len); +void http_engine_bodycap (HTTP_Engine *eng, int mincap); +char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); +void http_engine_bodyack (HTTP_Engine *eng, int num); +void http_engine_done (HTTP_Engine *eng); +void http_engine_undo (HTTP_Engine *eng); +void http_global_init(void); +void http_global_free(void); + +typedef struct HTTP_Client HTTP_Client; + +typedef struct { + void *data0; + int data1; + int data2; +} HTTP_RequestHandle; + +HTTP_Client* http_client_init (void); +void http_client_free (HTTP_Client *client); +int http_client_request (HTTP_Client *client, HTTP_RequestHandle *handle); +int http_client_wait (HTTP_Client *client, HTTP_RequestHandle *handle); +void http_request_trace (HTTP_RequestHandle handle, bool trace); +void http_request_line (HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url); +void http_request_header (HTTP_RequestHandle handle, char *header, int len); +void http_request_body (HTTP_RequestHandle handle, char *body, int len); +void http_request_submit (HTTP_RequestHandle handle); +HTTP_Response* http_request_result (HTTP_RequestHandle handle); +void http_request_free (HTTP_RequestHandle handle); + +typedef struct { + void *data0; + int data1; + int data2; +} HTTP_ResponseHandle; + +typedef struct HTTP_Server HTTP_Server; + +HTTP_Server *http_server_init(HTTP_String addr, uint16_t port); + +HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, + uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key); + +void http_server_free (HTTP_Server *server); +int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandle *handle); +int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); +void http_response_status (HTTP_ResponseHandle res, int status); +void http_response_header (HTTP_ResponseHandle res, const char *fmt, ...); +void http_response_body (HTTP_ResponseHandle res, char *src, int len); +void http_response_bodycap (HTTP_ResponseHandle res, int mincap); +char* http_response_bodybuf (HTTP_ResponseHandle res, int *cap); +void http_response_bodyack (HTTP_ResponseHandle res, int num); +void http_response_undo (HTTP_ResponseHandle res); +void http_response_done (HTTP_ResponseHandle res); + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file); + +typedef struct HTTP_Router HTTP_Router; +typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; + +HTTP_Router* http_router_init (void); +void http_router_free (HTTP_Router *router); +void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res); +void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path); +void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*); +int http_serve (char *addr, int port, HTTP_Router *router); + + +#ifdef __cplusplus +} +#endif + +#endif /* HTTP_AMALGAMATION_H */ diff --git a/misc/amalg.py b/misc/amalg.py new file mode 100644 index 0000000..73dfc45 --- /dev/null +++ b/misc/amalg.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +""" +AmalgamationBuilder - Implementation with improved #line support +""" + +import re +import os +from typing import Set + +class AmalgamationBuilder: + def __init__(self): + self.body = "" + self.local_includes: Set[str] = set() # These should deduplicate items + self.global_includes: Set[str] = set() + + def add(self, filepath: str): + """Add a file to the amalgamation""" + try: + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + lines = f.readlines() + except FileNotFoundError: + print(f"Warning: File '{filepath}' not found, skipping") + return + + original_line_number = 1 + skipped_lines = 0 + first_content_line = True + + for line in lines: + # Check if the line is an include + if self._is_include_line(line): + include_info = self._extract_include_info(line) + if include_info: + filename, uses_angular_brackets = include_info + if uses_angular_brackets: + self.global_includes.add(filename) + else: + self.local_includes.add(filename) + + original_line_number += 1 + skipped_lines += 1 + continue + + # Check if the line is part of a header guard + if self._is_header_guard_line(line): + original_line_number += 1 + skipped_lines += 1 + continue + + # Skip empty lines at the beginning of files + if first_content_line and line.strip() == '': + original_line_number += 1 + skipped_lines += 1 + continue + + # Add #line directive when starting a new file or after skipping significant content + if first_content_line or skipped_lines > 0: + # Only add #line if we skipped lines or it's a new file + # self.body += f'#line {original_line_number} "{filepath}"\n' + first_content_line = False + skipped_lines = 0 + + # Append the current line to body + self.body += line + original_line_number += 1 + + def _is_include_line(self, line: str) -> bool: + """Check if a line is an #include directive""" + # Skip obviously non-include lines for performance + if '#include' not in line: + return False + + # Check if this line is commented out + if self._is_line_commented(line): + return False + + # Regex to match include pattern + include_pattern = re.compile(r'^\s*#\s*include\s*[<"][^>"]+[>"]') + return bool(include_pattern.match(line.strip())) + + def _extract_include_info(self, line: str) -> tuple[str, bool] | None: + """Extract filename and bracket type from include line""" + # Pattern to capture include details + include_pattern = re.compile(r'^\s*#\s*include\s*([<"])([^>"]+)([>"])') + match = include_pattern.match(line.strip()) + + if match: + open_delim = match.group(1) + filename = match.group(2) + close_delim = match.group(3) + + # Validate matching delimiters + if (open_delim == '<' and close_delim == '>') or \ + (open_delim == '"' and close_delim == '"'): + uses_angular_brackets = (open_delim == '<') + return filename, uses_angular_brackets + + return None + + def _is_header_guard_line(self, line: str) -> bool: + """Check if a line is part of a header guard (assumes _INCLUDED suffix)""" + stripped = line.strip() + + # Simplified header guard patterns for _INCLUDED suffix + header_guard_patterns = [ + re.compile(r'#ifndef\s+\w+_INCLUDED'), + re.compile(r'#define\s+\w+_INCLUDED'), + re.compile(r'#endif\s*//.*_INCLUDED'), + re.compile(r'#endif\s*/\*.*_INCLUDED.*\*/'), + ] + + return any(pattern.match(stripped) for pattern in header_guard_patterns) + + def _is_line_commented(self, line: str) -> bool: + """Basic check if line is commented out""" + include_pos = line.find('#include') + if include_pos == -1: + return False + + # Check for // comment before #include + comment_pos = line.find('//') + if comment_pos != -1 and comment_pos < include_pos: + return True + + # Check for /* comment before #include (basic case) + before_include = line[:include_pos] + if '/*' in before_include and '*/' not in before_include: + return True + + return False + + def result(self) -> str: + """Generate the final amalgamated result""" + result = "" + + # Write all global includes at the top + for item in sorted(self.global_includes): + result += f'#include <{item}>\n' + + if self.global_includes: + result += "\n" # Add spacing after includes + + # Add the body content + result += self.body + + return result + + +def main(): + """Example usage of AmalgamationBuilder""" + + # Build the header + print("Building header...") + header_builder = AmalgamationBuilder() + + header_files = [ + "src/basic.h", + "src/parse.h", + "src/engine.h", + "src/client.h", + "src/server.h", + "src/cert.h", + "src/router.h" + ] + + for file in header_files: + header_builder.add(file) + + header = header_builder.result() + + # Build the source + print("Building source...") + source_builder = AmalgamationBuilder() + + source_files = [ + "src/cert.h", + "src/socket.h", + "src/basic.c", + "src/parse.c", + "src/engine.c", + "src/socket.c", + "src/client.c", + "src/server.c", + "src/router.c" + ] + + for file in source_files: + source_builder.add(file) + + source = source_builder.result() + + # Write results + print("Writing http.h...") + with open("http.h", 'w', encoding='utf-8') as f: + f.write('/*\n') + f.write(' * HTTP Library - Amalgamated Header\n') + f.write(' * Generated automatically - do not edit manually\n') + f.write(' */\n\n') + f.write('#ifndef HTTP_AMALGAMATION_H\n') + f.write('#define HTTP_AMALGAMATION_H\n\n') + f.write('#ifdef __cplusplus\n') + f.write('extern "C" {\n') + f.write('#endif\n\n') + f.write(header) + f.write('\n#ifdef __cplusplus\n') + f.write('}\n') + f.write('#endif\n\n') + f.write('#endif /* HTTP_AMALGAMATION_H */\n') + + print("Writing http.c...") + with open("http.c", 'w', encoding='utf-8') as f: + f.write('/*\n') + f.write(' * HTTP Library - Amalgamated Source\n') + f.write(' * Generated automatically - do not edit manually\n') + f.write(' */\n\n') + f.write('#include "http.h"\n\n') + f.write(source) + + print("Amalgamation complete!") + print(f"Header: {len(header.splitlines())} lines") + print(f"Source: {len(source.splitlines())} lines") + + # Show some statistics + print(f"\nHeader global includes: {len(header_builder.global_includes)}") + print(f"Source global includes: {len(source_builder.global_includes)}") + print(f"Local includes found: {len(header_builder.local_includes | source_builder.local_includes)}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/basic.c b/src/basic.c new file mode 100644 index 0000000..2355a9d --- /dev/null +++ b/src/basic.c @@ -0,0 +1,96 @@ +#include "basic.h" +#include +#include + +int http_streq(HTTP_String s1, HTTP_String s2) +{ + if (s1.len != s2.len) + return 0; + for (int i = 0; i < s1.len; i++) + if (s1.ptr[i] != s2.ptr[i]) + return 0; + return 1; +} + +static char to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + +int http_streqcase(HTTP_String s1, HTTP_String s2) +{ + if (s1.len != s2.len) + return 0; + for (int i = 0; i < s1.len; i++) + if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) + return 0; + return 1; +} + +HTTP_String http_trim(HTTP_String s) +{ + int i = 0; + while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) + i++; + + if (i == s.len) { + s.ptr = NULL; + s.len = 0; + } else { + s.ptr += i; + s.len -= i; + while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') + s.len--; + } + + return s; +} + +static bool is_printable(char c) +{ + return c >= ' ' && c <= '~'; +} + +#include +void print_bytes(HTTP_String prefix, HTTP_String src) +{ + if (src.len == 0) + return; + + FILE *stream = stdout; + + bool new_line = true; + int cur = 0; + for (;;) { + int start = cur; + + while (cur < src.len && is_printable(src.ptr[cur])) + cur++; + + if (new_line) { + fwrite(prefix.ptr, 1, prefix.len, stream); + new_line = false; + } + + fwrite(src.ptr + start, 1, cur - start, stream); + + if (cur == src.len) + break; + + if (src.ptr[cur] == '\n') { + putc('\\', stream); + putc('n', stream); + putc('\n', stream); + new_line = true; + } else if (src.ptr[cur] == '\r') { + putc('\\', stream); + putc('r', stream); + } else { + putc('.', stream); + } + cur++; + } + putc('\n', stream); +} diff --git a/src/basic.h b/src/basic.h new file mode 100644 index 0000000..7feda81 --- /dev/null +++ b/src/basic.h @@ -0,0 +1,18 @@ +#ifndef BASIC_INCLUDED +#define BASIC_INCLUDED +#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1}) +#define HTTP_CEIL(X, Y) (((X) + (Y) - 1) / (Y)) + +typedef struct { + char *ptr; + long len; +} HTTP_String; + +int http_streq (HTTP_String s1, HTTP_String s2); +int http_streqcase (HTTP_String s1, HTTP_String s2); +HTTP_String http_trim (HTTP_String s); + +#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0])) +#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} + +#endif // BASIC_INCLUDED diff --git a/src/cert.c b/src/cert.c new file mode 100644 index 0000000..bcc5a19 --- /dev/null +++ b/src/cert.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static EVP_PKEY *generate_rsa_key_pair(int key_bits) +{ + EVP_PKEY_CTX *ctx; + EVP_PKEY *pkey; + + // Create the context for key generation + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) + return NULL; + + if (EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) +{ + X509 *x509 = X509_new(); + if (!x509) + return NULL; + + // Set version (version 3) + X509_set_version(x509, 2); + + // Set serial number + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + + // Set validity period + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year + + // Set public key + X509_set_pubkey(x509, pkey); + + // Set subject name + X509_NAME *name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); + + // Set issuer name (same as subject for self-signed) + X509_set_issuer_name(x509, name); + + if (!X509_sign(x509, pkey, EVP_sha256())) { + X509_free(x509); + return NULL; + } + + return x509; +} + +static int save_private_key(EVP_PKEY *pkey, const char *filename) +{ + FILE *fp = fopen(filename, "wb"); + if (!fp) + return -1; + + // Write private key in PEM format + if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +static int cert_save(X509 *x509, const char *filename) +{ + FILE *fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Error opening file for certificate: %s\n", filename); + return -1; + } + + // Write certificate in PEM format + if (!PEM_write_X509(fp, x509)) { + fprintf(stderr, "Error writing certificate\n"); + fclose(fp); + return -1; + } + + fclose(fp); + printf("Certificate saved to: %s\n", filename); + return 0; +} + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + EVP_PKEY *pkey = generate_rsa_key_pair(2048); + if (pkey == NULL) + return -1; + + X509 *x509 = create_certificate(pkey, C, O, CN, 1) + if (x509 == NULL) { + EVP_PKEY_free(pkey); + return -1; + } + + if (save_private_key(pkey, key_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + if (save_certificate(x509, cert_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + X509_free(x509); + EVP_PKEY_free(pkey); + return 0; +} \ No newline at end of file diff --git a/src/cert.h b/src/cert.h new file mode 100644 index 0000000..ebb9642 --- /dev/null +++ b/src/cert.h @@ -0,0 +1,9 @@ +#ifndef CERT_INCLUDED +#define CERT_INCLUDED + +#include "basic.h" + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file); + +#endif // CERT_INCLUDED \ No newline at end of file diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..4e5b58d --- /dev/null +++ b/src/client.c @@ -0,0 +1,373 @@ +#include "client.h" +#include "socket.h" +#include "engine.h" +#include +#include +#include +#include + +// TODO +#include +#define ERROR printf("error at %s:%d\n", __FILE__, __LINE__); + +#define CLIENT_MAX_CONNS 256 + +typedef enum { + CLIENT_CONNECTION_FREE, + CLIENT_CONNECTION_INIT, + CLIENT_CONNECTION_WAIT, + CLIENT_CONNECTION_DONE, +} ClientConnectionState; + +typedef struct { + ClientConnectionState state; + uint16_t gen; + Socket socket; + HTTP_Engine engine; + bool trace; +} ClientConnection; + +struct HTTP_Client { + SocketGroup group; + int num_conns; + ClientConnection conns[CLIENT_MAX_CONNS]; + + int ready_head; + int ready_count; + int ready[CLIENT_MAX_CONNS]; +}; + +// Rename the memory function +static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { + (void)data; + switch (tag) { + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +void http_global_init(void) +{ + socket_global_init(); +} + +void http_global_free(void) +{ + socket_global_free(); +} + +HTTP_Client *http_client_init(void) +{ + HTTP_Client *client = malloc(sizeof(HTTP_Client)); + if (client == NULL) + return NULL; + + if (socket_group_init(&client->group) < 0) { + free(client); + return NULL; + } + + for (int i = 0; i < CLIENT_MAX_CONNS; i++) { + client->conns[i].state = CLIENT_CONNECTION_FREE; + client->conns[i].gen = 1; + } + + client->num_conns = 0; + client->ready_head = 0; + client->ready_count = 0; + + return client; +} + +void http_client_free(HTTP_Client *client) +{ + for (int i = 0, j = 0; j < client->num_conns; i++) { + + if (client->conns[i].state == CLIENT_CONNECTION_FREE) + continue; + j++; + + // TODO + } + + socket_group_free(&client->group); + free(client); +} + +int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle) +{ + if (client->num_conns == CLIENT_MAX_CONNS) + return -1; + + int i = 0; + while (client->conns[i].state != CLIENT_CONNECTION_FREE) + i++; + + client->conns[i].trace = false; + client->conns[i].state = CLIENT_CONNECTION_INIT; + http_engine_init(&client->conns[i].engine, 1, client_memfunc, NULL); + + client->num_conns++; + + *handle = (HTTP_RequestHandle) { client, i, client->conns[i].gen }; + return 0; +} + +static void client_connection_update(ClientConnection *conn) +{ + HTTP_ASSERT(conn->state == CLIENT_CONNECTION_WAIT); + + socket_update(&conn->socket); + + while (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) { + + HTTP_EngineState engine_state; + + engine_state = http_engine_state(&conn->engine); + + if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { + int len; + char *buf; + buf = http_engine_recvbuf(&conn->engine, &len); + if (buf) { + int ret = socket_read(&conn->socket, buf, len); + if (conn->trace) + print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret }); + http_engine_recvack(&conn->engine, ret); + } + } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { + int len; + char *buf; + buf = http_engine_sendbuf(&conn->engine, &len); + if (buf) { + int ret = socket_write(&conn->socket, buf, len); + if (conn->trace) + print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret }); + http_engine_sendack(&conn->engine, ret); + } + } + + engine_state = http_engine_state(&conn->engine); + + if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED || + engine_state == HTTP_ENGINE_STATE_CLIENT_READY) + socket_close(&conn->socket); + } + + if (socket_state(&conn->socket) == SOCKET_STATE_DIED) + conn->state = CLIENT_CONNECTION_DONE; +} + +int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle) +{ + while (client->ready_count == 0) { + + int num_polled = 0; + int indices[CLIENT_MAX_CONNS]; + struct pollfd polled[CLIENT_MAX_CONNS]; + + for (int i = 0, j = 0; j < client->num_conns; i++) { + + HTTP_ASSERT(i < CLIENT_MAX_CONNS); + ClientConnection *conn = &client->conns[i]; + + if (conn->state == CLIENT_CONNECTION_FREE) + continue; + j++; + + int events = 0; + if (conn->state == CLIENT_CONNECTION_WAIT) { + switch (conn->socket.event) { + case SOCKET_WANT_READ : events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; + case SOCKET_WANT_NONE : events = 0; break; + } + } + + if (events) { + indices[num_polled] = i; + polled[num_polled].fd = conn->socket.fd; + polled[num_polled].events = events; + polled[num_polled].revents = 0; + num_polled++; + } + } + + if (num_polled == 0) + return -1; + + poll(polled, num_polled, -1); + + for (int i = 0; i < num_polled; i++) { + + int connidx = indices[i]; + ClientConnection *conn = &client->conns[connidx]; + + if (conn->state != CLIENT_CONNECTION_WAIT) + continue; + + if (polled[i].revents == 0) + continue; + + // TODO: handle error revents + + client_connection_update(conn); + + if (conn->state == CLIENT_CONNECTION_DONE) { + int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; + client->ready[tail] = connidx; + client->ready_count++; + } + } + } + + int index = client->ready[client->ready_head]; + client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS; + client->ready_count--; + *handle = (HTTP_RequestHandle) { client, index, client->conns[index].gen }; + return 0; +} + +static ClientConnection *handle2clientconn(HTTP_RequestHandle handle) +{ + if (handle.data0 == NULL) + return NULL; + + HTTP_Client *client = handle.data0; + + if (handle.data1 >= CLIENT_MAX_CONNS) + return NULL; + + ClientConnection *conn = &client->conns[handle.data1]; + + if (handle.data2 != conn->gen) + return NULL; + + return conn; +} + +void http_request_trace(HTTP_RequestHandle handle, bool trace) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + conn->trace = trace; +} + +void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + HTTP_Client *client = handle.data0; + + HTTP_URL parsed_url; + int ret = http_parse_url(url.ptr, url.len, &parsed_url); + if (ret != url.len) { + // TODO + ERROR; + return; + } + + bool secure = false; + if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { + secure = true; + } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { + // TODO + ERROR; + return; + } + + int port = parsed_url.authority.port; + if (port == 0) { + if (secure) + port = 443; + else + port = 80; + } + + SocketGroup *group = secure ? &client->group : NULL; + switch (parsed_url.authority.host.mode) { + case HTTP_HOST_MODE_IPV4: socket_connect_ipv4(&conn->socket, group, parsed_url.authority.host.ipv4, port); break; + case HTTP_HOST_MODE_IPV6: socket_connect_ipv6(&conn->socket, group, parsed_url.authority.host.ipv6, port); break; + case HTTP_HOST_MODE_NAME: socket_connect (&conn->socket, group, parsed_url.authority.host.name, port); break; + + case HTTP_HOST_MODE_VOID: + // TODO + ERROR; + return; + } + + http_engine_url(&conn->engine, method, url, 1); +} + +void http_request_header(HTTP_RequestHandle handle, char *header, int len) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + http_engine_header(&conn->engine, header, len); +} + +void http_request_body(HTTP_RequestHandle handle, char *body, int len) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + http_engine_body(&conn->engine, body, len); +} + +void http_request_submit(HTTP_RequestHandle handle) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_INIT) + return; + + http_engine_done(&conn->engine); + conn->state = CLIENT_CONNECTION_WAIT; +} + +HTTP_Response *http_request_result(HTTP_RequestHandle handle) +{ + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return NULL; + if (conn->state != CLIENT_CONNECTION_DONE) + return NULL; + HTTP_EngineState engine_state = http_engine_state(&conn->engine); + if (engine_state != HTTP_ENGINE_STATE_CLIENT_READY) + return NULL; + return http_engine_getres(&conn->engine); +} + +void http_request_free(HTTP_RequestHandle handle) +{ + HTTP_Client *client = handle.data0; + ClientConnection *conn = handle2clientconn(handle); + if (conn == NULL) + return; + if (conn->state != CLIENT_CONNECTION_DONE) + return; + http_engine_free(&conn->engine); + socket_free(&conn->socket); + conn->state = CLIENT_CONNECTION_FREE; + client->num_conns--; +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..7129ebc --- /dev/null +++ b/src/client.h @@ -0,0 +1,30 @@ +#ifndef CLIENT_INCLUDED +#define CLIENT_INCLUDED + +#include +#include "parse.h" + +void http_global_init(void); +void http_global_free(void); + +typedef struct HTTP_Client HTTP_Client; + +typedef struct { + void *data0; + int data1; + int data2; +} HTTP_RequestHandle; + +HTTP_Client* http_client_init (void); +void http_client_free (HTTP_Client *client); +int http_client_request (HTTP_Client *client, HTTP_RequestHandle *handle); +int http_client_wait (HTTP_Client *client, HTTP_RequestHandle *handle); +void http_request_trace (HTTP_RequestHandle handle, bool trace); +void http_request_line (HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url); +void http_request_header (HTTP_RequestHandle handle, char *header, int len); +void http_request_body (HTTP_RequestHandle handle, char *body, int len); +void http_request_submit (HTTP_RequestHandle handle); +HTTP_Response* http_request_result (HTTP_RequestHandle handle); +void http_request_free (HTTP_RequestHandle handle); + +#endif // CLIENT_INCLUDED diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..02114bc --- /dev/null +++ b/src/engine.c @@ -0,0 +1,998 @@ +#include +#include +#include +#include +#include // TODO: remove some of these headers +#include +#include +#include +#include +#include +#include "basic.h" +#include "engine.h" +#include "byte_queue.h" + +// This is the implementation of a byte queue useful +// for systems that need to process engs of bytes. +// +// It features sticky errors, a zero-copy interface, +// and a safe mechanism to patch previously written +// bytes. +// +// Only up to 4GB of data can be stored at once. + +enum { + BYTE_QUEUE_ERROR = 1 << 0, + BYTE_QUEUE_READ = 1 << 1, + BYTE_QUEUE_WRITE = 1 << 2, +}; + +static void* +callback_malloc(HTTP_ByteQueue *queue, int len) +{ + return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata); +} + +static void +callback_free(HTTP_ByteQueue *queue, void *ptr, int len) +{ + queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata); +} + +// Initialize the queue +static void +byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata) +{ + queue->flags = 0; + queue->head = 0; + queue->size = 0; + queue->used = 0; + queue->curs = 0; + queue->limit = limit; + queue->data = NULL; + queue->read_target = NULL; + queue->memfunc = memfunc; + queue->memfuncdata = memfuncdata; +} + +// Deinitialize the queue +static void +byte_queue_free(HTTP_ByteQueue *queue) +{ + if (queue->read_target) { + if (queue->read_target != queue->data) + callback_free(queue, queue->read_target, queue->read_target_size); + queue->read_target = NULL; + queue->read_target_size = 0; + } + + callback_free(queue, queue->data, queue->size); + queue->data = NULL; +} + +static int +byte_queue_error(HTTP_ByteQueue *queue) +{ + return queue->flags & BYTE_QUEUE_ERROR; +} + +static void +byte_queue_setlimit(HTTP_ByteQueue *queue, unsigned int value) +{ + queue->limit = value; +} + +static int +byte_queue_empty(HTTP_ByteQueue *queue) +{ + return queue->used == 0; +} + +// Start a read operation on the queue. +// +// This function returnes the pointer to the memory region containing the bytes +// to read. Callers can't read more than [*len] bytes from it. To complete the +// read, the [byte_queue_read_ack] function must be called with the number of +// bytes that were acknowledged by the caller. +// +// Note: +// - You can't have more than one pending read. +static char* +byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) +{ + if (queue->flags & BYTE_QUEUE_ERROR) { + *len = 0; + return NULL; + } + + HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); + queue->flags |= BYTE_QUEUE_READ; + queue->read_target = queue->data; + queue->read_target_size = queue->size; + + *len = queue->used; + if (queue->data == NULL) + return NULL; + return queue->data + queue->head; +} + +// Complete a previously started operation on the queue. +static void +byte_queue_read_ack(HTTP_ByteQueue *queue, int num) +{ + HTTP_ASSERT(num >= 0); + + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_READ) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_READ; + + HTTP_ASSERT((unsigned int) num <= queue->used); + queue->head += (unsigned int) num; + queue->used -= (unsigned int) num; + queue->curs += (unsigned int) num; + + if (queue->read_target) { + if (queue->read_target != queue->data) + callback_free(queue, queue->read_target, queue->read_target_size); + queue->read_target = NULL; + queue->read_target_size = 0; + } +} + +static char* +byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) +{ + if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) { + *cap = 0; + return NULL; + } + + HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); + queue->flags |= BYTE_QUEUE_WRITE; + + unsigned int ucap = queue->size - (queue->head + queue->used); + if (ucap > INT_MAX) ucap = INT_MAX; + + *cap = (int) ucap; + return queue->data + (queue->head + queue->used); +} + +static void +byte_queue_write_ack(HTTP_ByteQueue *queue, int num) +{ + HTTP_ASSERT(num >= 0); + + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_WRITE) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_WRITE; + queue->used += (unsigned int) num; +} + +// Sets the minimum capacity for the next write operation +// and returns 1 if the content of the queue was moved, else +// 0 is returned. +// +// You must not call this function while a write is pending. +// In other words, you must do this: +// +// byte_queue_write_setmincap(queue, mincap); +// dst = byte_queue_write_buf(queue, &cap); +// ... +// byte_queue_write_ack(num); +// +// And NOT this: +// +// dst = byte_queue_write_buf(queue, &cap); +// byte_queue_write_setmincap(queue, mincap); <-- BAD +// ... +// byte_queue_write_ack(num); +// +static int +byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) +{ + HTTP_ASSERT(mincap >= 0); + unsigned int umincap = (unsigned int) mincap; + + // Sticky error + if (queue->flags & BYTE_QUEUE_ERROR) + return 0; + + // In general, the queue's contents look like this: + // + // size + // v + // [___xxxxxxxxxxxx________] + // ^ ^ ^ + // 0 head head + used + // + // This function needs to make sure that at least [mincap] + // bytes are available on the right side of the content. + // + // We have 3 cases: + // + // 1) If there is enough memory already, this function doesn't + // need to do anything. + // + // 2) If there isn't enough memory on the right but there is + // enough free memory if we cound the left unused region, + // then the content is moved back to the + // start of the buffer. + // + // 3) If there isn't enough memory considering both sides, this + // function needs to allocate a new buffer. + // + // If there are pending read or write operations, the application + // is holding pointers to the buffer, so we need to make sure + // to not invalidate them. The only real problem is pending reads + // since this function can only be called before starting a write + // opearation. + // + // To avoid invalidating the read pointer when we allocate a new + // buffer, we don't free the old buffer. Instead, we store the + // pointer in the "old" field so that the read ack function can + // free it. + // + // To avoid invalidating the pointer when we are moving back the + // content since there is enough memory at the start of the buffer, + // we just avoid that. Even if there is enough memory considering + // left and right free regions, we allocate a new buffer. + + HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); + + unsigned int total_free_space = queue->size - queue->used; + unsigned int free_space_after_data = queue->size - queue->used - queue->head; + + int moved = 0; + if (free_space_after_data < umincap) { + + if (total_free_space < umincap || (queue->read_target == queue->data)) { + // Resize required + + if (queue->used + umincap > queue->limit) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + unsigned int size; + if (queue->size > UINT32_MAX / 2) + size = UINT32_MAX; + else + size = 2 * queue->size; + + if (size < queue->used + umincap) + size = queue->used + umincap; + + if (size > queue->limit) + size = queue->limit; + + char *data = callback_malloc(queue, size); + if (!data) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + if (queue->used > 0) + memcpy(data, queue->data + queue->head, queue->used); + + if (queue->read_target != queue->data) + callback_free(queue, queue->data, queue->size); + + queue->data = data; + queue->head = 0; + queue->size = size; + + } else { + // Move required + memmove(queue->data, queue->data + queue->head, queue->used); + queue->head = 0; + } + + moved = 1; + } + + return moved; +} + +static HTTP_ByteQueueOffset +byte_queue_offset(HTTP_ByteQueue *queue) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return (HTTP_ByteQueueOffset) { 0 }; + return (HTTP_ByteQueueOffset) { queue->curs + queue->used }; +} + +static unsigned int +byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off) +{ + return queue->curs + queue->used - off; +} + +static void +byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, + char *src, unsigned int len) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + // Check that the offset is in range + HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used); + + // Check that the length is in range + HTTP_ASSERT(len <= queue->used - (off - queue->curs)); + + // Perform the patch + char *dst = queue->data + queue->head + (off - queue->curs); + memcpy(dst, src, len); +} + +static void +byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + unsigned long long num = (queue->curs + queue->used) - offset; + HTTP_ASSERT(num <= queue->used); + + queue->used -= num; +} + +static void +byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len) +{ + if (str == NULL) str = ""; + if (len < 0) len = strlen(str); + + int cap; + byte_queue_write_setmincap(queue, len); + char *dst = byte_queue_write_buf(queue, &cap); + if (dst) memcpy(dst, str, len); + byte_queue_write_ack(queue, len); +} + +static void +byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + va_list args2; + va_copy(args2, args); + + int cap; + byte_queue_write_setmincap(queue, 128); + char *dst = byte_queue_write_buf(queue, &cap); + + int len = vsnprintf(dst, cap, fmt, args); + if (len < 0) { + queue->flags |= BYTE_QUEUE_ERROR; + va_end(args2); + va_end(args); + return; + } + + if (len > cap) { + byte_queue_write_ack(queue, 0); + byte_queue_write_setmincap(queue, len+1); + dst = byte_queue_write_buf(queue, &cap); + vsnprintf(dst, cap, fmt, args2); + } + + byte_queue_write_ack(queue, len); + + va_end(args2); + va_end(args); +} + +static void +byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + byte_queue_write_fmt2(queue, fmt, args); + va_end(args); +} + +#define TEN_SPACES " " + +void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) +{ + if (client) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; + else + eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; + + eng->closing = 0; + eng->numexch = 0; + + byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata); + byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata); +} + +void http_engine_free(HTTP_Engine *eng) +{ + byte_queue_free(&eng->input); + byte_queue_free(&eng->output); + eng->state = HTTP_ENGINE_STATE_NONE; +} + +void http_engine_close(HTTP_Engine *eng) +{ + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + else + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; +} + +HTTP_EngineState http_engine_state(HTTP_Engine *eng) +{ + return eng->state; +} + +const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove + switch (state) { + case HTTP_ENGINE_STATE_NONE: return "NONE"; + case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL"; + case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR"; + case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF"; + case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK"; + case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF"; + case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK"; + case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY"; + case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED"; + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF"; + case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS"; + case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR"; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF"; + case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK"; + case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED"; + default: return "UNKNOWN"; + } +} + +char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) { + *cap = 0; + return NULL; + } + + eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF; + eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK; + + byte_queue_write_setmincap(&eng->input, 1<<9); + if (byte_queue_error(&eng->input)) { + *cap = 0; + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + else + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + return NULL; + } + + return byte_queue_write_buf(&eng->input, cap); +} + +static int +should_keep_alive(HTTP_Engine *eng) +{ + HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); + +#if 0 + // If the parent system doesn't want us to reuse + // the connection, we certainly can't keep alive. + if ((eng->state & TINYHTTP_STREAM_REUSE) == 0) + return 0; +#endif + + if (eng->numexch >= 100) // TODO: Make this a parameter + return 0; + + HTTP_Request *req = &eng->result.req; + + // If the client is using HTTP/1.0, we can't + // keep alive. + if (req->minor == 0) + return 0; + + // TODO: This assumes "Connection" can only hold a single token, + // but this is not true. + int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection")); + if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) + return 0; + + return 1; +} + +static void process_incoming_request(HTTP_Engine *eng) +{ + HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK + || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK + || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF + || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); + + char *src; + int len; + src = byte_queue_read_buf(&eng->input, &len); + + int ret = http_parse_request(src, len, &eng->result.req); + + if (ret == 0) { + byte_queue_read_ack(&eng->input, 0); + eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; + return; + } + + if (ret < 0) { + byte_queue_read_ack(&eng->input, 0); + byte_queue_write(&eng->output, + "HTTP/1.1 400 Bad Request\r\n" + "Connection: Close\r\n" + "Content-Length: 0\r\n" + "\r\n", -1 + ); + if (byte_queue_error(&eng->output)) + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + else { + eng->closing = 1; + eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; + } + return; + } + + HTTP_ASSERT(ret > 0); + + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; + eng->reqsize = ret; + eng->keepalive = should_keep_alive(eng); + eng->response_offset = byte_queue_offset(&eng->output); +} + +void http_engine_recvack(HTTP_Engine *eng, int num) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0) + return; + + byte_queue_write_ack(&eng->input, num); + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { + + char *src; + int len; + src = byte_queue_read_buf(&eng->input, &len); + + int ret = http_parse_response(src, len, &eng->result.res); + + if (ret == 0) { + byte_queue_read_ack(&eng->input, 0); + eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; + return; + } + + if (ret < 0) { + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; + } + + HTTP_ASSERT(ret > 0); + + eng->state = HTTP_ENGINE_STATE_CLIENT_READY; + + } else { + process_incoming_request(eng); + } +} + +char *http_engine_sendbuf(HTTP_Engine *eng, int *len) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) { + *len = 0; + return NULL; + } + + eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF; + eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK; + + return byte_queue_read_buf(&eng->output, len); +} + +void http_engine_sendack(HTTP_Engine *eng, int num) +{ + if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK && + eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK) + return; + + byte_queue_read_ack(&eng->output, num); + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { + if (byte_queue_empty(&eng->output)) + eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; + else + eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; + } else { + if (byte_queue_empty(&eng->output)) { + if (!eng->closing && eng->keepalive) + process_incoming_request(eng); + else + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + } else + eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; + } +} + +HTTP_Request *http_engine_getreq(HTTP_Engine *eng) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0) + return NULL; + return &eng->result.req; +} + +HTTP_Response *http_engine_getres(HTTP_Engine *eng) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0) + return NULL; + return &eng->result.res; +} + +void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor) +{ + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) + return; + + eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients + + HTTP_URL parsed_url; + int ret = http_parse_url(url.ptr, url.len, &parsed_url); + if (ret != url.len) { + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR; + return; + } + + HTTP_String method_and_space = HTTP_STR("???"); + switch (method) { + case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break; + case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break; + case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break; + case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break; + case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break; + case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break; + case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break; + case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break; + case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break; + } + + HTTP_String path = parsed_url.path; + if (path.len == 0) + path = HTTP_STR("/"); + + byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len); + byte_queue_write(&eng->output, path.ptr, path.len); + byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len); + byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1); + byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len); + if (parsed_url.authority.port > 0) + byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port); + byte_queue_write(&eng->output, "\r\n", 2); + + eng->keepalive = 1; // TODO + + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER; +} + + +static const char* +get_status_text(int code) +{ + switch(code) { + + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "Switch Proxy"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 420: return "Enhance your calm"; + case 422: return "Unprocessable Entity"; + case 426: return "Upgrade Required"; + case 429: return "Too many requests"; + case 431: return "Request Header Fields Too Large"; + case 449: return "Retry With"; + case 451: return "Unavailable For Legal Reasons"; + + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 509: return "Bandwidth Limit Exceeded"; + } + return "???"; +} + +void http_engine_status(HTTP_Engine *eng, int status) +{ + if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS) + return; + + byte_queue_write_fmt(&eng->output, + "HTTP/1.1 %d %s\r\n", + status, get_status_text(status)); + + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER; +} + +void http_engine_header(HTTP_Engine *eng, const char *src, int len) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) + return; + + if (len < 0) len = strlen(src); + + // TODO: Check that the header is valid + + byte_queue_write(&eng->output, src, len); + byte_queue_write(&eng->output, "\r\n", 2); +} + +void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) + return; + + // TODO: Check that the header is valid + + byte_queue_write_fmt2(&eng->output, fmt, args); + byte_queue_write(&eng->output, "\r\n", 2); +} + +void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + http_engine_header_fmt2(eng, fmt, args); + va_end(args); +} + +static void +complete_message_head(HTTP_Engine *eng) +{ + if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); + else byte_queue_write(&eng->output, "Connection: Close\r\n", -1); + + byte_queue_write(&eng->output, "Content-Length: ", -1); + eng->content_length_value_offset = byte_queue_offset(&eng->output); + byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1); + + byte_queue_write(&eng->output, "\r\n", -1); + eng->content_length_offset = byte_queue_offset(&eng->output); +} + +static void complete_message_body(HTTP_Engine *eng) +{ + unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); + + if (content_length > UINT32_MAX) { + // TODO + } + + char tmp[10]; + + tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; + tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; + tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; + tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; + tmp[4] = '0' + content_length / 100000; content_length %= 100000; + tmp[5] = '0' + content_length / 10000; content_length %= 10000; + tmp[6] = '0' + content_length / 1000; content_length %= 1000; + tmp[7] = '0' + content_length / 100; content_length %= 100; + tmp[8] = '0' + content_length / 10; content_length %= 10; + tmp[9] = '0' + content_length; + + int i = 0; + while (i < 9 && tmp[i] == '0') + i++; + + byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); +} + +void http_engine_body(HTTP_Engine *eng, void *src, int len) +{ + if (len < 0) len = strlen(src); + + http_engine_bodycap(eng, len); + int cap; + char *buf = http_engine_bodybuf(eng, &cap); + if (buf) { + memcpy(buf, src, len); + http_engine_bodyack(eng, len); + } +} + +static void ensure_body_entered(HTTP_Engine *eng) +{ + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { + + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; + } + + } else { + + if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; + } + } +} + +void http_engine_bodycap(HTTP_Engine *eng, int mincap) +{ + ensure_body_entered(eng); + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) + return; + + byte_queue_write_setmincap(&eng->output, mincap); +} + +char *http_engine_bodybuf(HTTP_Engine *eng, int *cap) +{ + ensure_body_entered(eng); + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) { + *cap = 0; + return NULL; + } + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK; + else + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK; + + return byte_queue_write_buf(&eng->output, cap); +} + +void http_engine_bodyack(HTTP_Engine *eng, int num) +{ + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK) + return; + + byte_queue_write_ack(&eng->output, num); + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; + else + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; +} + +void http_engine_done(HTTP_Engine *eng) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) + return; + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { + + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; + } + + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; + } + + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF) + complete_message_body(eng); + + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) { + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; + } + + if (byte_queue_error(&eng->output)) { + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; + } + + eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; + + } else { + + if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; + } + + if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) + complete_message_body(eng); + + if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) { + byte_queue_remove_from_offset(&eng->output, eng->response_offset); + byte_queue_write(&eng->output, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Length: 0\r\n" + "Connection: Close\r\n" + "\r\n", + -1 + ); + } + + if (byte_queue_error(&eng->output)) { + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + return; + } + + byte_queue_read_ack(&eng->input, eng->reqsize); + eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; + } +} + +void http_engine_undo(HTTP_Engine *eng) +{ + if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) + return; + + byte_queue_write_ack(&eng->output, 0); + byte_queue_remove_from_offset(&eng->output, eng->response_offset); + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; + else + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; +} diff --git a/src/engine.h b/src/engine.h new file mode 100644 index 0000000..2e1c875 --- /dev/null +++ b/src/engine.h @@ -0,0 +1,118 @@ +#ifndef HTTP_ENGINE_INCLUDED +#define HTTP_ENGINE_INCLUDED +#include "parse.h" + +typedef enum { + HTTP_MEMFUNC_MALLOC, + HTTP_MEMFUNC_FREE, +} HTTP_MemoryFuncTag; + +typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data); + +typedef struct { + + HTTP_MemoryFunc memfunc; + void *memfuncdata; + + unsigned long long curs; + + char* data; + unsigned int head; + unsigned int size; + unsigned int used; + unsigned int limit; + + char* read_target; + unsigned int read_target_size; + + int flags; +} HTTP_ByteQueue; + +typedef unsigned long long HTTP_ByteQueueOffset; + +#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) +#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) +#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) +#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) +#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) +#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) +#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) +#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) +#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) +#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) +#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) +#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) +#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) +#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) + +typedef enum { + HTTP_ENGINE_STATE_NONE = 0, + HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, + HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, + HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, + HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, + HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, +} HTTP_EngineState; + +typedef struct { + HTTP_EngineState state; + HTTP_ByteQueue input; + HTTP_ByteQueue output; + int numexch; + int reqsize; + int closing; + int keepalive; + HTTP_ByteQueueOffset response_offset; + HTTP_ByteQueueOffset content_length_offset; + HTTP_ByteQueueOffset content_length_value_offset; + union { + HTTP_Request req; + HTTP_Response res; + } result; +} HTTP_Engine; + +void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); +void http_engine_free (HTTP_Engine *eng); + +void http_engine_close (HTTP_Engine *eng); +HTTP_EngineState http_engine_state (HTTP_Engine *eng); + +const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove + +char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); +void http_engine_recvack (HTTP_Engine *eng, int num); +char* http_engine_sendbuf (HTTP_Engine *eng, int *len); +void http_engine_sendack (HTTP_Engine *eng, int num); + +HTTP_Request* http_engine_getreq (HTTP_Engine *eng); +HTTP_Response* http_engine_getres (HTTP_Engine *eng); + +void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor); +void http_engine_status (HTTP_Engine *eng, int status); +void http_engine_header (HTTP_Engine *eng, const char *src, int len); +void http_engine_body (HTTP_Engine *eng, void *src, int len); +void http_engine_bodycap (HTTP_Engine *eng, int mincap); +char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); +void http_engine_bodyack (HTTP_Engine *eng, int num); +void http_engine_done (HTTP_Engine *eng); +void http_engine_undo (HTTP_Engine *eng); +#endif // HTTP_ENGINE_INCLUDED diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..d41001d --- /dev/null +++ b/src/parse.c @@ -0,0 +1,1037 @@ +#include +#include +#include +#include +#include "parse.h" +#include "basic.h" + +// From RFC 9112 + // request-target = origin-form + // / absolute-form + // / authority-form + // / asterisk-form + // origin-form = absolute-path [ "?" query ] + // absolute-form = absolute-URI + // authority-form = uri-host ":" port + // asterisk-form = "*" + // + // From RFC 9110 + // URI-reference = + // absolute-URI = + // relative-part = + // authority = + // uri-host = + // port = + // path-abempty = + // segment = + // query = + // + // absolute-path = 1*( "/" segment ) + // partial-URI = relative-part [ "?" query ] + // + // From RFC 3986: + // segment = *pchar + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + // pct-encoded = "%" HEXDIG HEXDIG + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + // / "*" / "+" / "," / ";" / "=" + // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + // query = *( pchar / "/" / "?" ) + // absolute-URI = scheme ":" hier-part [ "?" query ] + // hier-part = "//" authority path-abempty + // / path-absolute + // / path-rootless + // / path-empty + // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + +typedef struct { + char *src; + int len; + int cur; +} Scanner; + +static int is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int is_alpha(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +static int is_hex_digit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +// From RFC 3986: +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +// / "*" / "+" / "," / ";" / "=" +static int is_sub_delim(char c) +{ + return c == '!' || c == '$' || c == '&' || c == '\'' + || c == '(' || c == ')' || c == '*' || c == '+' + || c == ',' || c == ';' || c == '='; +} + +// From RFC 3986: +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +static int is_unreserved(char c) +{ + return is_alpha(c) || is_digit(c) + || c == '-' || c == '.' + || c == '_' || c == '~'; +} + +// From RFC 3986: +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +static int is_pchar(char c) +{ + return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'; +} + +static int is_tchar(char c) +{ + return is_digit(c) || is_alpha(c) + || c == '!' || c == '#' || c == '$' + || c == '%' || c == '&' || c == '\'' + || c == '*' || c == '+' || c == '-' + || c == '.' || c == '^' || c == '_' + || c == '~'; +} + +static int is_vchar(char c) +{ + return c >= ' ' && c <= '~'; +} + +static int +consume_absolute_path(Scanner *s) +{ + if (s->cur == s->len || s->src[s->cur] != '/') + return -1; // ERROR + s->cur++; + + for (;;) { + + while (s->cur < s->len && is_pchar(s->src[s->cur])) + s->cur++; + + if (s->cur == s->len || s->src[s->cur] != '/') + break; + s->cur++; + } + + return 0; +} + +// If abempty=1: +// path-abempty = *( "/" segment ) +// else: +// path-absolute = "/" [ segment-nz *( "/" segment ) ] +// path-rootless = segment-nz *( "/" segment ) +// path-empty = 0 +static int parse_path(Scanner *s, HTTP_String *path, int abempty) +{ + int start = s->cur; + + if (abempty) { + + // path-abempty + while (s->cur < s->len && s->src[s->cur] == '/') { + do + s->cur++; + while (s->cur < s->len && is_pchar(s->src[s->cur])); + } + + } else if (s->cur < s->len && (s->src[s->cur] == '/')) { + + // path-absolute + s->cur++; + if (s->cur < s->len && is_pchar(s->src[s->cur])) { + s->cur++; + for (;;) { + + while (s->cur < s->len && is_pchar(s->src[s->cur])) + s->cur++; + + if (s->cur == s->len || s->src[s->cur] != '/') + break; + s->cur++; + } + } + + } else if (s->cur < s->len && is_pchar(s->src[s->cur])) { + + // path-rootless + s->cur++; + for (;;) { + + while (s->cur < s->len && is_pchar(s->src[s->cur])) + s->cur++; + + if (s->cur == s->len || s->src[s->cur] != '/') + break; + s->cur++; + } + + } else { + // path->empty + // (do nothing) + } + + *path = (HTTP_String) { + s->src + start, + s->cur - start, + }; + if (path->len == 0) + path->ptr = NULL; + + return 0; +} + +// RFC 3986: +// query = *( pchar / "/" / "?" ) +static int is_query(char c) +{ + return is_pchar(c) || c == '/' || c == '?'; +} + +// RFC 3986: +// fragment = *( pchar / "/" / "?" ) +static int is_fragment(char c) +{ + return is_pchar(c) || c == '/' || c == '?'; +} + +static int little_endian(void) +{ + uint16_t x = 1; + return *((uint8_t*) &x); +} + +static void invert_bytes(void *p, int len) +{ + char *c = p; + for (int i = 0; i < len/2; i++) { + char tmp = c[i]; + c[i] = c[len-i-1]; + c[len-i-1] = tmp; + } +} + +static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4) +{ + unsigned int out = 0; + int i = 0; + for (;;) { + + if (s->cur == s->len || !is_digit(s->src[s->cur])) + return -1; + + int b = 0; + do { + int x = s->src[s->cur++] - '0'; + if (b > (UINT8_MAX - x) / 10) + return -1; + b = b * 10 + x; + } while (s->cur < s->len && is_digit(s->src[s->cur])); + + out <<= 8; + out |= (unsigned char) b; + + i++; + if (i == 4) + break; + + if (s->cur == s->len || s->src[s->cur] != '.') + return -1; + s->cur++; + } + + if (little_endian()) + invert_bytes(&out, 4); + + ipv4->data = out; + return 0; +} + +static int hex_digit_to_int(char c) +{ + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= '0' && c <= '9') return c - '0'; + return -1; +} + +static int parse_ipv6_comp(Scanner *s) +{ + unsigned short buf; + + if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) + return -1; + buf = hex_digit_to_int(s->src[s->cur]); + s->cur++; + + if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) + return buf; + buf <<= 4; + buf |= hex_digit_to_int(s->src[s->cur]); + s->cur++; + + if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) + return buf; + buf <<= 4; + buf |= hex_digit_to_int(s->src[s->cur]); + s->cur++; + + if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) + return buf; + buf <<= 4; + buf |= hex_digit_to_int(s->src[s->cur]); + s->cur++; + + return (int) buf; +} + +static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6) +{ + unsigned short head[8]; + unsigned short tail[8]; + int head_len = 0; + int tail_len = 0; + + if (s->len - s->cur > 1 + && s->src[s->cur+0] == ':' + && s->src[s->cur+1] == ':') + s->cur += 2; + else { + + for (;;) { + + int ret = parse_ipv6_comp(s); + if (ret < 0) return ret; + + head[head_len++] = (unsigned short) ret; + if (head_len == 8) break; + + if (s->cur == s->len || s->src[s->cur] != ':') + return -1; + s->cur++; + + if (s->cur < s->len && s->src[s->cur] == ':') { + s->cur++; + break; + } + } + } + + if (head_len < 8) { + while (s->cur < s->len && is_hex_digit(s->src[s->cur])) { + + int ret = parse_ipv6_comp(s); + if (ret < 0) return ret; + + tail[tail_len++] = (unsigned short) ret; + if (head_len + tail_len == 8) break; + + if (s->cur == s->len || s->src[s->cur] != ':') + break; + s->cur++; + } + } + + for (int i = 0; i < head_len; i++) + ipv6->data[i] = head[i]; + + for (int i = 0; i < 8 - head_len - tail_len; i++) + ipv6->data[head_len + i] = 0; + + for (int i = 0; i < tail_len; i++) + ipv6->data[8 - tail_len + i] = tail[i]; + + if (little_endian()) + for (int i = 0; i < 8; i++) + invert_bytes(&ipv6->data[i], 2); + + return 0; +} + +// From RFC 3986: +// reg-name = *( unreserved / pct-encoded / sub-delims ) +static int is_regname(char c) +{ + return is_unreserved(c) || is_sub_delim(c); +} + +static int parse_regname(Scanner *s, HTTP_String *regname) +{ + if (s->cur == s->len || !is_regname(s->src[s->cur])) + return -1; + int start = s->cur; + do + s->cur++; + while (s->cur < s->len && is_regname(s->src[s->cur])); + regname->ptr = s->src + start; + regname->len = s->cur - start; + return 0; +} + +static int parse_host(Scanner *s, HTTP_Host *host) +{ + int ret; + if (s->cur < s->len && s->src[s->cur] == '[') { + + s->cur++; + + int start = s->cur; + HTTP_IPv6 ipv6; + ret = parse_ipv6(s, &ipv6); + if (ret < 0) return ret; + + host->mode = HTTP_HOST_MODE_IPV6; + host->ipv6 = ipv6; + host->text = (HTTP_String) { s->src + start, s->cur - start }; + + if (s->cur == s->len || s->src[s->cur] != ']') + return -1; + s->cur++; + + } else { + + int start = s->cur; + HTTP_IPv4 ipv4; + ret = parse_ipv4(s, &ipv4); + if (ret >= 0) { + host->mode = HTTP_HOST_MODE_IPV4; + host->ipv4 = ipv4; + } else { + s->cur = start; + + HTTP_String regname; + ret = parse_regname(s, ®name); + if (ret < 0) return ret; + + host->mode = HTTP_HOST_MODE_NAME; + host->name = regname; + } + host->text = (HTTP_String) { s->src + start, s->cur - start }; + } + + return 0; +} + +// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +static int is_scheme_head(char c) +{ + return is_alpha(c); +} + +static int is_scheme_body(char c) +{ + return is_alpha(c) + || is_digit(c) + || c == '+' + || c == '-' + || c == '.'; +} + +// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +static int is_userinfo(char c) +{ + return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded +} + +// authority = [ userinfo "@" ] host [ ":" port ] +static int parse_authority(Scanner *s, HTTP_Authority *authority) +{ + HTTP_String userinfo; + { + int start = s->cur; + while (s->cur < s->len && is_userinfo(s->src[s->cur])) + s->cur++; + if (s->cur < s->len && s->src[s->cur] == '@') { + userinfo = (HTTP_String) { + s->src + start, + s->cur - start + }; + s->cur++; + } else { + // Rollback + s->cur = start; + userinfo = (HTTP_String) {NULL, 0}; + } + } + + HTTP_Host host; + { + int ret = parse_host(s, &host); + if (ret < 0) + return ret; + } + + int port = 0; + if (s->cur < s->len && s->src[s->cur] == ':') { + s->cur++; + if (s->cur < s->len && is_digit(s->src[s->cur])) { + port = s->src[s->cur++] - '0'; + while (s->cur < s->len && is_digit(s->src[s->cur])) { + int x = s->src[s->cur++] - '0'; + if (port > (UINT16_MAX - x) / 10) + return -1; // ERROR: Port too big + port = port * 10 + x; + } + } + } + + authority->userinfo = userinfo; + authority->host = host; + authority->port = port; + return 0; +} + +static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) +{ + HTTP_String scheme = {0}; + { + int start = s->cur; + if (s->cur == s->len || !is_scheme_head(s->src[s->cur])) + return -1; // ERROR: Missing scheme + do + s->cur++; + while (s->cur < s->len && is_scheme_body(s->src[s->cur])); + scheme = (HTTP_String) { + s->src + start, + s->cur - start, + }; + + if (s->cur == s->len || s->src[s->cur] != ':') + return -1; // ERROR: Missing ':' after scheme + s->cur++; + } + + int abempty = 0; + HTTP_Authority authority = {0}; + if (s->len - s->cur > 1 + && s->src[s->cur+0] == '/' + && s->src[s->cur+1] == '/') { + + s->cur += 2; + + int ret = parse_authority(s, &authority); + if (ret < 0) return ret; + + abempty = 1; + } + + HTTP_String path; + int ret = parse_path(s, &path, abempty); + if (ret < 0) return ret; + + HTTP_String query = {0}; + if (s->cur < s->len && s->src[s->cur] == '?') { + int start = s->cur; + do + s->cur++; + while (s->cur < s->len && is_query(s->src[s->cur])); + query = (HTTP_String) { + s->src + start, + s->cur - start, + }; + } + + HTTP_String fragment = {0}; + if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') { + int start = s->cur; + do + s->cur++; + while (s->cur < s->len && is_fragment(s->src[s->cur])); + fragment = (HTTP_String) { + s->src + start, + s->cur - start, + }; + } + + url->scheme = scheme; + url->authority = authority; + url->path = path; + url->query = query; + url->fragment = fragment; + + return 1; +} + +// authority-form = host ":" port +// host = IP-literal / IPv4address / reg-name +// IP-literal = "[" ( IPv6address / IPvFuture ) "]" +// reg-name = *( unreserved / pct-encoded / sub-delims ) +static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) +{ + int ret; + + ret = parse_host(s, host); + if (ret < 0) return ret; + + // Default port value + *port = 0; + + if (s->cur == s->len || s->src[s->cur] != ':') + return 0; // No port + s->cur++; + + if (s->cur == s->len || !is_digit(s->src[s->cur])) + return 0; // No port + + int buf = 0; + do { + int x = s->src[s->cur++] - '0'; + if (buf > (UINT16_MAX - x) / 10) + return -1; // ERROR + buf = buf * 10 + x; + } while (s->cur < s->len && is_digit(s->src[s->cur])); + + *port = buf; + return 0; +} + +static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query) +{ + int ret, start; + + start = s->cur; + ret = consume_absolute_path(s); + if (ret < 0) return ret; + *path = (HTTP_String) { s->src + start, s->cur - start }; + + if (s->cur < s->len && s->src[s->cur] == '?') { + start = s->cur; + do + s->cur++; + while (s->cur < s->len && is_query(s->src[s->cur])); + *query = (HTTP_String) { s->src + start, s->cur - start }; + } else + *query = (HTTP_String) { NULL, 0 }; + + return 0; +} + +static int parse_asterisk_form(Scanner *s) +{ + if (s->len - s->cur < 2 + || s->src[s->cur+0] != '*' + || s->src[s->cur+1] != ' ') + return -1; + s->cur++; + return 0; +} + +static int parse_request_target(Scanner *s, HTTP_URL *url) +{ + int ret; + + memset(url, 0, sizeof(HTTP_URL)); + + // asterisk-form + ret = parse_asterisk_form(s); + if (ret >= 0) return ret; + + ret = parse_uri(s, url, 0); + if (ret >= 0) return ret; + + ret = parse_authority_form(s, &url->authority.host, &url->authority.port); + if (ret >= 0) return ret; + + ret = parse_origin_form(s, &url->path, &url->query); + if (ret >= 0) return ret; + + return -1; +} + +static int is_header_body(char c) +{ + return is_vchar(c) || c == ' ' || c == '\t'; +} + +static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) +{ + int num_headers = 0; + for (;;) { + + if (s->len - s->cur > 1 + && s->src[s->cur+0] == '\r' + && s->src[s->cur+1] == '\n') { + s->cur += 2; + break; + } + + // RFC 9112: + // field-line = field-name ":" OWS field-value OWS + // + // RFC 9110: + // field-value = *field-content + // field-content = field-vchar + // [ 1*( SP / HTAB / field-vchar ) field-vchar ] + // field-vchar = VCHAR / obs-text + // obs-text = %x80-FF + + int start; + + if (s->cur == s->len || !is_tchar(s->src[s->cur])) + return -1; // ERROR + start = s->cur; + do + s->cur++; + while (s->cur < s->len && is_tchar(s->src[s->cur])); + HTTP_String name = { s->src + start, s->cur - start }; + + if (s->cur == s->len || s->src[s->cur] != ':') + return -1; // ERROR + s->cur++; + + start = s->cur; + while (s->cur < s->len && is_header_body(s->src[s->cur])) + s->cur++; + HTTP_String body = { s->src + start, s->cur - start }; + body = http_trim(body); + + if (s->len - s->cur < 2 + || s->src[s->cur+0] != '\r' + || s->src[s->cur+1] != '\n') + return -1; // ERROR + s->cur += 2; + + if (num_headers < max_headers) + headers[num_headers++] = (HTTP_Header) { name, body }; + } + + return num_headers; +} + +static int +parse_content_length(const char *src, int len, unsigned long long *out) +{ + int cur = 0; + while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) + cur++; + + if (cur == len || !is_digit(src[cur])) + return -1; + + unsigned long long buf = 0; + do { + int d = src[cur++] - '0'; + if (buf > (UINT64_MAX - d) / 10) + return -1; + buf = buf * 10 + d; + } while (cur < len && is_digit(src[cur])); + + *out = buf; + return 0; +} + +static int contains_head(char *src, int len) +{ + int cur = 0; + while (len - cur > 3) { + if (src[cur+0] == '\r' && + src[cur+1] == '\n' && + src[cur+2] == '\r' && + src[cur+3] == '\n') + return 1; + cur++; + } + return 0; +} + +static int parse_request(Scanner *s, HTTP_Request *req) +{ + if (!contains_head(s->src + s->cur, s->len - s->cur)) + return 0; + + if (s->len - s->cur >= 3 + && s->src[s->cur+0] == 'G' + && s->src[s->cur+1] == 'E' + && s->src[s->cur+2] == 'T') { + s->cur += 3; + req->method = HTTP_METHOD_GET; + } else if (s->len - s->cur >= 4 + && s->src[s->cur+0] == 'P' + && s->src[s->cur+1] == 'O' + && s->src[s->cur+2] == 'S' + && s->src[s->cur+3] == 'T') { + s->cur += 4; + req->method = HTTP_METHOD_POST; + } else if (s->len - s->cur >= 3 + && s->src[s->cur+0] == 'P' + && s->src[s->cur+1] == 'U' + && s->src[s->cur+2] == 'T') { + s->cur += 3; + req->method = HTTP_METHOD_PUT; + } else if (s->len - s->cur >= 4 + && s->src[s->cur+0] == 'H' + && s->src[s->cur+1] == 'E' + && s->src[s->cur+2] == 'A' + && s->src[s->cur+3] == 'D') { + s->cur += 4; + req->method = HTTP_METHOD_HEAD; + } else if (s->len - s->cur >= 6 + && s->src[s->cur+0] == 'D' + && s->src[s->cur+1] == 'E' + && s->src[s->cur+2] == 'L' + && s->src[s->cur+3] == 'E' + && s->src[s->cur+4] == 'T' + && s->src[s->cur+5] == 'E') { + s->cur += 6; + req->method = HTTP_METHOD_DELETE; + } else if (s->len - s->cur >= 7 + && s->src[s->cur+0] == 'C' + && s->src[s->cur+1] == 'O' + && s->src[s->cur+2] == 'N' + && s->src[s->cur+3] == 'N' + && s->src[s->cur+4] == 'E' + && s->src[s->cur+5] == 'C' + && s->src[s->cur+6] == 'T') { + s->cur += 7; + req->method = HTTP_METHOD_CONNECT; + } else if (s->len - s->cur >= 7 + && s->src[s->cur+0] == 'O' + && s->src[s->cur+1] == 'P' + && s->src[s->cur+2] == 'T' + && s->src[s->cur+3] == 'I' + && s->src[s->cur+4] == 'O' + && s->src[s->cur+5] == 'N' + && s->src[s->cur+6] == 'S') { + s->cur += 7; + req->method = HTTP_METHOD_OPTIONS; + } else if (s->len - s->cur >= 5 + && s->src[s->cur+0] == 'T' + && s->src[s->cur+1] == 'R' + && s->src[s->cur+2] == 'A' + && s->src[s->cur+3] == 'C' + && s->src[s->cur+4] == 'E') { + s->cur += 5; + req->method = HTTP_METHOD_TRACE; + } else if (s->len - s->cur >= 5 + && s->src[s->cur+0] == 'P' + && s->src[s->cur+1] == 'A' + && s->src[s->cur+2] == 'T' + && s->src[s->cur+3] == 'C' + && s->src[s->cur+4] == 'H') { + s->cur += 5; + req->method = HTTP_METHOD_PATCH; + } else { + return -1; + } + + if (s->cur == s->len || s->src[s->cur] != ' ') + return -1; + s->cur++; + + { + Scanner s2 = *s; + int peek = s->cur; + while (peek < s->len && s->src[peek] != ' ') + peek++; + if (peek == s->len) + return -1; + s2.len = peek; + + int ret = parse_request_target(&s2, &req->url); + if (ret < 0) return ret; + + s->cur = s2.cur; + } + + { + if (s->len - s->cur < 7 + || s->src[s->cur+0] != ' ' + || s->src[s->cur+1] != 'H' + || s->src[s->cur+2] != 'T' + || s->src[s->cur+3] != 'T' + || s->src[s->cur+4] != 'P' + || s->src[s->cur+5] != '/' + || s->src[s->cur+6] != '1') + return -1; // ERROR + s->cur += 7; + + if (s->cur == s->len || s->src[s->cur] != '.') + req->minor = 0; + else { + s->cur++; + if (s->cur == s->len || !is_digit(s->src[s->cur])) + return -1; // ERROR; + req->minor = s->src[s->cur] - '0'; + s->cur++; + } + + if (s->len - s->cur < 2 + || s->src[s->cur+0] != '\r' + || s->src[s->cur+1] != '\n') + return -1; // ERROR + s->cur += 2; + } + + int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS); + if (num_headers < 0) + return num_headers; + req->num_headers = num_headers; + + // TODO + return 1; +} + +int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name) +{ + for (int i = 0; i < num_headers; i++) + if (http_streqcase(name, headers[i].name)) + return i; + return -1; +} + +static int parse_response(Scanner *s, HTTP_Response *res) +{ + if (!contains_head(s->src + s->cur, s->len - s->cur)) + return 0; + + if (s->len - s->cur < 6 + || s->src[s->cur+0] != 'H' + || s->src[s->cur+1] != 'T' + || s->src[s->cur+2] != 'T' + || s->src[s->cur+3] != 'P' + || s->src[s->cur+4] != '/' + || s->src[s->cur+5] != '1') + return -1; // ERROR + s->cur += 6; + + if (s->cur == s->len || s->src[s->cur] != '.') + res->minor = 0; + else { + s->cur++; + if (s->cur == s->len || !is_digit(s->src[s->cur])) + return -1; // ERROR + res->minor = s->src[s->cur] - '0'; + s->cur++; + } + + if (s->len - s->cur < 5 + || s->src[s->cur+0] != ' ' + || !is_digit(s->src[s->cur+1]) + || !is_digit(s->src[s->cur+2]) + || !is_digit(s->src[s->cur+3]) + || s->src[s->cur+4] != ' ') + return -1; + s->cur += 5; + + res->status = + (s->src[s->cur-2] - '0') * 1 + + (s->src[s->cur-3] - '0') * 10 + + (s->src[s->cur-4] - '0') * 100; + + while (s->cur < s->len && ( + s->src[s->cur] == '\t' || + s->src[s->cur] == ' ' || + is_vchar(s->src[s->cur]))) // TODO: obs-text + s->cur++; + + if (s->len - s->cur < 2 + || s->src[s->cur+0] != '\r' + || s->src[s->cur+1] != '\n') + return -1; + s->cur += 2; + + int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS); + if (num_headers < 0) + return num_headers; + res->num_headers = num_headers; + + int content_length_index = http_find_header( + res->headers, res->num_headers, + HTTP_STR("Content-Length")); + if (content_length_index == -1) { + res->body.ptr = NULL; + res->body.len = 0; + return 1; + } + + // TODO: transfer-encoding + + HTTP_String content_length_str = res->headers[content_length_index].value; + + unsigned long long content_length; + if (parse_content_length(content_length_str.ptr, content_length_str.len, &content_length) < 0) { + HTTP_ASSERT(0); // TODO + } + + if (content_length > 1<<20) { + HTTP_ASSERT(0); // TODO + } + + if (content_length > (unsigned long long) (s->len - s->cur)) + return 0; + + res->body.ptr = s->src + s->cur; + res->body.len = content_length; + return 1; +} + +int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4) +{ + Scanner s = {src, len, 0}; + int ret = parse_ipv4(&s, ipv4); + if (ret < 0) return ret; + return s.cur; +} + +int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6) +{ + Scanner s = {src, len, 0}; + int ret = parse_ipv6(&s, ipv6); + if (ret < 0) return ret; + return s.cur; +} + +int http_parse_url(char *src, int len, HTTP_URL *url) +{ + Scanner s = {src, len, 0}; + int ret = parse_uri(&s, url, 1); + if (ret == 1) + return s.cur; + return ret; +} + +int http_parse_request(char *src, int len, HTTP_Request *req) +{ + Scanner s = {src, len, 0}; + int ret = parse_request(&s, req); + if (ret == 1) + return s.cur; + return ret; +} + +int http_parse_response(char *src, int len, HTTP_Response *res) +{ + Scanner s = {src, len, 0}; + int ret = parse_response(&s, res); + if (ret == 1) + return s.cur; + return ret; +} + +HTTP_String http_getqueryparam(HTTP_Request *req, HTTP_String name) +{ + // TODO + return (HTTP_String) {NULL, 0}; +} + +HTTP_String http_getbodyparam(HTTP_Request *req, HTTP_String name) +{ + // TODO + return (HTTP_String) {NULL, 0}; +} + +HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) +{ + // TODO + return (HTTP_String) {NULL, 0}; +} diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 0000000..9b88ff1 --- /dev/null +++ b/src/parse.h @@ -0,0 +1,93 @@ +#ifndef PARSE_INCLUDED +#define PARSE_INCLUDED +#include "basic.h" + +#define HTTP_MAX_HEADERS 32 + +typedef struct { + unsigned int data; +} HTTP_IPv4; + +typedef struct { + unsigned short data[8]; +} HTTP_IPv6; + +typedef enum { + HTTP_HOST_MODE_VOID = 0, + HTTP_HOST_MODE_NAME, + HTTP_HOST_MODE_IPV4, + HTTP_HOST_MODE_IPV6, +} HTTP_HostMode; + +typedef struct { + HTTP_HostMode mode; + HTTP_String text; + union { + HTTP_String name; + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + }; +} HTTP_Host; + +typedef struct { + HTTP_String userinfo; + HTTP_Host host; + int port; +} HTTP_Authority; + +// ZII +typedef struct { + HTTP_String scheme; + HTTP_Authority authority; + HTTP_String path; + HTTP_String query; + HTTP_String fragment; +} HTTP_URL; + +typedef enum { + HTTP_METHOD_GET, + HTTP_METHOD_HEAD, + HTTP_METHOD_POST, + HTTP_METHOD_PUT, + HTTP_METHOD_DELETE, + HTTP_METHOD_CONNECT, + HTTP_METHOD_OPTIONS, + HTTP_METHOD_TRACE, + HTTP_METHOD_PATCH, +} HTTP_Method; + +typedef struct { + HTTP_String name; + HTTP_String value; +} HTTP_Header; + +typedef struct { + HTTP_Method method; + HTTP_URL url; + int minor; + int num_headers; + HTTP_Header headers[HTTP_MAX_HEADERS]; + HTTP_String body; +} HTTP_Request; + +typedef struct { + int minor; + int status; + HTTP_String reason; + int num_headers; + HTTP_Header headers[HTTP_MAX_HEADERS]; + HTTP_String body; +} HTTP_Response; + +int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4); +int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6); +int http_parse_url (char *src, int len, HTTP_URL *url); +int http_parse_request (char *src, int len, HTTP_Request *req); +int http_parse_response (char *src, int len, HTTP_Response *res); + +int http_find_header (HTTP_Header *headers, int num_headers, HTTP_String name); +HTTP_String http_getqueryparam (HTTP_Request *req, HTTP_String name); +HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name); +HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name); + +#endif // PARSE_INCLUDED diff --git a/src/router.c b/src/router.c new file mode 100644 index 0000000..fde1bfe --- /dev/null +++ b/src/router.c @@ -0,0 +1,429 @@ +#include +#include +#include +#include "router.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +typedef enum { + ROUTE_STATIC_DIR, + ROUTE_DYNAMIC, +} RouteType; + +typedef struct { + RouteType type; + HTTP_String endpoint; + HTTP_String path; + HTTP_RouterFunc func; + void *ptr; +} Route; + +struct HTTP_Router { + int num_routes; + int max_routes; + Route routes[]; +}; + +HTTP_Router *http_router_init(void) +{ + int max_routes = 32; + HTTP_Router *router = malloc(max_routes * sizeof(HTTP_Router)); + if (router == NULL) + return NULL; + router->max_routes = max_routes; + router->num_routes = 0; + return router; +} + +void http_router_free(HTTP_Router *router) +{ + free(router); +} + +void http_router_dir(HTTP_Router *router, HTTP_String endpoint, HTTP_String path) +{ + if (router->num_routes == router->max_routes) + abort(); + Route *route = &router->routes[router->num_routes++]; + route->type = ROUTE_STATIC_DIR; + route->endpoint = endpoint; + route->path = path; +} + +void http_router_func(HTTP_Router *router, HTTP_Method method, + HTTP_String endpoint, HTTP_RouterFunc func, void *ptr) +{ + if (router->num_routes == router->max_routes) + abort(); + Route *route = &router->routes[router->num_routes++]; + // TODO: Don't ignore the method + route->type = ROUTE_DYNAMIC; + route->endpoint = endpoint; + route->func = func; + route->ptr = ptr; +} + +static int valid_component_char(char c) +{ + return is_alpha(c) || is_digit(c) || c == '-' || c == '_' || c == '.'; // TODO +} + +static int parse_and_sanitize_path(HTTP_String path, HTTP_String *comps, int max_comps) +{ + // We treat relative and absolute paths the same + if (path.len > 0 && path.ptr[0] == '/') { + path.ptr++; + path.len--; + if (path.len == 0) + return 0; + } + + int num = 0; + int cur = 0; + for (;;) { + if (cur == path.len || !valid_component_char(path.ptr[cur])) + return -1; // Empty component + int start = cur; + do + cur++; + while (cur < path.len && valid_component_char(path.ptr[cur])); + HTTP_String comp = { path.ptr + start, cur - start }; + + if (http_streq(comp, HTTP_STR(".."))) { + if (num == 0) + return -1; + num--; + } else if (!http_streq(comp, HTTP_STR("."))) { + if (num == max_comps) + return -1; + comps[num++] = comp; + } + + if (cur < path.len) { + if (path.ptr[cur] != '/') + return -1; + cur++; + } + + if (cur == path.len) + break; + } + + return num; +} + +static int +serialize_parsed_path(HTTP_String *comps, int num_comps, char *dst, int max) +{ + int len = 0; + for (int i = 0; i < num_comps; i++) + len += comps[i].len + 1; + + if (len >= max) + return -1; + + int copied = 0; + for (int i = 0; i < num_comps; i++) { + + if (i > 0) + dst[copied++] = '/'; + + memcpy(dst + copied, + comps[i].ptr, + comps[i].len); + + copied += comps[i].len; + } + + dst[copied] = '\0'; + return copied; +} + +#define MAX_COMPS 32 + +static int sanitize_path(HTTP_String path, char *dst, int max) +{ + HTTP_String comps[MAX_COMPS]; + int num_comps = parse_and_sanitize_path(path, comps, MAX_COMPS); + if (num_comps < 0) return -1; + + return serialize_parsed_path(comps, num_comps, dst, max); +} + +static int swap_parents(HTTP_String original_parent_path, HTTP_String new_parent_path, HTTP_String path, char *mem, int max) +{ + int num_original_parent_path_comps; + HTTP_String original_parent_path_comps[MAX_COMPS]; + + int num_new_parent_path_comps; + HTTP_String new_parent_path_comps[MAX_COMPS]; + + int num_path_comps; + HTTP_String path_comps[MAX_COMPS]; + + num_original_parent_path_comps = parse_and_sanitize_path(original_parent_path, original_parent_path_comps, MAX_COMPS); + num_new_parent_path_comps = parse_and_sanitize_path(new_parent_path, new_parent_path_comps, MAX_COMPS); + num_path_comps = parse_and_sanitize_path(path, path_comps, MAX_COMPS); + if (num_original_parent_path_comps < 0 || num_new_parent_path_comps < 0 || num_path_comps < 0) + return -1; + + int match = 1; + if (num_path_comps < num_original_parent_path_comps) + match = 0; + else { + for (int i = 0; i < num_original_parent_path_comps; i++) + if (!http_streq(original_parent_path_comps[i], path_comps[i])) { + match = 0; + break; + } + } + if (!match) + return 0; + + int num_result_comps = num_new_parent_path_comps + num_path_comps - num_original_parent_path_comps; + if (num_result_comps < 0 || num_result_comps > MAX_COMPS) + return -1; + + HTTP_String result_comps[MAX_COMPS]; + for (int i = 0; i < num_new_parent_path_comps; i++) + result_comps[i] = new_parent_path_comps[i]; + + for (int i = 0; i < num_path_comps; i++) + result_comps[num_new_parent_path_comps + i] = path_comps[num_original_parent_path_comps + i]; + + return serialize_parsed_path(result_comps, num_result_comps, mem, max); +} + +#if _WIN32 +typedef HANDLE File; +#else +typedef int File; +#endif + +static int file_open(const char *path, File *handle, int *size) +{ +#ifdef _WIN32 + *handle = CreateFileA( + path, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if (*handle == INVALID_HANDLE_VALUE) { + DWORD error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) + return 1; + if (error == ERROR_ACCESS_DENIED) + return 1; + return -1; + } + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(*handle, &fileSize)) { + CloseHandle(*handle); + return -1; + } + if (fileSize.QuadPart > INT_MAX) { + CloseHandle(*handle); + return -1; + } + *size = (int) fileSize.QuadPart; + return 0; +#else + *handle = open(path, O_RDONLY); + if (*handle < 0) { + if (errno == ENOENT) + return 1; + return -1; + } + struct stat info; + if (fstat(*handle, &info) < 0) { + close(*handle); + return -1; + } + if (S_ISDIR(info.st_mode)) { + close(*handle); + return 1; + } + if (info.st_size > INT_MAX) { + close(*handle); + return -1; + } + *size = (int) info.st_size; + return 0; +#endif +} + +static void file_close(File file) +{ +#ifdef _WIN32 + CloseHandle(file); +#else + close(file); +#endif +} + +static int file_read(File file, char *dst, int max) +{ +#ifdef _WIN32 + DWORD num; + BOOL ok = ReadFile(file, dst, max, &num, NULL); + if (!ok) + return -1; + return (int) num; +#else + return read(file, dst, max); +#endif +} + +static int serve_file_or_index(HTTP_ResponseHandle res, HTTP_String base_endpoint, HTTP_String base_path, HTTP_String endpoint) +{ + char mem[1<<12]; + int ret = swap_parents(base_endpoint, base_path, endpoint, mem, sizeof(mem)); + if (ret <= 0) + return ret; + HTTP_String path = {mem, ret}; // Note that this is zero terminated + + int size; + File file; + ret = file_open(path.ptr, &file, &size); + if (ret == -1) { + http_response_status(res, 500); + http_response_done(res); + return 1; + } + if (ret == 1) { + + // File missing + + char index[] = "index.html"; + if (path.len + sizeof(index) + 1 > sizeof(mem)) { + http_response_status(res, 500); + http_response_done(res); + return 1; + } + path.ptr[path.len++] = '/'; + memcpy(path.ptr + path.len, index, sizeof(index)); + path.len += sizeof(index)-1; + + ret = file_open(path.ptr, &file, &size); + if (ret == -1) { + http_response_status(res, 500); + http_response_done(res); + return 1; + } + if (ret == 1) + return 0; // File missing + } + HTTP_ASSERT(ret == 0); + + int cap; + char *dst; + http_response_status(res, 200); + http_response_bodycap(res, size); + dst = http_response_bodybuf(res, &cap); + if (dst) { + int copied = 0; + while (copied < size) { + int ret = file_read(file, dst + copied, size - copied); + if (ret < 0) goto err; + if (ret == 0) break; + copied += ret; + } + if (copied < size) goto err; + http_response_bodyack(res, size); + } + http_response_done(res); + file_close(file); + return 1; +err: + http_response_bodyack(res, 0); + http_response_undo(res); + http_response_status(res, 500); + http_response_done(res); + file_close(file); + return 1; +} + +static int serve_dynamic_route(Route *route, HTTP_Request *req, HTTP_ResponseHandle res) +{ + char path_mem[1<<12]; + int path_len = sanitize_path(req->url.path, path_mem, (int) sizeof(path_mem)); + if (path_len < 0) { + http_response_status(res, 400); + http_response_body(res, "Invalid path", -1); + http_response_done(res); + return 1; + } + HTTP_String path = {path_mem, path_len}; + + if (!http_streq(path, route->endpoint)) + return 0; + + route->func(req, res, route->ptr); + return 1; +} + +void http_router_resolve(HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res) +{ + for (int i = 0; i < router->num_routes; i++) { + Route *route = &router->routes[i]; + switch (route->type) { + case ROUTE_STATIC_DIR: + if (serve_file_or_index(res, + route->endpoint, + route->path, + req->url.path)) + return; + break; + + case ROUTE_DYNAMIC: + if (serve_dynamic_route(route, req, res)) + return; + break; + + default: + http_response_status(res, 500); + http_response_done(res); + return; + } + } + http_response_status(res, 404); + http_response_done(res); +} + +int http_serve(char *addr, int port, HTTP_Router *router) +{ + int ret; + + HTTP_Server *server = http_server_init((HTTP_String) { addr, strlen(addr) }, port, 0, (HTTP_String) {}, (HTTP_String) {}); + if (server == NULL) { + http_router_free(router); + return -1; + } + + for (;;) { + HTTP_Request *req; + HTTP_ResponseHandle res; + ret = http_server_wait(server, &req, &res); + if (ret < 0) { + http_server_free(server); + http_router_free(router); + return -1; + } + if (ret == 0) + continue; + http_router_resolve(router, req, res); + } + + http_server_free(server); + http_router_free(router); + return 0; +} \ No newline at end of file diff --git a/src/router.h b/src/router.h new file mode 100644 index 0000000..4c42fac --- /dev/null +++ b/src/router.h @@ -0,0 +1,16 @@ +#ifndef HTTP_ROUTER_INCLUDED +#define HTTP_ROUTER_INCLUDED + +#include "server.h" + +typedef struct HTTP_Router HTTP_Router; +typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; + +HTTP_Router* http_router_init (void); +void http_router_free (HTTP_Router *router); +void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res); +void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path); +void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*); +int http_serve (char *addr, int port, HTTP_Router *router); + +#endif // HTTP_ROUTER_INCLUDED \ No newline at end of file diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..37c275e --- /dev/null +++ b/src/server.c @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include +#include "engine.h" +#include "socket.h" +#include "server.h" + +#define MAX_CONNS (1<<10) + +typedef struct { + bool used; + uint16_t gen; + Socket socket; + HTTP_Engine engine; +} Connection; + +struct HTTP_Server { + SocketGroup group; + + int listen_fd; + int secure_fd; + + int num_conns; + Connection conns[MAX_CONNS]; + + int ready_head; + int ready_count; + int ready[MAX_CONNS]; +}; + +static int listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog) +{ + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) + return -1; + + { + int flags = fcntl(listen_fd, F_GETFL, 0); + if (flags < 0) { + close(listen_fd); + return -1; + } + + if (fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK) < 0) { + close(listen_fd); + return -1; + } + } + + if (reuse_addr) { + int one = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + } + + struct in_addr addr_buf; + if (addr.len == 0) + addr_buf.s_addr = htonl(INADDR_ANY); + else { + + _Static_assert(sizeof(struct in_addr) == sizeof(HTTP_IPv4)); + if (http_parse_ipv4(addr.ptr, addr.len, (HTTP_IPv4*) &addr_buf) < 0) { + close(listen_fd); + return -1; + } + } + + struct sockaddr_in bind_buf; + bind_buf.sin_family = AF_INET; + bind_buf.sin_addr = addr_buf; + bind_buf.sin_port = htons(port); + if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { + close(listen_fd); + return -1; + } + + if (listen(listen_fd, backlog) < 0) { + close(listen_fd); + return -1; + } + + return listen_fd; +} + +HTTP_Server *http_server_init(HTTP_String addr, uint16_t port) +{ + return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); +} + +HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, + uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key) +{ + HTTP_Server *server = malloc(sizeof(HTTP_Server)); + if (server == NULL) + return NULL; + + int backlog = 32; + bool reuse_addr = true; + + if (port == 0 && secure_port == 0) { + // You must have at least one! + free(server); + return NULL; + } + + if (port == 0) + server->listen_fd = -1; + else { + server->listen_fd = listen_socket(addr, port, reuse_addr, backlog); + if (server->listen_fd < 0) { + free(server); + return NULL; + } + } + + if (secure_port == 0) + server->secure_fd = -1; + else { + + if (socket_group_init_server(&server->group, cert_key, private_key) < 0) { + close(server->listen_fd); + free(server); + return NULL; + } + + server->secure_fd = listen_socket(addr, secure_port, reuse_addr, backlog); + if (server->secure_fd < 0) { + socket_group_free(&server->group); + close(server->listen_fd); + free(server); + return NULL; + } + } + + server->num_websites = 0; + server->num_conns = 0; + server->ready_head = 0; + server->ready_count = 0; + + for (int i = 0; i < MAX_CONNS; i++) { + server->conns[i].used = false; + server->conns[i].gen = 1; + } + + return server; +} + +void http_server_free(HTTP_Server *server) +{ + for (int i = 0, j = 0; j < server->num_conns; i++) { + + if (!server->conns[i].used) + continue; + j++; + + // TODO + } + + close(server->secure_fd); + close(server->listen_fd); + if (server->secure_fd != -1) + socket_group_free(&server->group); + free(server); +} + +int http_server_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +{ + return socket_group_add_domain(&server->group, domain, cert_file, key_file); +} + +static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { + (void)data; + switch (tag) { + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandle *handle) +{ + while (server->ready_count == 0) { + + int num_polled = 0; + struct pollfd polled[MAX_CONNS+2]; + int indices[MAX_CONNS+2]; + + if (server->num_conns < MAX_CONNS) { + + if (server->listen_fd != -1) { + polled[num_polled].fd = server->listen_fd; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + indices[num_polled] = -1; + num_polled++; + } + + if (server->secure_fd != -1) { + polled[num_polled].fd = server->secure_fd; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + indices[num_polled] = -1; + num_polled++; + } + } + + for (int i = 0, j = 0; i < server->num_conns; i++) { + + if (!server->conns[i].used) + continue; + j++; + + int events = 0; + + if (server->conns[i].socket.ssl_ctx) + events = server->conns[i].socket.event; + else { + switch (http_engine_state(&server->conns[i].engine)) { + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: events = POLLIN; break; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: events = POLLOUT; break; + default:break; + } + } + + if (events) { + polled[num_polled].fd = server->conns[i].socket.fd; + polled[num_polled].events = events; + polled[num_polled].revents = 0; + indices[num_polled] = i; + num_polled++; + } + } + + int timeout = -1; + poll(polled, num_polled, timeout); + + for (int i = 0; i < num_polled; i++) { + + if (polled[i].fd == server->listen_fd || polled[i].fd == server->secure_fd) { + + bool secure = false; + if (polled[i].fd == server->secure_fd) + secure = true; + + if ((polled[i].revents & POLLIN) && server->num_conns < MAX_CONNS) { + + int new_fd = accept(polled[i].fd, NULL, NULL); + + int k = 0; + while (server->conns[k].used) + k++; + + server->conns[k].used = true; + socket_accept(&server->conns[k].socket, secure ? &server->group : NULL, new_fd); + http_engine_init(&server->conns[k].engine, 0, server_memfunc, NULL); + server->num_conns++; + } + + } else { + + int connidx = indices[i]; + Connection *conn = &server->conns[connidx]; + + socket_update(&conn->socket); + + if (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) { + + switch (http_engine_state(&conn->engine)) { + + int len; + char *buf; + + case HTTP_ENGINE_STATE_SERVER_RECV_BUF: + buf = http_engine_recvbuf(&conn->engine, &len); + if (buf) { + int ret = socket_read(&conn->socket, buf, len); + http_engine_recvack(&conn->engine, ret); + } + break; + + case HTTP_ENGINE_STATE_SERVER_SEND_BUF: + buf = http_engine_sendbuf(&conn->engine, &len); + if (buf) { + int ret = socket_write(&conn->socket, buf, len); + http_engine_sendack(&conn->engine, ret); + } + break; + + default: + break; + } + + switch (http_engine_state(&conn->engine)) { + + int tail; + + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: + tail = (server->ready_head + server->ready_count) % MAX_CONNS; + server->ready[tail] = connidx; + server->ready_count++; + break; + + case HTTP_ENGINE_STATE_SERVER_CLOSED: + socket_close(&conn->socket); + break; + + default: + break; + + } + } + + if (socket_state(&conn->socket) == SOCKET_STATE_DIED) { + socket_free(&conn->socket); + http_engine_free(&conn->engine); + conn->used = false; + server->num_conns--; + } + } + } + } + + int index = server->ready[server->ready_head]; + server->ready_head = (server->ready_head + 1) % MAX_CONNS; + server->ready_count--; + + *req = http_engine_getreq(&server->conns[index].engine); + *handle = (HTTP_ResponseHandle) { server, index, server->conns[index].gen }; + return 0; +} + +static Connection* +handle2conn(HTTP_ResponseHandle handle) +{ + HTTP_Server *server = handle.data0; + if (handle.data1 >= MAX_CONNS) + return NULL; + + Connection *conn = &server->conns[handle.data1]; + if (conn->gen != handle.data2) + return NULL; + + return conn; +} + +void http_response_status(HTTP_ResponseHandle res, int status) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + http_engine_status(&conn->engine, status); +} + +void http_response_header(HTTP_ResponseHandle res, const char *fmt, ...) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + va_list args; + va_start(args, fmt); + http_engine_header_fmt2(&conn->engine, fmt, args); + va_end(args); +} + +void http_response_body(HTTP_ResponseHandle res, char *src, int len) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + if (len < 0) + len = strlen(src); + + http_engine_body(&conn->engine, src, len); +} + +void http_response_bodycap(HTTP_ResponseHandle res, int mincap) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + http_engine_bodycap(&conn->engine, mincap); +} + +char *http_response_bodybuf(HTTP_ResponseHandle res, int *cap) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) { + *cap = 0; + return NULL; + } + + return http_engine_bodybuf(&conn->engine, cap); +} + +void http_response_bodyack(HTTP_ResponseHandle res, int num) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + http_engine_bodyack(&conn->engine, num); +} + +void http_response_undo(HTTP_ResponseHandle res) +{ + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + http_engine_undo(&conn->engine); +} + +void http_response_done(HTTP_ResponseHandle res) +{ + HTTP_Server *server = res.data0; + Connection *conn = handle2conn(res); + if (conn == NULL) + return; + + http_engine_done(&conn->engine); + + conn->gen++; + if (conn->gen == 0 || conn->gen == UINT16_MAX) + conn->gen = 1; + + HTTP_EngineState state = http_engine_state(&conn->engine); + + if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { + int tail = (server->ready_head + server->ready_count) % MAX_CONNS; + server->ready[tail] = res.data1; + server->ready_count++; + } + + if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) { + socket_close(&conn->socket); + http_engine_free(&conn->engine); + server->num_conns--; + } +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..63aa4ff --- /dev/null +++ b/src/server.h @@ -0,0 +1,32 @@ +#ifndef HTTP_SERVER_INCLUDED +#define HTTP_SERVER_INCLUDED + +#include +#include "parse.h" + +typedef struct { + void *data0; + int data1; + int data2; +} HTTP_ResponseHandle; + +typedef struct HTTP_Server HTTP_Server; + +HTTP_Server *http_server_init(HTTP_String addr, uint16_t port); + +HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, + uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key); + +void http_server_free (HTTP_Server *server); +int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandle *handle); +int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); +void http_response_status (HTTP_ResponseHandle res, int status); +void http_response_header (HTTP_ResponseHandle res, const char *fmt, ...); +void http_response_body (HTTP_ResponseHandle res, char *src, int len); +void http_response_bodycap (HTTP_ResponseHandle res, int mincap); +char* http_response_bodybuf (HTTP_ResponseHandle res, int *cap); +void http_response_bodyack (HTTP_ResponseHandle res, int num); +void http_response_undo (HTTP_ResponseHandle res); +void http_response_done (HTTP_ResponseHandle res); + +#endif // HTTP_SERVER_INCLUDED \ No newline at end of file diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..61e9837 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,784 @@ +#include +#include +#include "socket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void socket_global_init(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +} + +void socket_global_free(void) +{ + EVP_cleanup(); + ERR_free_strings(); +} + +int socket_group_init(SocketGroup *group) +{ + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Set certificate verification mode + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + + // Load default trusted certificate store + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + fprintf(stderr, "Failed to set default verify paths\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + group->ssl_ctx = ssl_ctx; + group->domains = NULL; + group->num_domains = 0; + group->max_domains = 0; + return 0; +} + +static int servername_callback(SSL *ssl, int *ad, void *arg) +{ + SocketGroup *group = arg; + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL) + return SSL_TLSEXT_ERR_NOACK; + + for (int i = 0; i < group->num_domains; i++) { + Domain *domain = &group->domains[i]; + if (!strcmp(domain->name, servername)) { + SSL_set_SSL_CTX(ssl, domain->ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} + +int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file) +{ + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create server SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Copy certificate file path to static buffer + static char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + fprintf(stderr, "Certificate file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + static char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + fprintf(stderr, "Private key file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load private key file: %s\n", key_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + fprintf(stderr, "Private key does not match certificate\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + SSL_CTX_set_tlsext_servername_callback(group->ssl_ctx, servername_callback); + SSL_CTX_set_tlsext_servername_arg(group->ssl_ctx, group); + + group->ssl_ctx = ssl_ctx; + group->domains = NULL; + group->num_domains = 0; + group->max_domains = 0; + return 0; +} + +void socket_group_free(SocketGroup *group) +{ + SSL_CTX_free(group->ssl_ctx); +} + +int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +{ + if (group->num_domains == group->max_domains) { + + int new_max_domains = 2 * group->max_domains; + if (new_max_domains == 0) + new_max_domains = 4; + + Domain *new_domains = malloc(new_max_domains * sizeof(Domain)); + if (new_domains == NULL) + return -1; + + if (group->max_domains > 0) { + for (int i = 0; i < group->num_domains; i++) + new_domains[i] = group->domains[i]; + free(group->domains); + } + + group->domains = new_domains; + group->max_domains = new_max_domains; + } + + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + fprintf(stderr, "Unable to create server SSL context\n"); + ERR_print_errors_fp(stderr); + return -1; + } + + // Set minimum TLS version (optional - for better security) + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); + + // Copy certificate file path to static buffer + static char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + fprintf(stderr, "Certificate file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + static char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + fprintf(stderr, "Private key file path too long\n"); + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "Failed to load private key file: %s\n", key_buffer); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + fprintf(stderr, "Private key does not match certificate\n"); + ERR_print_errors_fp(stderr); + SSL_CTX_free(ssl_ctx); + return -1; + } + + Domain *domain_info = &group->domains[group->num_domains]; + if (domain.len >= (int) sizeof(domain_info->name)) { + SSL_CTX_free(ssl_ctx); + return -1; + } + memcpy(domain_info->name, domain.ptr, domain.len); + domain_info->name[domain.len] = '\0'; + domain_info->ssl_ctx = ssl_ctx; + group->num_domains++; + return 0; +} + +SocketState socket_state(Socket *sock) +{ + return sock->state; +} + +void socket_accept(Socket *sock, SocketGroup *group, int fd) +{ + // Initialize socket for server-side TLS handshake + sock->state = SOCKET_STATE_ACCEPTED; // TCP connection already established + sock->event = SOCKET_WANT_NONE; + sock->fd = fd; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = 0; + + // Set non-blocking mode for the accepted socket + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) { + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + + // Start the TLS handshake process + socket_update(sock); +} + +void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->port = port; + sock->hostname = (char*)malloc(host.len + 1); + memcpy(sock->hostname, host.ptr, host.len); + sock->hostname[host.len] = '\0'; + // DNS query + struct addrinfo hints = {0}, *res = NULL, *rp = NULL; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%u", port); + if (getaddrinfo(sock->hostname, portstr, &hints, &res) != 0) { + sock->state = SOCKET_STATE_DIED; + return; + } + // Count addresses + int count = 0; + for (rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) count++; + } + if (count == 0) { + freeaddrinfo(res); + sock->state = SOCKET_STATE_DIED; + return; + } + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo) * count); + sock->addr_count = count; + sock->addr_cursor = 0; + int i = 0; + for (rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + sock->addr_list[i].is_ipv6 = 0; + memcpy(&sock->addr_list[i].addr.ipv4, &((struct sockaddr_in*)rp->ai_addr)->sin_addr, sizeof(HTTP_IPv4)); + i++; + } else if (rp->ai_family == AF_INET6) { + sock->addr_list[i].is_ipv6 = 1; + memcpy(&sock->addr_list[i].addr.ipv6, &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr, sizeof(HTTP_IPv6)); + i++; + } + } + freeaddrinfo(res); + // Set event/state and call update + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); +} + +void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = port; + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo)); + sock->addr_list[0].is_ipv6 = 0; + memcpy(&sock->addr_list[0].addr.ipv4, &addr, sizeof(HTTP_IPv4)); + sock->addr_count = 1; + sock->addr_cursor = 0; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); +} + +void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) { + sock->state = SOCKET_STATE_PENDING; + sock->event = SOCKET_WANT_NONE; + sock->fd = -1; + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; + sock->addr_list = NULL; + sock->addr_count = 0; + sock->addr_cursor = 0; + sock->hostname = NULL; + sock->port = port; + sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo)); + sock->addr_list[0].is_ipv6 = 1; + memcpy(&sock->addr_list[0].addr.ipv6, &addr, sizeof(HTTP_IPv6)); + sock->addr_count = 1; + sock->addr_cursor = 0; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + socket_update(sock); +} + +void socket_update(Socket *sock) +{ + sock->event = SOCKET_WANT_NONE; + + bool again; + do { + + again = false; + + switch (sock->state) { + case SOCKET_STATE_PENDING: + { + if (sock->ssl) { + SSL_free(sock->ssl); + sock->ssl = NULL; + } + + if (sock->fd != -1) + close(sock->fd); + + // If cursor reached the end, die + if (sock->addr_cursor >= sock->addr_count) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + break; + } + + // Take current address + AddrInfo *ai = &sock->addr_list[sock->addr_cursor]; + int family = ai->is_ipv6 ? AF_INET6 : AF_INET; + int fd = socket(family, SOCK_STREAM, 0); + if (fd < 0) { + // Try next address + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + break; + } + + // Set non-blocking + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) fcntl(fd, F_SETFL, flags | O_NONBLOCK); // TODO: Handle error by setting the socket to DIED + + // Prepare sockaddr + int ret; + if (ai->is_ipv6) { + struct sockaddr_in6 sa6 = {0}; + sa6.sin6_family = AF_INET6; + memcpy(&sa6.sin6_addr, &ai->addr.ipv6, sizeof(HTTP_IPv6)); + sa6.sin6_port = htons(sock->port); + ret = connect(fd, (struct sockaddr*)&sa6, sizeof(sa6)); + } else { + struct sockaddr_in sa4 = {0}; + sa4.sin_family = AF_INET; + memcpy(&sa4.sin_addr, &ai->addr.ipv4, sizeof(HTTP_IPv4)); + sa4.sin_port = htons(sock->port); + ret = connect(fd, (struct sockaddr*)&sa4, sizeof(sa4)); + } + + if (ret == 0) { + // Connected immediately + sock->fd = fd; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_CONNECTED; + again = true; + break; + } + + if (ret < 0 && errno == EINPROGRESS) { + // Connection pending + sock->fd = fd; + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_CONNECTING; + break; + } + + // Connect failed + // If remote peer not working, try next address + if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == ENETUNREACH || errno == EHOSTUNREACH) { + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } else { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + case SOCKET_STATE_CONNECTING: + { + // Check connect result + int err = 0; + socklen_t len = sizeof(err); + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0 || err != 0) { + close(sock->fd); + // If remote peer not working, try next address + if (err == ECONNREFUSED || err == ETIMEDOUT || err == ENETUNREACH || err == EHOSTUNREACH) { + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } else { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + break; + } + + // Connect succeeded + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_CONNECTED; + again = true; + break; + } + break; + + case SOCKET_STATE_CONNECTED: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } else { + + // Start SSL handshake + if (!sock->ssl) { + sock->ssl = SSL_new(sock->ssl_ctx); + SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error? + if (sock->hostname) SSL_set_tlsext_host_name(sock->ssl, sock->hostname); + } + + int ret = SSL_connect(sock->ssl); + if (ret == 1) { + // Handshake done + free(sock->addr_list); sock->addr_list = NULL; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + sock->addr_cursor++; + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_PENDING; + again = true; + } + } + break; + + case SOCKET_STATE_ACCEPTED: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } else { + + // Start server-side SSL handshake + if (!sock->ssl) { + sock->ssl = SSL_new(sock->ssl_ctx); + SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error? + } + + int ret = SSL_accept(sock->ssl); + if (ret == 1) { + // Handshake done + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + // Server socket error - close the connection + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + case SOCKET_STATE_ESTABLISHED_WAIT: + { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_ESTABLISHED_READY; + } + break; + + case SOCKET_STATE_SHUTDOWN: + { + if (sock->ssl_ctx == NULL) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } else { + + int ret = SSL_shutdown(sock->ssl); + if (ret == 1) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + break; + } + + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + break; + } + + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + break; + + default: + // Do nothing + break; + } + + } while (again); +} + +int socket_read(Socket *sock, char *dst, int max) { + // If not ESTABLISHED, set state to DIED and return + if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + return -1; + } + + if (sock->ssl_ctx == NULL) { + int ret = read(sock->fd, dst, max); + if (ret == 0) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } else { + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + if (errno != EINTR) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + ret = 0; + } + } + return ret; + } else { + int ret = SSL_read(sock->ssl, dst, max); + if (ret <= 0) { + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + fprintf(stderr, "OpenSSL error in socket_read: "); + ERR_print_errors_fp(stderr); + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + ret = 0; + } + return ret; + } +} + +int socket_write(Socket *sock, char *src, int len) { + // If not ESTABLISHED, set state to DIED and return + if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + return 0; + } + + if (sock->ssl_ctx == NULL) { + int ret = write(sock->fd, src, len); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + if (errno != EINTR) { + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + } + ret = 0; + } + return ret; + } else { + int ret = SSL_write(sock->ssl, src, len); + if (ret <= 0) { + int err = SSL_get_error(sock->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + sock->event = SOCKET_WANT_READ; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else if (err == SSL_ERROR_WANT_WRITE) { + sock->event = SOCKET_WANT_WRITE; + sock->state = SOCKET_STATE_ESTABLISHED_WAIT; + } else { + fprintf(stderr, "OpenSSL error in socket_write: "); + ERR_print_errors_fp(stderr); + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_DIED; + } + ret = 0; + } + return ret; + } +} + +void socket_close(Socket *sock) { + // Set state to SHUTDOWN and call update + sock->event = SOCKET_WANT_NONE; + sock->state = SOCKET_STATE_SHUTDOWN; + socket_update(sock); +} + +void socket_free(Socket *sock) { + // Release all resources associated to the socket + if (sock->ssl) { + SSL_free(sock->ssl); + sock->ssl = NULL; + } + if (sock->fd >= 0) { + close(sock->fd); + sock->fd = -1; + } + if (sock->hostname) { + free(sock->hostname); + sock->hostname = NULL; + } + if (sock->addr_list) { + free(sock->addr_list); + sock->addr_list = NULL; + } +} + +#define COUNT(X) (sizeof(X) / sizeof((X)[0])) + +int socket_wait(Socket **socks, int num_socks) +{ + if (num_socks <= 0) + return -1; + + struct pollfd polled[100]; // TODO: make this value configurable + if (num_socks > (int) COUNT(polled)) + return -1; + + for (;;) { + + for (int i = 0; i < num_socks; i++) { + + int events = 0; + switch (socks[i]->event) { + case SOCKET_WANT_READ : events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; + case SOCKET_WANT_NONE : return i; + default: HTTP_ASSERT(0); break; + } + + polled[i].fd = socks[i]->fd; + polled[i].events = events; + polled[i].revents = 0; + } + + int ret = poll(polled, num_socks, -1); + if (ret < 0) + return -1; + + // Update socket states based on poll results + for (int i = 0; i < num_socks; i++) { + + if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + socks[i]->event = SOCKET_WANT_NONE; + socks[i]->state = SOCKET_STATE_DIED; + return i; + } + + if (polled[i].revents & (POLLIN | POLLOUT)) { + socks[i]->event = SOCKET_WANT_NONE; + socket_update(socks[i]); + } + } + } + + return -1; +} diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..42aa242 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,131 @@ +#ifndef SOCKET_INCLUDED +#define SOCKET_INCLUDED +// This is a socket abstraction module for non-blocking TCP and TLS sockets. +// +// Sockets may be in a number of states based on if they are plain TCP or TLS +// sockets. Users generally only care about when the connection is established +// or is terminated. +// +// Sockets can be created by connecting to a server using one of these: +// +// socket_connect +// socket_connect_ipv4 +// socket_connect_ipv6 +// +// They allow connecting to a remote host by specifying its name, of IP address. +// Or by interning a socket accepted by a listening socket: +// +// socket_accept +// +// after creation, the event field will hold one of the values: +// +// SOCKET_WANT_READ +// SOCKET_WANT_WRITE +// +// Which respectively mean that the socket object needs to read or write +// from the underlying socket, and to do so non-blockingly, the caller needs +// to wait for the socket being ready for that operation. This is one way +// to do it: +// +// // Translate the socket event field to poll() flags +// int events; +// if (sock.event == SOCKET_WANT_READ) +// events = POLLIN; +// else if (sock.event == SOCKET_WANT_WRITE) +// events = POLLOUT; +// +// // block until the socket is ready +// struct pollfd buf; +// buf.fd = sock.fd; +// buf.events = events; +// buf.revents = 0; +// poll(&buf, 1, -1); +// +// whenever a socket is ready, the user must call the socket_update +// function. Then, if the socket is in the SOCKET_STATE_ESTABLISHED_READY +// state, the user can call one of +// +// socket_close +// socket_read +// socket_write +// +// At any point the socket could reach the SOCKET_STATE_DIED state, +// which means the user needs to call socket_free to free the socket +// as it's not unusable. + +#include + +#include "parse.h" +#include +#include +#include + +typedef struct { + int is_ipv6; + union { + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + } addr; +} AddrInfo; + +typedef enum { + SOCKET_STATE_PENDING, + SOCKET_STATE_CONNECTING, + SOCKET_STATE_CONNECTED, + SOCKET_STATE_ACCEPTED, + SOCKET_STATE_ESTABLISHED_WAIT, + SOCKET_STATE_ESTABLISHED_READY, + SOCKET_STATE_SHUTDOWN, + SOCKET_STATE_DIED +} SocketState; + +typedef enum { + SOCKET_WANT_NONE, + SOCKET_WANT_READ, + SOCKET_WANT_WRITE, +} SocketWantEvent; + +typedef struct { + SocketState state; + SocketWantEvent event; + int fd; + SSL *ssl; + SSL_CTX *ssl_ctx; + AddrInfo *addr_list; + int addr_count; + int addr_cursor; + char *hostname; + uint16_t port; +} Socket; + +typedef struct { + char name[128]; + SSL_CTX *ssl_ctx; +} Domain; + +typedef struct { + SSL_CTX *ssl_ctx; + int num_domains; + int max_domains; + Domain *domains; +} SocketGroup; + +void socket_global_init (void); +void socket_global_free (void); +int socket_group_init (SocketGroup *group); +int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file); +int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_key, HTTP_String private_key); +void socket_group_free (SocketGroup *group); +SocketState socket_state (Socket *sock); +void socket_accept (Socket *sock, SocketGroup *group, int fd); +void socket_connect (Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port); +void socket_connect_ipv4 (Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port); +void socket_connect_ipv6 (Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port); +void socket_update (Socket *sock); +int socket_read (Socket *sock, char *dst, int max); +int socket_write (Socket *sock, char *src, int len); +void socket_close (Socket *sock); +void socket_free (Socket *sock); +int socket_wait (Socket **socks, int num_socks); + +#endif // SOCKET_INCLUDED \ No newline at end of file diff --git a/tinyhttp.h b/tinyhttp.h deleted file mode 100644 index 24bbda1..0000000 --- a/tinyhttp.h +++ /dev/null @@ -1,477 +0,0 @@ -#ifndef HTTP_INCLUDED -#define HTTP_INCLUDED - -#ifndef HTTP_PARSE -#define HTTP_PARSE 1 -#endif - -#ifndef HTTP_ENGINE -#define HTTP_ENGINE 1 -#endif - -#ifndef HTTP_PROXY_ENGINE -#define HTTP_PROXY_ENGINE 1 -#endif - -#ifndef HTTP_CLIENT -#define HTTP_CLIENT 1 -#endif - -#ifndef HTTP_SERVER -#define HTTP_SERVER 1 -#endif - -#ifndef HTTP_PROXY -#define HTTP_PROXY 1 -#endif - -#ifndef HTTP_ROUTER -#define HTTP_ROUTER 1 -#endif - -///////////////////////////////////////////////////////////////////// -// UTILITIES -///////////////////////////////////////////////////////////////////// - -#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1}) -#define HTTP_CEIL(X, Y) (((X) + (Y) - 1) / (Y)) - -typedef struct { - char *ptr; - long len; -} HTTP_String; - -int http_streq (HTTP_String s1, HTTP_String s2); -int http_streqcase (HTTP_String s1, HTTP_String s2); -HTTP_String http_trim (HTTP_String s); - -///////////////////////////////////////////////////////////////////// -// HTTP PARSER -///////////////////////////////////////////////////////////////////// -#if HTTP_PARSE - -#define HTTP_MAX_HEADERS 32 - -typedef struct { - unsigned int data; -} HTTP_IPv4; - -typedef struct { - unsigned short data[8]; -} HTTP_IPv6; - -typedef enum { - HTTP_HOST_MODE_VOID = 0, - HTTP_HOST_MODE_NAME, - HTTP_HOST_MODE_IPV4, - HTTP_HOST_MODE_IPV6, -} HTTP_HostMode; - -typedef struct { - HTTP_HostMode mode; - HTTP_String text; - union { - HTTP_String name; - HTTP_IPv4 ipv4; - HTTP_IPv6 ipv6; - }; -} HTTP_Host; - -typedef struct { - HTTP_String userinfo; - HTTP_Host host; - int port; -} HTTP_Authority; - -// ZII -typedef struct { - HTTP_String scheme; - HTTP_Authority authority; - HTTP_String path; - HTTP_String query; - HTTP_String fragment; -} HTTP_URL; - -typedef enum { - HTTP_METHOD_GET, - HTTP_METHOD_HEAD, - HTTP_METHOD_POST, - HTTP_METHOD_PUT, - HTTP_METHOD_DELETE, - HTTP_METHOD_CONNECT, - HTTP_METHOD_OPTIONS, - HTTP_METHOD_TRACE, - HTTP_METHOD_PATCH, -} HTTP_Method; - -typedef struct { - HTTP_String name; - HTTP_String value; -} HTTP_Header; - -typedef struct { - HTTP_Method method; - HTTP_URL url; - int minor; - int num_headers; - HTTP_Header headers[HTTP_MAX_HEADERS]; - HTTP_String body; -} HTTP_Request; - -typedef struct { - int minor; - int status; - HTTP_String reason; - int num_headers; - HTTP_Header headers[HTTP_MAX_HEADERS]; - HTTP_String body; -} HTTP_Response; - -int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4); -int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6); -int http_parse_url (char *src, int len, HTTP_URL *url); -int http_parse_request (char *src, int len, HTTP_Request *req); -int http_parse_response (char *src, int len, HTTP_Response *res); - -HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name); -HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name); - -#endif // HTTP_PARSE -///////////////////////////////////////////////////////////////////// -// HTTP BYTE QUEUE -///////////////////////////////////////////////////////////////////// -#if HTTP_ENGINE - -typedef enum { - HTTP_MEMFUNC_MALLOC, - HTTP_MEMFUNC_FREE, -} HTTP_MemoryFuncTag; - -typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data); - -typedef struct { - - HTTP_MemoryFunc memfunc; - void *memfuncdata; - - unsigned long long curs; - - char* data; - unsigned int head; - unsigned int size; - unsigned int used; - unsigned int limit; - - char* read_target; - unsigned int read_target_size; - - int flags; -} HTTP_ByteQueue; - -typedef unsigned long long HTTP_ByteQueueOffset; - -#endif // HTTP_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP ENGINE -///////////////////////////////////////////////////////////////////// -#if HTTP_ENGINE - -#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) -#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) -#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) -#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) -#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) -#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) -#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) -#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) -#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) -#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) -#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) -#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) -#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) -#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) - -typedef enum { - HTTP_ENGINE_STATE_NONE = 0, - HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, - HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, - HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, - HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, - HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, -} HTTP_EngineState; - -typedef struct { - HTTP_EngineState state; - HTTP_ByteQueue input; - HTTP_ByteQueue output; - int numexch; - int reqsize; - int closing; - int keepalive; - HTTP_ByteQueueOffset response_offset; - HTTP_ByteQueueOffset content_length_offset; - HTTP_ByteQueueOffset content_length_value_offset; - union { - HTTP_Request req; - HTTP_Response res; - } result; -} HTTP_Engine; - -void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_engine_free (HTTP_Engine *eng); - -void http_engine_close (HTTP_Engine *eng); -HTTP_EngineState http_engine_state (HTTP_Engine *eng); - -char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); -void http_engine_recvack (HTTP_Engine *eng, int num); -char* http_engine_sendbuf (HTTP_Engine *eng, int *len); -void http_engine_sendack (HTTP_Engine *eng, int num); - -HTTP_Request* http_engine_getreq (HTTP_Engine *eng); -HTTP_Response* http_engine_getres (HTTP_Engine *eng); - -void http_engine_url (HTTP_Engine *eng, HTTP_Method method, char *url, int minor); -void http_engine_status (HTTP_Engine *eng, int status); -void http_engine_header (HTTP_Engine *eng, const char *src, int len); -void http_engine_body (HTTP_Engine *eng, void *src, int len); -void http_engine_bodycap (HTTP_Engine *eng, int mincap); -char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); -void http_engine_bodyack (HTTP_Engine *eng, int num); -void http_engine_done (HTTP_Engine *eng); -void http_engine_undo (HTTP_Engine *eng); - -#endif // HTTP_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP PROXY ENGINE -///////////////////////////////////////////////////////////////////// -#if HTTP_PROXY_ENGINE - -typedef struct { - HTTP_Engine in; - HTTP_Engine out; -} HTTP_ProxyEngine; - -typedef enum { - HTTP_PROXY_ENGINE_STATE_NONE, - HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_BUF, - HTTP_PROXY_ENGINE_STATE_CLIENT_RECV_ACK, - HTTP_PROXY_ENGINE_STATE_SERVER_SEND_BUF, - HTTP_PROXY_ENGINE_STATE_SERVER_SEND_ACK, - HTTP_PROXY_ENGINE_STATE_SERVER_RECV_BUF, - HTTP_PROXY_ENGINE_STATE_SERVER_RECV_ACK, - HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_BUF, - HTTP_PROXY_ENGINE_STATE_CLIENT_SEND_ACK, - HTTP_PROXY_ENGINE_STATE_CLOSED, -} HTTP_ProxyEngineState; - -void http_proxyengine_init(HTTP_ProxyEngine *eng, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_proxyengine_free(HTTP_ProxyEngine *eng); -HTTP_ProxyEngineState http_proxyengine_state(HTTP_ProxyEngine *eng); -void http_proxyengine_close(HTTP_ProxyEngine *eng); -char* http_proxyengine_serverrecvbuf(HTTP_ProxyEngine *eng, int *cap); -void http_proxyengine_serverrecvack(HTTP_ProxyEngine *eng, int num); -char* http_proxyengine_serversendbuf(HTTP_ProxyEngine *eng, int *len); -void http_proxyengine_serversendack(HTTP_ProxyEngine *eng, int num); -char* http_proxyengine_clientrecvbuf(HTTP_ProxyEngine *eng, int *cap); -void http_proxyengine_clientrecvack(HTTP_ProxyEngine *eng, int num); -char* http_proxyengine_clientsendbuf(HTTP_ProxyEngine *eng, int *len); -void http_proxyengine_clientsendack(HTTP_ProxyEngine *eng, int num); - -#endif // HTTP_PROXY_ENGINE -///////////////////////////////////////////////////////////////////// -// HTTP CLIENT AND SERVER -///////////////////////////////////////////////////////////////////// -#if HTTP_CLIENT || HTTP_SERVER || HTTP_PROXY - -typedef unsigned long long HTTP_Socket; - -#endif // HTTP_CLIENT || HTTP_SERVER -///////////////////////////////////////////////////////////////////// -// HTTP CLIENT -///////////////////////////////////////////////////////////////////// -#if HTTP_CLIENT - -#define HTTP_CLIENT_TLS 0 - -#define HTTP_CLIENT_WAIT_LIMIT (1<<9) - -typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSContext; -typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSClientContext; - -typedef enum { - HTTP_STATE_CLIENT_IDLE, - HTTP_STATE_CLIENT_CONNECT, - HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV, - HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND, - HTTP_STATE_CLIENT_RECV, - HTTP_STATE_CLIENT_SEND, - HTTP_STATE_CLIENT_CLOSED, - HTTP_STATE_CLIENT_READY, -} HTTP_ClientState; - -enum { - HTTP_CLIENT_OK = 0, - HTTP_CLIENT_ERROR_INVURL = -1, - HTTP_CLIENT_ERROR_NOSYS = -2, - HTTP_CLIENT_ERROR_INVPROTO = -3, - HTTP_CLIENT_ERROR_FSOCK = -4, - HTTP_CLIENT_ERROR_FCONNECT = -5, - HTTP_CLIENT_ERROR_DNS = -6, - HTTP_CLIENT_ERROR_FSSLNEW = -7, - HTTP_CLIENT_ERROR_FRECV = -8, - HTTP_CLIENT_ERROR_FSSLREAD = -9, - HTTP_CLIENT_ERROR_FSEND = -10, - HTTP_CLIENT_ERROR_FSSLWRITE = -11, - HTTP_CLIENT_ERROR_FGETSOCKOPT = -12, - HTTP_CLIENT_ERROR_FSSLCONNECT = -13, -}; - -typedef struct { - int secure; - int code; - HTTP_Socket fd; - HTTP_Engine eng; - HTTP_ClientState state; - HTTP_TLSClientContext tls; -} HTTP_Client; - -void http_tls_global_init(void); -void http_tls_global_free(void); - -int http_tls_init(HTTP_TLSContext *tls); -void http_tls_free(HTTP_TLSContext *tls); - -void http_client_init(HTTP_Client *client); -void http_client_free(HTTP_Client *client); - -void http_client_startreq( - HTTP_Client *client, HTTP_Method method, - const char *url, HTTP_String *headers, - int num_headers, char *body, int body_len, - HTTP_TLSContext *tls); - -int http_client_waitany(HTTP_Client **clients, int num_clients, int timeout); -int http_client_waitall(HTTP_Client **clients, int num_clients, int timeout); - -int http_client_result(HTTP_Client *client, HTTP_Response **res); -const char *http_client_strerror(int code); - -#endif // HTTP_CLIENT -///////////////////////////////////////////////////////////////////// -// HTTP SERVER -///////////////////////////////////////////////////////////////////// -#if HTTP_SERVER - -#define HTTP_MAX_CLIENTS_PER_SERVER (1<<9) - -typedef unsigned long long HTTP_BitsetWord; -typedef struct { - HTTP_BitsetWord data[HTTP_CEIL(HTTP_MAX_CLIENTS_PER_SERVER, - sizeof(HTTP_BitsetWord))]; -} HTTP_Bitset; - -typedef struct { - int head; - int count; - int items[HTTP_MAX_CLIENTS_PER_SERVER]; - HTTP_Bitset set; -} HTTP_IntQueue; - -typedef struct { - void *ptr; - int idx; - int gen; -} HTTP_ResponseHandle; - -typedef struct { - HTTP_Socket fd; - HTTP_Engine eng; - unsigned short gen; -} HTTP_ServerConnection; - -typedef struct { - HTTP_Socket listen_fd; - HTTP_IntQueue ready; - int num_conns; - HTTP_ServerConnection conns[HTTP_MAX_CLIENTS_PER_SERVER]; -} HTTP_Server; - -int http_server_init(HTTP_Server *server, - const char *addr, int port); - -void http_server_free(HTTP_Server *server); - -int http_server_wait(HTTP_Server *server, HTTP_Request **req, - HTTP_ResponseHandle *res, int timeout); - -void http_response_status(HTTP_ResponseHandle res, int status); -void http_response_header(HTTP_ResponseHandle res, const char *fmt, ...); -void http_response_body(HTTP_ResponseHandle res, char *src, int len); -void http_response_done(HTTP_ResponseHandle res); -void http_response_undo(HTTP_ResponseHandle res); - -#endif // HTTP_SERVER -///////////////////////////////////////////////////////////////////// -// HTTP PROXY -///////////////////////////////////////////////////////////////////// -#if HTTP_PROXY - -#define HTTP_MAX_CONNS_PER_PROXY (1<<10) - -typedef struct { - HTTP_Socket client_sock; - HTTP_Socket server_sock; - HTTP_ProxyEngine eng; -} HTTP_ProxyConnection; - -typedef struct { - HTTP_Socket listen_sock; - int num_conns; - HTTP_ProxyConnection conns[HTTP_MAX_CONNS_PER_PROXY]; -} HTTP_Proxy; - -int http_proxy_init(HTTP_Proxy *proxy, const char *addr, int port); -void http_proxy_free(HTTP_Proxy *proxy); -int http_proxy_wait(HTTP_Proxy *proxy, int timeout); - -#endif // HTTP_PROXY -///////////////////////////////////////////////////////////////////// -// HTTP ROUTER -///////////////////////////////////////////////////////////////////// -#if HTTP_ROUTER - -typedef struct HTTP_Router HTTP_Router; -typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; - -HTTP_Router* http_router_init (void); -void http_router_free (HTTP_Router *router); -void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res); -void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path); -void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*); -int http_serve (const char *addr, int port, HTTP_Router *router); - -#endif // HTTP_ROUTER -///////////////////////////////////////////////////////////////////// -// THE END -///////////////////////////////////////////////////////////////////// -#endif // HTTP_INCLUDED \ No newline at end of file