Author SHA1 Message Date
cozis 71b19ac358 Update README.md 2025-12-21 08:58:36 +01:00
cozis b8eaeedd58 Increate output buffer limit to 16MB 2025-12-18 13:55:36 +01:00
cozis 7f0c638b62 Don't assert that requests have a host header 2025-12-18 10:37:28 +01:00
cozis 02d8206e9d Load full certificate chains, not just the leafs 2025-12-15 23:08:31 +01:00
cozis 2eb6a5fe9d Free SSL socket objects when clients disconnect from the server 2025-12-15 22:37:40 +01:00
cozis e2d52f8b63 Bug fix triggered by HTTPS connections 2025-12-15 22:23:07 +01:00
cozis f3345ba670 Properly mark server connections as FREE when they are deinitialized 2025-12-15 22:22:43 +01:00
cozis c681590fa2 Fix typo in assertion 2025-12-15 08:45:06 +01:00
cozis 1f9411119a Add request timeouts 2025-12-12 22:36:27 +01:00
cozis b361125bc3 Add second argument to _Static_assert to silence clang warnings 2025-12-12 19:53:53 +01:00
cozis 04deae62d6 Replace http_/HTTP_ prefixes with chttp_/CHTTP_ 2025-12-09 12:12:21 +01:00
cozis db030b3834 Add missing OpenSSL include 2025-12-09 08:57:39 +01:00
cozis e686e477e2 Ignore socket events when an HTTP response is being built by the user 2025-12-03 18:40:26 +01:00
cozis f6d81c1a87 Add -Wall -Wextra flags to the build script and fix the resulting compiler warnings 2025-12-02 12:02:59 +01:00
cozis f1fd633356 Add Content-Length to HTTP requests 2025-11-29 21:28:56 +01:00
cozis 64b40dfac7 Close socket when an HTTP response is received 2025-11-29 19:56:54 +01:00
cozis 954bcb7e3e Add option for HTTPS clients to not verify the peer's certificate 2025-11-29 19:56:09 +01:00
cozis 008772aef1 Fix bug in response builder state machine 2025-11-27 21:02:31 +01:00
32 changed files with 2576 additions and 2227 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
CFLAGS = -ggdb
CFLAGS = -ggdb -Wall -Wextra
LFLAGS =
ifeq ($(shell uname -s),Linux)
+150 -6
View File
@@ -1,12 +1,156 @@
# cHTTP
This is an HTTP library for C, featuring an HTTP(S) server, HTTP(S) client, and much more!
cHTTP is an HTTP client and server library distributed as a single file with support for HTTPS, virtual hosts, fully non-blocking operations.
## Quick Start
### Your first request
The simplest way to perform a GET request looks like this:
```c
#include "chttp.h"
int main(void)
{
CHTTP_Response *response;
int ret = chttp_get(CHTTP_STR("http://coz.is/"), NULL, 0, &response);
if (ret == CHTTP_OK) {
printf("Received %d bytes\n", response->body.len);
chttp_free_response(response);
} else {
printf("Request failure: %s\n", chttp_strerror(ret));
}
return 0;
}
```
(Note the `http:` schema. If you want HTTPS, you'll have to enable it explicitly! Refer to the HTTPS section.)
Copy this code to `first_request.c` near `chttp.c` and compile it by running:
```sh
# Linux
gcc chttp.c first_request.c -o first_request
# Windows (mingw)
gcc chttp.c first_request.c -o first_request.exe -lws2_32
```
Then, run the program
```sh
# Linux
./first_request
# Windows
.\first_request.exe
```
Done!
### Your first server
The setup for a basic server looks like this:
```c
#include "chttp.h"
int main(void)
{
int ret;
CHTTP_Server server;
ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
for (;;) {
CHTTP_Request *request;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &request, &builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello, world!"));
chttp_response_builder_send(builder);
}
chttp_server_free(&server);
return 0;
}
```
Copy this code to a `first_server.c` file and compile it by running
```sh
# Linux
gcc chttp.c first_server.c -o first_server
# Windows (mingw)
gcc chttp.c first_server.c -o first_server.exe -lws2_32
```
Then, run the program
```sh
# Linux
./first_server
# Windows
.\first_server.exe
```
While the program is running, open a browser and visit `http://127.0.0.1:8080/`. You should see the text "Hello, world!" sent by the server and a log of the HTTP requests and responses processed by the server in the console.
## HTTPS
HTTPS is supported via OpenSSL, which is easily available on Linux and less so on Windows.
First, install the OpenSSL development libraries:
```sh
# Ubuntu/Debian Linux
sudo apt install libssl-dev gcc
```
Then, enable HTTPS by compiling your program with the following flags:
```sh
# Linux
gcc chttp.c main.c -lssl -lcrypto -DHTTPS_ENABLED
# Windows
gcc chttp.c main.c -lws2_32 -lssl -lcrypto -DHTTPS_ENABLED
```
## Development Status
The major limitation of cHTTP is HTTPS on Windows. For that to work correctly it will be necessary to port the OpenSSL code to SChannel.
Other limitations:
* HTTP client doesn't follow redirections (responses with code 3xx)
* Support for HTTP client cookies is limited
* HTTP server adherence to the spec can be improved
## Contributing
Contributions are welcome! The following are some notes on how to work with the codebase. Don't worry if you get something wrong. I will remind you.
The source code in the `src/` directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons:
1. You never need need to include other cHTTP source files
2. All inclusions of third-party headers are to be placed inside `src/includes.h`
3. All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file.
4. All files must end with a single empty line.
The source code in the src/ directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons:
* You never need need to include other cHTTP source files
* All inclusions of third-party headers are to be placed inside src/includes.h
* All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file.
* All files must end with a single empty line.
+811 -749
View File
File diff suppressed because it is too large Load Diff
+303 -264
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -2,14 +2,14 @@
int main(void)
{
HTTP_Response *response;
CHTTP_Response *response;
int ret = http_get(HTTP_STR("http://coz.is/"), NULL, 0, &response);
if (ret == HTTP_OK) {
int ret = chttp_get(CHTTP_STR("http://coz.is/"), NULL, 0, &response);
if (ret == CHTTP_OK) {
printf("Received %d bytes\n", response->body.len);
http_free_response(response);
chttp_free_response(response);
} else {
printf("Request failure: %s\n", http_strerror(ret));
printf("Request failure: %s\n", chttp_strerror(ret));
}
return 0;
}
+14 -14
View File
@@ -4,33 +4,33 @@ int main(void)
{
int ret;
HTTP_Server server;
ret = http_server_init(&server);
CHTTP_Server server;
ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
ret = http_server_listen_tcp(&server, HTTP_STR("127.0.0.1"), 8080);
ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
for (;;) {
HTTP_Request *request;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &request, &builder);
CHTTP_Request *request;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &request, &builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello, world!"));
chttp_response_builder_send(builder);
}
http_server_free(&server);
chttp_server_free(&server);
return 0;
}
+63 -54
View File
@@ -9,45 +9,52 @@
#include "../chttp.h"
static int pick_timeout(int t1, int t2)
{
if (t1 < 0) return t2;
if (t2 < 0) return t1;
return (t1 < t2) ? t1 : t2;
}
int main()
{
int ret;
HTTP_String remote = HTTP_STR("http://coz.is");
CHTTP_String remote = CHTTP_STR("http://coz.is");
HTTP_Client client;
ret = http_client_init(&client);
CHTTP_Client client;
ret = chttp_client_init(&client);
if (ret < 0) {
printf("Couldn't initialize client (%s)\n", http_strerror(ret));
printf("Couldn't initialize client (%s)\n", chttp_strerror(ret));
return -1;
}
HTTP_Server server;
ret = http_server_init(&server);
CHTTP_Server server;
ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
ret = http_server_listen_tcp(&server, HTTP_STR("127.0.0.1"), 8080);
ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
bool used[HTTP_SERVER_CAPACITY];
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
bool used[CHTTP_SERVER_CAPACITY];
for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++)
used[i] = false;
HTTP_ResponseBuilder pending[HTTP_SERVER_CAPACITY];
CHTTP_ResponseBuilder pending[CHTTP_SERVER_CAPACITY];
int num_pending = 0;
for (;;) {
#define POLL_CAPACITY (HTTP_CLIENT_POLL_CAPACITY + HTTP_SERVER_POLL_CAPACITY)
#define POLL_CAPACITY (CHTTP_CLIENT_POLL_CAPACITY + CHTTP_SERVER_POLL_CAPACITY)
void *ptrs[POLL_CAPACITY];
struct pollfd polled[POLL_CAPACITY];
@@ -55,82 +62,84 @@ int main()
EventRegister server_reg = {
ptrs,
polled,
0
0,
-1
};
http_server_register_events(&server, &server_reg);
chttp_server_register_events(&server, &server_reg);
EventRegister client_reg = {
ptrs + server_reg.num_polled,
polled + server_reg.num_polled,
0
0,
-1
};
http_client_register_events(&client, &client_reg);
chttp_client_register_events(&client, &client_reg);
if (server_reg.num_polled > 0 ||
client_reg.num_polled > 0) {
int num_polled = server_reg.num_polled
+ client_reg.num_polled;
POLL(polled, num_polled, -1);
}
int timeout = pick_timeout(server_reg.timeout, client_reg.timeout);
POLL(polled, num_polled, timeout);
int result;
void *user;
HTTP_Response *response;
http_client_process_events(&client, client_reg);
while (http_client_next_response(&client, &result, &user, &response)) {
CHTTP_Response *response;
chttp_client_process_events(&client, client_reg);
while (chttp_client_next_response(&client, &result, &user, &response)) {
HTTP_ResponseBuilder *builder = (HTTP_ResponseBuilder*) user;
CHTTP_ResponseBuilder *builder = (CHTTP_ResponseBuilder*) user;
int i = builder - pending;
assert(i > -1);
assert(i < HTTP_SERVER_CAPACITY);
assert(i < CHTTP_SERVER_CAPACITY);
if (result == HTTP_OK) {
http_response_builder_status(*builder, response->status);
http_response_builder_body(*builder, response->body);
http_response_builder_send(*builder);
if (result == CHTTP_OK) {
chttp_response_builder_status(*builder, response->status);
chttp_response_builder_body(*builder, response->body);
chttp_response_builder_send(*builder);
} else {
http_response_builder_status(*builder, 500);
http_response_builder_send(*builder);
chttp_response_builder_status(*builder, 500);
chttp_response_builder_send(*builder);
}
used[i] = false;
num_pending--;
}
HTTP_Request *request;
HTTP_ResponseBuilder response_builder;
http_server_process_events(&server, server_reg);
while (http_server_next_request(&server, &request, &response_builder)) {
CHTTP_Request *request;
CHTTP_ResponseBuilder response_builder;
chttp_server_process_events(&server, server_reg);
while (chttp_server_next_request(&server, &request, &response_builder)) {
int i = 0;
while (used[i]) {
i++;
assert(i < HTTP_SERVER_CAPACITY);
assert(i < CHTTP_SERVER_CAPACITY);
}
HTTP_String path = request->url.path;
CHTTP_String path = request->url.path;
if (path.len == 0)
path = HTTP_STR("/");
path = CHTTP_STR("/");
char target[1<<10];
int target_len = snprintf(target, sizeof(target),
"%.*s%.*s", HTTP_UNPACK(remote), HTTP_UNPACK(path));
"%.*s%.*s", CHTTP_UNPACK(remote), CHTTP_UNPACK(path));
if (target_len < 0 || target_len >= (int) sizeof(target)) {
http_response_builder_status(response_builder, 500);
http_response_builder_send(response_builder);
chttp_response_builder_status(response_builder, 500);
chttp_response_builder_send(response_builder);
continue;
}
HTTP_RequestBuilder request_builder = http_client_get_builder(&client);
http_request_builder_set_user(request_builder, &pending[i]);
http_request_builder_trace(request_builder, true);
http_request_builder_method(request_builder, request->method);
http_request_builder_target(request_builder, (HTTP_String) { target, target_len });
ret = http_request_builder_send(request_builder);
CHTTP_RequestBuilder request_builder = chttp_client_get_builder(&client);
chttp_request_builder_set_user(request_builder, &pending[i]);
chttp_request_builder_trace(request_builder, true);
chttp_request_builder_method(request_builder, request->method);
chttp_request_builder_target(request_builder, (CHTTP_String) { target, target_len });
ret = chttp_request_builder_send(request_builder);
if (ret < 0) {
http_response_builder_status(response_builder, 500);
http_response_builder_send(response_builder);
chttp_response_builder_status(response_builder, 500);
chttp_response_builder_send(response_builder);
continue;
}
@@ -140,7 +149,7 @@ int main()
}
}
http_server_free(&server);
http_client_free(&client);
chttp_server_free(&server);
chttp_client_free(&client);
return 0;
}
+23 -23
View File
@@ -16,21 +16,21 @@ int main(void)
int ret;
HTTP_Server server;
ret = http_server_init(&server);
CHTTP_Server server;
ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
HTTP_String local_addr = HTTP_STR("127.0.0.1");
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
uint16_t local_port = 8080;
ret = http_server_listen_tcp(&server, local_addr, local_port);
ret = chttp_server_listen_tcp(&server, local_addr, local_port);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
@@ -48,29 +48,29 @@ int main(void)
for (;;) {
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
if (http_match_host(req, HTTP_STR("websiteB.com"), local_port)) {
if (chttp_match_host(req, CHTTP_STR("websiteB.com"), local_port)) {
// Website B
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!"));
chttp_response_builder_send(builder);
} else if (http_match_host(req, HTTP_STR("websiteC.com"), local_port)) {
} else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), local_port)) {
// Website C
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!"));
chttp_response_builder_send(builder);
} else {
// Serve websiteA by default
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
}
}
http_server_free(&server);
chttp_server_free(&server);
return 0;
}
+17 -17
View File
@@ -4,39 +4,39 @@
int main(void)
{
HTTP_String local_addr = HTTP_STR("127.0.0.1");
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
uint16_t local_port = 8443;
HTTP_String cert_file = HTTP_STR("websiteA_cert.pem");
HTTP_String key_file = HTTP_STR("websiteA_key.pem");
CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem");
CHTTP_String key_file = CHTTP_STR("websiteA_key.pem");
HTTP_Server server;
int ret = http_server_init(&server);
CHTTP_Server server;
int ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
ret = http_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
for (;;) {
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
}
http_server_free(&server);
chttp_server_free(&server);
return 0;
}
+53 -53
View File
@@ -38,47 +38,47 @@ int main(void)
if (ret < 0)
return -1;
HTTP_Server server;
ret = http_server_init(&server);
CHTTP_Server server;
ret = chttp_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
return -1;
}
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
// 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_String local_addr = HTTP_STR("127.0.0.1");
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
uint16_t local_port = 8443;
HTTP_String cert_file = HTTP_STR("websiteA_cert.pem");
HTTP_String key_file = HTTP_STR("websiteA_key.pem");
CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem");
CHTTP_String key_file = CHTTP_STR("websiteA_key.pem");
ret = http_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
return -1;
}
// Then we can add an arbitrary number of additional
// certificates using the add_website function
ret = http_server_add_certificate(&server,
HTTP_STR("websiteB.com"),
HTTP_STR("websiteB_cert.pem"),
HTTP_STR("websiteB_key.pem"));
ret = chttp_server_add_certificate(&server,
CHTTP_STR("websiteB.com"),
CHTTP_STR("websiteB_cert.pem"),
CHTTP_STR("websiteB_key.pem"));
if (ret < 0)
return -1;
ret = http_server_add_certificate(&server,
HTTP_STR("websiteC.com"),
HTTP_STR("websiteC_cert.pem"),
HTTP_STR("websiteC_key.pem"));
ret = chttp_server_add_certificate(&server,
CHTTP_STR("websiteC.com"),
CHTTP_STR("websiteC_cert.pem"),
CHTTP_STR("websiteC_key.pem"));
if (ret < 0)
return -1;
@@ -109,67 +109,67 @@ int main(void)
for (;;) {
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) ||
http_match_host(req, HTTP_STR("websiteB.com"), 8443)) {
if (chttp_match_host(req, CHTTP_STR("websiteB.com"), 8080) ||
chttp_match_host(req, CHTTP_STR("websiteB.com"), 8443)) {
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!"));
chttp_response_builder_send(builder);
} else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) ||
http_match_host(req, HTTP_STR("websiteC.com"), 8443)) {
} else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), 8080) ||
chttp_match_host(req, CHTTP_STR("websiteC.com"), 8443)) {
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!"));
chttp_response_builder_send(builder);
} else {
// Serve websiteA.com by default to be consistent
// with the certificate setup
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
}
}
http_server_free(&server);
chttp_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")
int ret = chttp_create_test_certificate(
CHTTP_STR("IT"),
CHTTP_STR("Organization A"),
CHTTP_STR("websiteA.com"),
CHTTP_STR("websiteA_cert.pem"),
CHTTP_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")
ret = chttp_create_test_certificate(
CHTTP_STR("IT"),
CHTTP_STR("Organization B"),
CHTTP_STR("websiteB.com"),
CHTTP_STR("websiteB_cert.pem"),
CHTTP_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")
ret = chttp_create_test_certificate(
CHTTP_STR("IT"),
CHTTP_STR("Organization C"),
CHTTP_STR("websiteC.com"),
CHTTP_STR("websiteC_cert.pem"),
CHTTP_STR("websiteC_key.pem")
);
if (ret < 0)
return -1;
+15 -15
View File
@@ -4,37 +4,37 @@ int main(void)
{
int ret;
HTTP_Client client;
ret = http_client_init(&client);
CHTTP_Client client;
ret = chttp_client_init(&client);
if (ret < 0) {
printf("Couldn't initialize client (%s)\n", http_strerror(ret));
printf("Couldn't initialize client (%s)\n", chttp_strerror(ret));
return -1;
}
HTTP_RequestBuilder builder = http_client_get_builder(&client);
CHTTP_RequestBuilder builder = chttp_client_get_builder(&client);
http_request_builder_method(builder, HTTP_METHOD_GET);
http_request_builder_target(builder, HTTP_STR("http://coz.is"));
http_request_builder_header(builder, HTTP_STR("Greeting: Hello from the cHTTP example!"));
chttp_request_builder_method(builder, CHTTP_METHOD_GET);
chttp_request_builder_target(builder, CHTTP_STR("http://coz.is"));
chttp_request_builder_header(builder, CHTTP_STR("Greeting: Hello from the cHTTP example!"));
ret = http_request_builder_send(builder);
ret = chttp_request_builder_send(builder);
if (ret < 0) {
printf("Couldn't build request (%s)\n", http_strerror(ret));
printf("Couldn't build request (%s)\n", chttp_strerror(ret));
return -1;
}
int result;
void *user;
HTTP_Response *response;
http_client_wait_response(&client, &result, &user, &response);
CHTTP_Response *response;
chttp_client_wait_response(&client, &result, &user, &response);
if (result == HTTP_OK) {
if (result == CHTTP_OK) {
printf("Received %d bytes\n", response->body.len);
} else {
printf("Couldn't receive response (%s)\n", http_strerror(result));
printf("Couldn't receive response (%s)\n", chttp_strerror(result));
}
http_free_response(response);
http_client_free(&client);
chttp_free_response(response);
chttp_client_free(&client);
return 0;
}
+3 -3
View File
@@ -1,3 +1,3 @@
- Add connection timeouts
- Per RFC 6265, cookie values can be quoted. This parser would include the quotes in the value.
- Make sure that the proxy example doesn't stall
[ ] Per RFC 6265, cookie values can be quoted. This parser would include the quotes in the value.
[ ] Return an appropriate error code when the user waits on a client with no pending requests
[ ] Add connection reuse limit
+6 -1
View File
@@ -53,10 +53,13 @@ license = """
"""
header = Amalgamator()
header.append_text("#ifndef CHTTP_INCLUDED\n")
header.append_text("#define CHTTP_INCLUDED\n")
header.append_text(desc)
header.append_file("src/includes.h")
header.append_file("src/basic.h")
header.append_file("src/parse.h")
header.append_file("src/time.h")
header.append_file("src/secure_context.h")
header.append_file("src/socket.h")
header.append_file("src/byte_queue.h")
@@ -64,16 +67,18 @@ header.append_file("src/cert.h")
header.append_file("src/client.h")
header.append_file("src/server.h")
header.append_text(license)
header.append_text("#endif // CHTTP_INCLUDED\n")
header.save("chttp.h")
source = Amalgamator()
source.append_text(desc)
source.append_text("\n")
source.append_text("#ifndef HTTP_DONT_INCLUDE\n")
source.append_text("#ifndef CHTTP_DONT_INCLUDE\n")
source.append_text('#include "chttp.h"\n')
source.append_text("#endif\n")
source.append_file("src/basic.c")
source.append_file("src/parse.c")
source.append_file("src/time.c")
source.append_file("src/secure_context.c")
source.append_file("src/socket.c")
source.append_file("src/byte_queue.c")
+12 -12
View File
@@ -1,5 +1,5 @@
bool http_streq(HTTP_String s1, HTTP_String s2)
bool chttp_streq(CHTTP_String s1, CHTTP_String s2)
{
if (s1.len != s2.len)
return false;
@@ -18,7 +18,7 @@ static char to_lower(char c)
return c;
}
bool http_streqcase(HTTP_String s1, HTTP_String s2)
bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2)
{
if (s1.len != s2.len)
return false;
@@ -30,7 +30,7 @@ bool http_streqcase(HTTP_String s1, HTTP_String s2)
return true;
}
HTTP_String http_trim(HTTP_String s)
CHTTP_String chttp_trim(CHTTP_String s)
{
int i = 0;
while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t'))
@@ -54,7 +54,7 @@ static bool is_printable(char c)
return c >= ' ' && c <= '~';
}
void print_bytes(HTTP_String prefix, HTTP_String src)
void print_bytes(CHTTP_String prefix, CHTTP_String src)
{
if (src.len == 0)
return;
@@ -95,16 +95,16 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
putc('\n', stream);
}
char *http_strerror(int code)
char *chttp_strerror(int code)
{
switch (code) {
case HTTP_OK: return "No error";
case HTTP_ERROR_UNSPECIFIED: return "Unspecified error";
case HTTP_ERROR_OOM: return "Out of memory";
case HTTP_ERROR_BADURL: return "Invalid URL";
case HTTP_ERROR_REQLIMIT: return "Parallel request limit reached";
case HTTP_ERROR_BADHANDLE: return "Invalid handle";
case HTTP_ERROR_NOTLS: return "TLS support not built-in";
case CHTTP_OK: return "No error";
case CHTTP_ERROR_UNSPECIFIED: return "Unspecified error";
case CHTTP_ERROR_OOM: return "Out of memory";
case CHTTP_ERROR_BADURL: return "Invalid URL";
case CHTTP_ERROR_REQLIMIT: return "Parallel request limit reached";
case CHTTP_ERROR_BADHANDLE: return "Invalid handle";
case CHTTP_ERROR_NOTLS: return "TLS support not built-in";
}
return "???";
}
+22 -22
View File
@@ -1,56 +1,56 @@
enum {
HTTP_OK = 0,
CHTTP_OK = 0,
// A generic error occurred
HTTP_ERROR_UNSPECIFIED = -1,
CHTTP_ERROR_UNSPECIFIED = -1,
// Out of memory
HTTP_ERROR_OOM = -2,
CHTTP_ERROR_OOM = -2,
// Invalid URL
HTTP_ERROR_BADURL = -3,
CHTTP_ERROR_BADURL = -3,
// Parallel request limit reached
HTTP_ERROR_REQLIMIT = -4,
CHTTP_ERROR_REQLIMIT = -4,
// Invalid handle
HTTP_ERROR_BADHANDLE = -5,
CHTTP_ERROR_BADHANDLE = -5,
// TLS support not built-in
HTTP_ERROR_NOTLS = -6,
CHTTP_ERROR_NOTLS = -6,
};
// String type used throughout cHTTP.
typedef struct {
char *ptr;
int len;
} HTTP_String;
} CHTTP_String;
// Compare two strings and return true iff they have
// the same contents.
bool http_streq(HTTP_String s1, HTTP_String s2);
bool chttp_streq(CHTTP_String s1, CHTTP_String s2);
// Compre two strings case-insensitively (uppercase and
// lowercase versions of a letter are considered the same)
// and return true iff they have the same contents.
bool http_streqcase(HTTP_String s1, HTTP_String s2);
bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2);
// Remove spaces and tabs from the start and the end of
// a string. This doesn't change the original string and
// the new one references the contents of the original one.
HTTP_String http_trim(HTTP_String s);
CHTTP_String chttp_trim(CHTTP_String s);
// Print the contents of a byte string with the given prefix.
// This is primarily used for debugging purposes.
void print_bytes(HTTP_String prefix, HTTP_String src);
void print_bytes(CHTTP_String prefix, CHTTP_String src);
// TODO: comment
char *http_strerror(int code);
char *chttp_strerror(int code);
// Macro to simplify converting string literals to
// HTTP_String.
// CHTTP_String.
//
// Instead of doing this:
//
@@ -58,7 +58,7 @@ char *http_strerror(int code);
//
// You do this:
//
// HTTP_String s = HTTP_STR("some string")
// CHTTP_String s = CHTTP_STR("some string")
//
// This is a bit cumbersome, but better than null-terminated
// strings, having a pointer and length variable pairs whenever
@@ -68,15 +68,15 @@ char *http_strerror(int code);
// #define S(X) ...
//
// But I don't want to cause collisions with user code.
#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1})
#define CHTTP_STR(X) ((CHTTP_String) {(X), sizeof(X)-1})
// Returns the number of items of a static array.
#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0]))
#define CHTTP_COUNT(X) (int) (sizeof(X) / sizeof((X)[0]))
// Macro to unpack an HTTP_String into its length and pointer components.
// Useful for passing HTTP_String to printf-style functions with "%.*s" format.
// Example: printf("%.*s", HTTP_UNPACK(str));
#define HTTP_UNPACK(X) (X).len, (X).ptr
// Macro to unpack an CHTTP_String into its length and pointer components.
// Useful for passing CHTTP_String to printf-style functions with "%.*s" format.
// Example: printf("%.*s", CHTTP_UNPACK(str));
#define CHTTP_UNPACK(X) (X).len, (X).ptr
// TODO: comment
#define HTTP_UNREACHABLE __builtin_trap()
#define CHTTP_UNREACHABLE __builtin_trap()
+3 -3
View File
@@ -179,7 +179,7 @@ int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap)
if (size > queue->limit)
size = queue->limit;
uint8_t *data = malloc(size);
char *data = malloc(size);
if (!data) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
@@ -236,7 +236,7 @@ void byte_queue_write_fmt2(ByteQueue *queue,
return;
}
if (len > dst.len) {
if ((size_t) len > dst.len) {
byte_queue_write_ack(queue, 0);
byte_queue_write_setmincap(queue, len+1);
dst = byte_queue_write_buf(queue);
@@ -277,7 +277,7 @@ void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
assert(len <= queue->used - (off - queue->curs));
// Perform the patch
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
char *dst = queue->data + queue->head + (off - queue->curs);
memcpy(dst, src, len);
}
+4 -4
View File
@@ -15,19 +15,19 @@ enum {
};
typedef struct {
uint8_t *ptr;
char *ptr;
size_t len;
} ByteView;
// Fields are for internal use only
typedef struct {
uint64_t curs;
uint8_t* data;
char* data;
uint32_t head;
uint32_t size;
uint32_t used;
uint32_t limit;
uint8_t* read_target;
char* read_target;
uint32_t read_target_size;
int flags;
} ByteQueue;
@@ -41,7 +41,7 @@ typedef uint64_t ByteQueueOffset;
// Initialize the queue with a given capacity limit.
// This is just a soft limit. The queue will allocate
// dynamically as needed up to this limit and won't
// grow further. When the limit is reached, http_queue_full
// grow further. When the limit is reached, chttp_queue_full
// returns true.
void byte_queue_init(ByteQueue *queue, uint32_t limit);
+7 -7
View File
@@ -27,7 +27,7 @@ static EVP_PKEY *generate_rsa_key_pair(int key_bits)
return pkey;
}
static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days)
static X509 *create_certificate(EVP_PKEY *pkey, CHTTP_String C, CHTTP_String O, CHTTP_String CN, int days)
{
X509 *x509 = X509_new();
if (!x509)
@@ -63,7 +63,7 @@ static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HT
return x509;
}
static int save_private_key(EVP_PKEY *pkey, HTTP_String file)
static int save_private_key(EVP_PKEY *pkey, CHTTP_String file)
{
char copy[1<<10];
if (file.len >= (int) sizeof(copy))
@@ -85,7 +85,7 @@ static int save_private_key(EVP_PKEY *pkey, HTTP_String file)
return 0;
}
static int save_certificate(X509 *x509, HTTP_String file)
static int save_certificate(X509 *x509, CHTTP_String file)
{
char copy[1<<10];
if (file.len >= (int) sizeof(copy))
@@ -107,8 +107,8 @@ static int save_certificate(X509 *x509, HTTP_String file)
return 0;
}
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file)
int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file)
{
EVP_PKEY *pkey = generate_rsa_key_pair(2048);
if (pkey == NULL)
@@ -139,8 +139,8 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
#else
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file)
int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file)
{
(void) C;
(void) O;
+2 -2
View File
@@ -12,5 +12,5 @@
//
// The output is a certificate file in PEM format and a private
// key file with the key used to sign the certificate.
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file);
int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file);
+246 -347
View File
File diff suppressed because it is too large Load Diff
+87 -77
View File
@@ -1,26 +1,26 @@
#ifndef HTTP_CLIENT_CAPACITY
#ifndef CHTTP_CLIENT_CAPACITY
// The maximum ammount of requests that can be performed
// in parallel.
#define HTTP_CLIENT_CAPACITY (1<<7)
#define CHTTP_CLIENT_CAPACITY (1<<7)
#endif
// Maximum number of descriptors the client will want
// to wait on. It's one per connection plus the wakeup
// self-pipe.
#define HTTP_CLIENT_POLL_CAPACITY (HTTP_CLIENT_CAPACITY+1)
#define CHTTP_CLIENT_POLL_CAPACITY (CHTTP_CLIENT_CAPACITY+1)
#ifndef HTTP_COOKIE_JAR_CAPACITY
#ifndef CHTTP_COOKIE_JAR_CAPACITY
// Maximum number of cookies that can be associated to a
// single client.
#define HTTP_COOKIE_JAR_CAPACITY 128
#define CHTTP_COOKIE_JAR_CAPACITY 128
#endif
typedef struct {
// Cookie name and value
HTTP_String name;
HTTP_String value;
CHTTP_String name;
CHTTP_String value;
// If the "exact_domain" is true, the cookie
// can only be sent to the exact domain referred
@@ -28,7 +28,7 @@ typedef struct {
// "exact_domain" is false, then the cookie is
// compatible with subdomains.
bool exact_domain;
HTTP_String domain;
CHTTP_String domain;
// If "exact_path" is set, the cookie is only
// compatible with requests to paths that match
@@ -36,47 +36,40 @@ typedef struct {
// then any path that starts with "path" is
// compatible with the cookie.
bool exact_path;
HTTP_String path;
CHTTP_String path;
// This cookie can only be sent over HTTPS
bool secure;
// Expiration information
bool have_expires;
HTTP_Date expires;
bool have_max_age;
uint32_t max_age;
time_t creation_time; // Unix timestamp when cookie was created
} HTTP_CookieJarEntry;
} CHTTP_CookieJarEntry;
typedef struct {
int count;
HTTP_CookieJarEntry items[HTTP_COOKIE_JAR_CAPACITY];
} HTTP_CookieJar;
CHTTP_CookieJarEntry items[CHTTP_COOKIE_JAR_CAPACITY];
} CHTTP_CookieJar;
typedef enum {
HTTP_CLIENT_CONN_FREE,
HTTP_CLIENT_CONN_WAIT_METHOD,
HTTP_CLIENT_CONN_WAIT_URL,
HTTP_CLIENT_CONN_WAIT_HEADER,
HTTP_CLIENT_CONN_WAIT_BODY,
HTTP_CLIENT_CONN_FLUSHING,
HTTP_CLIENT_CONN_BUFFERING,
HTTP_CLIENT_CONN_COMPLETE,
} HTTP_ClientConnState;
CHTTP_CLIENT_CONN_FREE,
CHTTP_CLIENT_CONN_WAIT_METHOD,
CHTTP_CLIENT_CONN_WAIT_URL,
CHTTP_CLIENT_CONN_WAIT_HEADER,
CHTTP_CLIENT_CONN_WAIT_BODY,
CHTTP_CLIENT_CONN_FLUSHING,
CHTTP_CLIENT_CONN_BUFFERING,
CHTTP_CLIENT_CONN_COMPLETE,
} CHTTP_ClientConnState;
// Fields of this struct are private
typedef struct HTTP_Client HTTP_Client;
typedef struct CHTTP_Client CHTTP_Client;
typedef struct {
HTTP_ClientConnState state;
CHTTP_ClientConnState state;
// Handle to the socket
SocketHandle handle;
// Pointer back to the client
HTTP_Client *client;
CHTTP_Client *client;
// Generation counter for request builder validation
uint16_t gen;
@@ -88,12 +81,15 @@ typedef struct {
// TODO: comment
bool trace_bytes;
// TODO: comment
bool dont_verify_cert;
// Allocated copy of the URL string
HTTP_String url_buffer;
CHTTP_String url_buffer;
// Parsed URL for connection establishment
// All url.* pointers reference into url_buffer
HTTP_URL url;
CHTTP_URL url;
// Data received from the server
ByteQueue input;
@@ -108,11 +104,21 @@ typedef struct {
int result;
// Parsed response once complete
HTTP_Response response;
} HTTP_ClientConn;
CHTTP_Response response;
// This offset points to the first byte that comes
// after the string "Content-Length: ".
ByteQueueOffset content_length_value_offset;
// This one points to the first byte of the body.
// This allows calculating the length of the request
// content byte subtracting it from the offset reached
// when the request is marked as done.
ByteQueueOffset content_length_offset;
} CHTTP_ClientConn;
// Fields of this struct are private
struct HTTP_Client {
struct CHTTP_Client {
// Size limit of the input and output buffer of each
// connection.
@@ -120,18 +126,18 @@ struct HTTP_Client {
uint32_t output_buffer_limit;
// List of cookies created during this session
HTTP_CookieJar cookie_jar;
CHTTP_CookieJar cookie_jar;
// Array of connections. The counter contains the
// number of structs such that state!=FREE.
int num_conns;
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
CHTTP_ClientConn conns[CHTTP_CLIENT_CAPACITY];
// Queue of indices referring to connections that
// are in the COMPLETE state.
int num_ready;
int ready_head;
int ready[HTTP_CLIENT_CAPACITY];
int ready[CHTTP_CLIENT_CAPACITY];
// Asynchronous TCP and TLS socket abstraction
SocketManager sockets;
@@ -141,76 +147,80 @@ struct HTTP_Client {
// manager with a pointer to it. This allows
// allocating the exact number of sockets we
// will need.
Socket socket_pool[HTTP_CLIENT_CAPACITY];
Socket socket_pool[CHTTP_CLIENT_CAPACITY];
};
// Initialize an HTTP client object. This allows one to
// perform a number of requests in parallel.
int http_client_init(HTTP_Client *client);
int chttp_client_init(CHTTP_Client *client);
// Release resources associated to a client object.
void http_client_free(HTTP_Client *client);
void chttp_client_free(CHTTP_Client *client);
// Set input and output buffer size limit for any
// given connection. The default value is 1MB
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit);
void http_client_set_output_limit(HTTP_Client *client, uint32_t limit);
void chttp_client_set_input_limit(CHTTP_Client *client, uint32_t limit);
void chttp_client_set_output_limit(CHTTP_Client *client, uint32_t limit);
// When a thread is blocked waiting for client events,
// other threads can call this function to wake it up.
int http_client_wakeup(HTTP_Client *client);
int chttp_client_wakeup(CHTTP_Client *client);
typedef struct {
HTTP_Client *client;
CHTTP_Client *client;
uint16_t index;
uint16_t gen;
} HTTP_RequestBuilder;
} CHTTP_RequestBuilder;
// Create a new request builder object.
HTTP_RequestBuilder http_client_get_builder(HTTP_Client *client);
CHTTP_RequestBuilder chttp_client_get_builder(CHTTP_Client *client);
// TODO: comment
void http_request_builder_set_user(HTTP_RequestBuilder builder,
void chttp_request_builder_set_user(CHTTP_RequestBuilder builder,
void *user);
// TODO: comment
void http_request_builder_trace(HTTP_RequestBuilder builder,
void chttp_request_builder_trace(CHTTP_RequestBuilder builder,
bool trace_bytes);
// TODO: comment
void chttp_request_builder_insecure(CHTTP_RequestBuilder builder,
bool insecure);
// Set the method of the current request. This is the first
// function of the request builder that the user must call.
void http_request_builder_method(HTTP_RequestBuilder builder,
HTTP_Method method);
void chttp_request_builder_method(CHTTP_RequestBuilder builder,
CHTTP_Method method);
// Set the URL of the current request. This must be set after
// the method and before any header/body
void http_request_builder_target(HTTP_RequestBuilder builder,
HTTP_String url);
void chttp_request_builder_target(CHTTP_RequestBuilder builder,
CHTTP_String url);
// After the URL, the user may set zero or more headers.
void http_request_builder_header(HTTP_RequestBuilder builder,
HTTP_String str);
void chttp_request_builder_header(CHTTP_RequestBuilder builder,
CHTTP_String str);
// Append bytes to the request's body. You can call this
// any amount of times, as long as it's after having set
// the URL.
void http_request_builder_body(HTTP_RequestBuilder builder,
HTTP_String str);
void chttp_request_builder_body(CHTTP_RequestBuilder builder,
CHTTP_String str);
// Mark this request as complete. This invalidates the
// builder.
// Returns 0 on success, -1 on error.
int http_request_builder_send(HTTP_RequestBuilder builder);
int chttp_request_builder_send(CHTTP_RequestBuilder builder);
// Resets the event register with the list of descriptors
// the client wants monitored.
void http_client_register_events(HTTP_Client *client,
void chttp_client_register_events(CHTTP_Client *client,
EventRegister *reg);
// The caller has waited for poll() to return and some
// I/O events to be triggered, so now the HTTP client
// can continue its buffering and flushing operations.
void http_client_process_events(HTTP_Client *client,
void chttp_client_process_events(CHTTP_Client *client,
EventRegister reg);
// After some I/O events were processes, some responses
@@ -218,33 +228,33 @@ void http_client_process_events(HTTP_Client *client,
// buffered responses. If a request was available, true
// is returned. If no more are avaiable, false is returned.
// The returned response must be freed using the
// http_free_response function.
// chttp_free_response function.
// TODO: Better comment talking about output arguments
bool http_client_next_response(HTTP_Client *client,
int *result, void **user, HTTP_Response **response);
bool chttp_client_next_response(CHTTP_Client *client,
int *result, void **user, CHTTP_Response **response);
// TODO: comment
void http_client_wait_response(HTTP_Client *client,
int *result, void **user, HTTP_Response **response);
void chttp_client_wait_response(CHTTP_Client *client,
int *result, void **user, CHTTP_Response **response);
// Free a response object. You can't access its fields
// again after this.
void http_free_response(HTTP_Response *response);
void chttp_free_response(CHTTP_Response *response);
// Perform a blocking GET request
int http_get(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_Response **response);
int chttp_get(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_Response **response);
// Perform a blocking POST request
int http_post(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_String body,
HTTP_Response **response);
int chttp_post(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_String body,
CHTTP_Response **response);
// Perform a blocking PUT request
int http_put(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_String body,
HTTP_Response **response);
int chttp_put(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_String body,
CHTTP_Response **response);
// Perform a blocking DELETE request
int http_delete(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_Response **response);
int chttp_delete(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_Response **response);
+2 -1
View File
@@ -5,7 +5,6 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -13,6 +12,7 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <time.h>
#include <limits.h>
#include <stdarg.h>
#include <unistd.h>
@@ -27,4 +27,5 @@
#ifdef HTTPS_ENABLED
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#endif
+159 -188
View File
@@ -128,7 +128,7 @@ consume_absolute_path(Scanner *s)
// path-absolute = "/" [ segment-nz *( "/" segment ) ]
// path-rootless = segment-nz *( "/" segment )
// path-empty = 0<pchar>
static int parse_path(Scanner *s, HTTP_String *path, int abempty)
static int parse_path(Scanner *s, CHTTP_String *path, int abempty)
{
int start = s->cur;
@@ -175,7 +175,7 @@ static int parse_path(Scanner *s, HTTP_String *path, int abempty)
// (do nothing)
}
*path = (HTTP_String) {
*path = (CHTTP_String) {
s->src + start,
s->cur - start,
};
@@ -215,7 +215,7 @@ static void invert_bytes(void *p, int len)
}
}
static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4)
static int parse_ipv4(Scanner *s, CHTTP_IPv4 *ipv4)
{
unsigned int out = 0;
int i = 0;
@@ -289,7 +289,7 @@ static int parse_ipv6_comp(Scanner *s)
return (int) buf;
}
static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6)
static int parse_ipv6(Scanner *s, CHTTP_IPv6 *ipv6)
{
unsigned short head[8];
unsigned short tail[8];
@@ -359,7 +359,7 @@ static int is_regname(char c)
return is_unreserved(c) || is_sub_delim(c);
}
static int parse_regname(Scanner *s, HTTP_String *regname)
static int parse_regname(Scanner *s, CHTTP_String *regname)
{
if (s->cur == s->len || !is_regname(s->src[s->cur]))
return -1;
@@ -372,7 +372,7 @@ static int parse_regname(Scanner *s, HTTP_String *regname)
return 0;
}
static int parse_host(Scanner *s, HTTP_Host *host)
static int parse_host(Scanner *s, CHTTP_Host *host)
{
int ret;
if (s->cur < s->len && s->src[s->cur] == '[') {
@@ -380,13 +380,13 @@ static int parse_host(Scanner *s, HTTP_Host *host)
s->cur++;
int start = s->cur;
HTTP_IPv6 ipv6;
CHTTP_IPv6 ipv6;
ret = parse_ipv6(s, &ipv6);
if (ret < 0) return ret;
host->mode = HTTP_HOST_MODE_IPV6;
host->mode = CHTTP_HOST_MODE_IPV6;
host->ipv6 = ipv6;
host->text = (HTTP_String) { s->src + start, s->cur - start };
host->text = (CHTTP_String) { s->src + start, s->cur - start };
if (s->cur == s->len || s->src[s->cur] != ']')
return -1;
@@ -395,22 +395,22 @@ static int parse_host(Scanner *s, HTTP_Host *host)
} else {
int start = s->cur;
HTTP_IPv4 ipv4;
CHTTP_IPv4 ipv4;
ret = parse_ipv4(s, &ipv4);
if (ret >= 0) {
host->mode = HTTP_HOST_MODE_IPV4;
host->mode = CHTTP_HOST_MODE_IPV4;
host->ipv4 = ipv4;
} else {
s->cur = start;
HTTP_String regname;
CHTTP_String regname;
ret = parse_regname(s, &regname);
if (ret < 0) return ret;
host->mode = HTTP_HOST_MODE_NAME;
host->mode = CHTTP_HOST_MODE_NAME;
host->name = regname;
}
host->text = (HTTP_String) { s->src + start, s->cur - start };
host->text = (CHTTP_String) { s->src + start, s->cur - start };
}
return 0;
@@ -439,16 +439,16 @@ static int is_userinfo(char c)
}
// authority = [ userinfo "@" ] host [ ":" port ]
static int parse_authority(Scanner *s, HTTP_Authority *authority)
static int parse_authority(Scanner *s, CHTTP_Authority *authority)
{
HTTP_String userinfo;
CHTTP_String userinfo;
{
int start = s->cur;
CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo);
if (s->cur < s->len && s->src[s->cur] == '@') {
userinfo = (HTTP_String) {
userinfo = (CHTTP_String) {
s->src + start,
s->cur - start
};
@@ -456,11 +456,11 @@ static int parse_authority(Scanner *s, HTTP_Authority *authority)
} else {
// Rollback
s->cur = start;
userinfo = (HTTP_String) {NULL, 0};
userinfo = (CHTTP_String) {NULL, 0};
}
}
HTTP_Host host;
CHTTP_Host host;
{
int ret = parse_host(s, &host);
if (ret < 0)
@@ -487,9 +487,9 @@ static int parse_authority(Scanner *s, HTTP_Authority *authority)
return 0;
}
static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
static int parse_uri(Scanner *s, CHTTP_URL *url, int allow_fragment)
{
HTTP_String scheme = {0};
CHTTP_String scheme = {0};
{
int start = s->cur;
if (s->cur == s->len || !is_scheme_head(s->src[s->cur]))
@@ -497,7 +497,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
do
s->cur++;
while (s->cur < s->len && is_scheme_body(s->src[s->cur]));
scheme = (HTTP_String) {
scheme = (CHTTP_String) {
s->src + start,
s->cur - start,
};
@@ -508,7 +508,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
}
int abempty = 0;
HTTP_Authority authority = {0};
CHTTP_Authority authority = {0};
if (s->len - s->cur > 1
&& s->src[s->cur+0] == '/'
&& s->src[s->cur+1] == '/') {
@@ -521,29 +521,29 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
abempty = 1;
}
HTTP_String path;
CHTTP_String path;
int ret = parse_path(s, &path, abempty);
if (ret < 0) return ret;
HTTP_String query = {0};
CHTTP_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) {
query = (CHTTP_String) {
s->src + start,
s->cur - start,
};
}
HTTP_String fragment = {0};
CHTTP_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) {
fragment = (CHTTP_String) {
s->src + start,
s->cur - start,
};
@@ -562,7 +562,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
// 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)
static int parse_authority_form(Scanner *s, CHTTP_Host *host, int *port)
{
int ret;
@@ -591,23 +591,23 @@ static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
return 0;
}
static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query)
static int parse_origin_form(Scanner *s, CHTTP_String *path, CHTTP_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 };
*path = (CHTTP_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 };
*query = (CHTTP_String) { s->src + start, s->cur - start };
} else
*query = (HTTP_String) { NULL, 0 };
*query = (CHTTP_String) { NULL, 0 };
return 0;
}
@@ -622,11 +622,11 @@ static int parse_asterisk_form(Scanner *s)
return 0;
}
static int parse_request_target(Scanner *s, HTTP_URL *url)
static int parse_request_target(Scanner *s, CHTTP_URL *url)
{
int ret;
memset(url, 0, sizeof(HTTP_URL));
memset(url, 0, sizeof(CHTTP_URL));
// asterisk-form
ret = parse_asterisk_form(s);
@@ -644,7 +644,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
return -1;
}
bool consume_str(Scanner *scan, HTTP_String token)
bool consume_str(Scanner *scan, CHTTP_String token)
{
assert(token.len > 0);
@@ -664,10 +664,10 @@ 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)
static int parse_headers(Scanner *s, CHTTP_Header *headers, int max_headers)
{
int num_headers = 0;
while (!consume_str(s, HTTP_STR("\r\n"))) {
while (!consume_str(s, CHTTP_STR("\r\n"))) {
// RFC 9112:
// field-line = field-name ":" OWS field-value OWS
@@ -687,7 +687,7 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
do
s->cur++;
while (s->cur < s->len && is_tchar(s->src[s->cur]));
HTTP_String name = { s->src + start, s->cur - start };
CHTTP_String name = { s->src + start, s->cur - start };
if (s->cur == s->len || s->src[s->cur] != ':')
return -1; // ERROR
@@ -695,13 +695,13 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
start = s->cur;
CONSUME_OPTIONAL_SEQUENCE(s, is_header_body);
HTTP_String body = { s->src + start, s->cur - start };
body = http_trim(body);
CHTTP_String body = { s->src + start, s->cur - start };
body = chttp_trim(body);
if (num_headers < max_headers)
headers[num_headers++] = (HTTP_Header) { name, body };
headers[num_headers++] = (CHTTP_Header) { name, body };
if (!consume_str(s, HTTP_STR("\r\n"))) {
if (!consume_str(s, CHTTP_STR("\r\n"))) {
return -1;
}
}
@@ -722,7 +722,7 @@ static bool is_space(char c)
}
static int
parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
parse_transfer_encoding(CHTTP_String src, TransferEncodingOption *dst, int max)
{
Scanner s = { src.ptr, src.len, 0 };
@@ -733,10 +733,10 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
TransferEncodingOption opt;
if (0) {}
else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED;
else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS;
else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE;
else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP;
else if (consume_str(&s, CHTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED;
else if (consume_str(&s, CHTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS;
else if (consume_str(&s, CHTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE;
else if (consume_str(&s, CHTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP;
else return -1; // Invalid option
if (num == max)
@@ -778,8 +778,8 @@ parse_content_length(const char *src, int len, uint64_t *out)
}
static int parse_body(Scanner *s,
HTTP_Header *headers, int num_headers,
HTTP_String *body, bool body_expected)
CHTTP_Header *headers, int num_headers,
CHTTP_String *body, bool body_expected)
{
// RFC 9112 section 6:
@@ -787,7 +787,7 @@ static int parse_body(Scanner *s,
// Transfer-Encoding header field. Request message framing is independent of method
// semantics.
int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding"));
int header_index = chttp_find_header(headers, num_headers, CHTTP_STR("Transfer-Encoding"));
if (header_index != -1) {
// RFC 9112 section 6.1:
@@ -795,10 +795,10 @@ static int parse_body(Scanner *s,
// or process such a request in accordance with the Transfer-Encoding alone. Regardless,
// the server MUST close the connection after responding to such a request to avoid the
// potential attacks.
if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1)
if (chttp_find_header(headers, num_headers, CHTTP_STR("Content-Length")) != -1)
return -1;
HTTP_String value = headers[header_index].value;
CHTTP_String value = headers[header_index].value;
// RFC 9112 section 6.1:
// If any transfer coding other than chunked is applied to a request's content, the
@@ -808,14 +808,14 @@ static int parse_body(Scanner *s,
// coding or terminate the message by closing the connection.
TransferEncodingOption opts[8];
int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts));
int num = parse_transfer_encoding(value, opts, CHTTP_COUNT(opts));
if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED)
return -1;
HTTP_String chunks_maybe[128];
HTTP_String *chunks = chunks_maybe;
CHTTP_String chunks_maybe[128];
CHTTP_String *chunks = chunks_maybe;
int num_chunks = 0;
int max_chunks = HTTP_COUNT(chunks_maybe);
int max_chunks = CHTTP_COUNT(chunks_maybe);
#define FREE_CHUNK_LIST \
if (chunks != chunks_maybe) \
@@ -905,7 +905,7 @@ static int parse_body(Scanner *s,
max_chunks *= 2;
HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String));
CHTTP_String *new_chunks = malloc(max_chunks * sizeof(CHTTP_String));
if (new_chunks == NULL) {
if (chunks != chunks_maybe)
free(chunks);
@@ -920,7 +920,7 @@ static int parse_body(Scanner *s,
chunks = new_chunks;
}
chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len };
chunks[num_chunks++] = (CHTTP_String) { chunk_ptr, chunk_len };
}
char *content_ptr = content_start;
@@ -929,7 +929,7 @@ static int parse_body(Scanner *s,
content_ptr += chunks[i].len;
}
*body = (HTTP_String) {
*body = (CHTTP_String) {
content_start,
content_ptr - content_start
};
@@ -944,11 +944,11 @@ static int parse_body(Scanner *s,
// If a valid Content-Length header field is present without Transfer-Encoding,
// its decimal value defines the expected message body length in octets.
header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length"));
header_index = chttp_find_header(headers, num_headers, CHTTP_STR("Content-Length"));
if (header_index != -1) {
// Have Content-Length
HTTP_String value = headers[header_index].value;
CHTTP_String value = headers[header_index].value;
uint64_t tmp;
if (parse_content_length(value.ptr, value.len, &tmp) < 0)
@@ -960,7 +960,7 @@ static int parse_body(Scanner *s,
if (len > s->len - s->cur)
return 0; // Incomplete request
*body = (HTTP_String) { s->src + s->cur, len };
*body = (CHTTP_String) { s->src + s->cur, len };
s->cur += len;
return 1;
@@ -969,7 +969,7 @@ static int parse_body(Scanner *s,
// No Content-Length or Transfer-Encoding
if (body_expected) return -1;
*body = (HTTP_String) { NULL, 0 };
*body = (CHTTP_String) { NULL, 0 };
return 1;
}
@@ -987,7 +987,7 @@ static int contains_head(char *src, int len)
return 0;
}
static int parse_request(Scanner *s, HTTP_Request *req)
static int parse_request(Scanner *s, CHTTP_Request *req)
{
if (!contains_head(s->src + s->cur, s->len - s->cur))
return 0;
@@ -995,15 +995,15 @@ static int parse_request(Scanner *s, HTTP_Request *req)
req->secure = false;
if (0) {}
else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET;
else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST;
else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT;
else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD;
else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE;
else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT;
else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS;
else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE;
else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH;
else if (consume_str(s, CHTTP_STR("GET "))) req->method = CHTTP_METHOD_GET;
else if (consume_str(s, CHTTP_STR("POST "))) req->method = CHTTP_METHOD_POST;
else if (consume_str(s, CHTTP_STR("PUT "))) req->method = CHTTP_METHOD_PUT;
else if (consume_str(s, CHTTP_STR("HEAD "))) req->method = CHTTP_METHOD_HEAD;
else if (consume_str(s, CHTTP_STR("DELETE "))) req->method = CHTTP_METHOD_DELETE;
else if (consume_str(s, CHTTP_STR("CONNECT "))) req->method = CHTTP_METHOD_CONNECT;
else if (consume_str(s, CHTTP_STR("OPTIONS "))) req->method = CHTTP_METHOD_OPTIONS;
else if (consume_str(s, CHTTP_STR("TRACE "))) req->method = CHTTP_METHOD_TRACE;
else if (consume_str(s, CHTTP_STR("PATCH "))) req->method = CHTTP_METHOD_PATCH;
else return -1;
{
@@ -1021,47 +1021,47 @@ static int parse_request(Scanner *s, HTTP_Request *req)
s->cur = s2.cur;
}
if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) {
if (consume_str(s, CHTTP_STR(" HTTP/1.1\r\n"))) {
req->minor = 1;
} else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) {
} else if (consume_str(s, CHTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, CHTTP_STR(" HTTP/1\r\n"))) {
req->minor = 0;
} else {
return -1;
}
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
int num_headers = parse_headers(s, req->headers, CHTTP_MAX_HEADERS);
if (num_headers < 0)
return num_headers;
req->num_headers = num_headers;
// Request methods that typically don't have a body
bool body_expected = true;
if (req->method == HTTP_METHOD_GET ||
req->method == HTTP_METHOD_HEAD ||
req->method == HTTP_METHOD_DELETE ||
req->method == HTTP_METHOD_OPTIONS ||
req->method == HTTP_METHOD_TRACE)
if (req->method == CHTTP_METHOD_GET ||
req->method == CHTTP_METHOD_HEAD ||
req->method == CHTTP_METHOD_DELETE ||
req->method == CHTTP_METHOD_OPTIONS ||
req->method == CHTTP_METHOD_TRACE)
body_expected = false;
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
}
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
int chttp_find_header(CHTTP_Header *headers, int num_headers, CHTTP_String name)
{
for (int i = 0; i < num_headers; i++)
if (http_streqcase(name, headers[i].name))
if (chttp_streqcase(name, headers[i].name))
return i;
return -1;
}
static int parse_response(Scanner *s, HTTP_Response *res)
static int parse_response(Scanner *s, CHTTP_Response *res)
{
if (!contains_head(s->src + s->cur, s->len - s->cur))
return 0;
if (consume_str(s, HTTP_STR("HTTP/1.1 "))) {
if (consume_str(s, CHTTP_STR("HTTP/1.1 "))) {
res->minor = 1;
} else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) {
} else if (consume_str(s, CHTTP_STR("HTTP/1.0 ")) || consume_str(s, CHTTP_STR("HTTP/1 "))) {
res->minor = 0;
} else {
return -1;
@@ -1093,7 +1093,7 @@ static int parse_response(Scanner *s, HTTP_Response *res)
return -1;
s->cur += 2;
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
int num_headers = parse_headers(s, res->headers, CHTTP_MAX_HEADERS);
if (num_headers < 0)
return num_headers;
res->num_headers = num_headers;
@@ -1113,7 +1113,7 @@ static int parse_response(Scanner *s, HTTP_Response *res)
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
}
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
int chttp_parse_ipv4(char *src, int len, CHTTP_IPv4 *ipv4)
{
Scanner s = {src, len, 0};
int ret = parse_ipv4(&s, ipv4);
@@ -1121,7 +1121,7 @@ int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
return s.cur;
}
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
int chttp_parse_ipv6(char *src, int len, CHTTP_IPv6 *ipv6)
{
Scanner s = {src, len, 0};
int ret = parse_ipv6(&s, ipv6);
@@ -1129,7 +1129,7 @@ int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
return s.cur;
}
int http_parse_url(char *src, int len, HTTP_URL *url)
int chttp_parse_url(char *src, int len, CHTTP_URL *url)
{
Scanner s = {src, len, 0};
int ret = parse_uri(&s, url, 1);
@@ -1138,7 +1138,7 @@ int http_parse_url(char *src, int len, HTTP_URL *url)
return ret;
}
int http_parse_request(char *src, int len, HTTP_Request *req)
int chttp_parse_request(char *src, int len, CHTTP_Request *req)
{
Scanner s = {src, len, 0};
int ret = parse_request(&s, req);
@@ -1147,7 +1147,7 @@ int http_parse_request(char *src, int len, HTTP_Request *req)
return ret;
}
int http_parse_response(char *src, int len, HTTP_Response *res)
int chttp_parse_response(char *src, int len, CHTTP_Response *res)
{
Scanner s = {src, len, 0};
int ret = parse_response(&s, res);
@@ -1156,14 +1156,14 @@ int http_parse_response(char *src, int len, HTTP_Response *res)
return ret;
}
HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
CHTTP_String chttp_get_cookie(CHTTP_Request *req, CHTTP_String name)
{
// Simple cookie parsing - does not handle quoted values or special characters
// See RFC 6265 for full cookie specification
for (int i = 0; i < req->num_headers; i++) {
if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie")))
if (!chttp_streqcase(req->headers[i].name, CHTTP_STR("Cookie")))
continue;
char *src = req->headers[i].value.ptr;
@@ -1181,7 +1181,7 @@ HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
while (cur < len && src[cur] != '=')
cur++;
HTTP_String cookie_name = { src + off, cur - off };
CHTTP_String cookie_name = { src + off, cur - off };
if (cur == len)
break;
@@ -1191,9 +1191,9 @@ HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
while (cur < len && src[cur] != ';')
cur++;
HTTP_String cookie_value = { src + off, cur - off };
CHTTP_String cookie_value = { src + off, cur - off };
if (http_streq(name, cookie_name))
if (chttp_streq(name, cookie_name))
return cookie_value;
if (cur == len)
@@ -1202,10 +1202,10 @@ HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
}
}
return HTTP_STR("");
return CHTTP_STR("");
}
HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap)
CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int cap)
{
// This is just a best-effort implementation
@@ -1218,29 +1218,29 @@ HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap
while (cur < len) {
HTTP_String name;
CHTTP_String name;
{
int off = cur;
while (cur < len && src[cur] != '=' && src[cur] != '&')
cur++;
name = (HTTP_String) { src + off, cur - off };
name = (CHTTP_String) { src + off, cur - off };
}
HTTP_String body = HTTP_STR("");
CHTTP_String body = CHTTP_STR("");
if (cur < len) {
cur++;
if (src[cur-1] == '=') {
int off = cur;
while (cur < len && src[cur] != '&')
cur++;
body = (HTTP_String) { src + off, cur - off };
body = (CHTTP_String) { src + off, cur - off };
if (cur < len)
cur++;
}
}
if (http_streq(str, name)) {
if (chttp_streq(str, name)) {
bool percent_encoded = false;
for (int i = 0; i < body.len; i++)
@@ -1253,9 +1253,9 @@ HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap
return body;
if (body.len > cap)
return (HTTP_String) { NULL, 0 };
return (CHTTP_String) { NULL, 0 };
HTTP_String decoded = { mem, 0 };
CHTTP_String decoded = { mem, 0 };
for (int i = 0; i < body.len; i++) {
char c = body.ptr[i];
@@ -1266,7 +1266,7 @@ HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap
if (body.len - i < 3
|| !is_hex_digit(body.ptr[i+1])
|| !is_hex_digit(body.ptr[i+2]))
return (HTTP_String) { NULL, 0 };
return (CHTTP_String) { NULL, 0 };
int h = hex_digit_to_int(body.ptr[i+1]);
int l = hex_digit_to_int(body.ptr[i+2]);
@@ -1283,13 +1283,13 @@ HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap
}
}
return HTTP_STR("");
return CHTTP_STR("");
}
int http_get_param_i(HTTP_String body, HTTP_String str)
int chttp_get_param_i(CHTTP_String body, CHTTP_String str)
{
char buf[128];
HTTP_String out = http_get_param(body, str, buf, (int) sizeof(buf));
CHTTP_String out = chttp_get_param(body, str, buf, (int) sizeof(buf));
if (out.len == 0 || !is_digit(out.ptr[0]))
return -1;
@@ -1305,38 +1305,39 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
return res;
}
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
bool chttp_match_host(CHTTP_Request *req, CHTTP_String domain, int port)
{
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
assert(idx != -1); // Requests without the host header are always rejected
int idx = chttp_find_header(req->headers, req->num_headers, CHTTP_STR("Host"));
if (idx < 0)
return false;
char tmp[1<<8];
if (port > -1 && port != 80) {
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
assert(ret > 0);
domain = (HTTP_String) { tmp, ret };
domain = (CHTTP_String) { tmp, ret };
}
HTTP_String host = req->headers[idx].value;
return http_streqcase(host, domain);
CHTTP_String host = req->headers[idx].value;
return chttp_streqcase(host, domain);
}
// <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
static int parse_date(Scanner *s, HTTP_Date *out)
static int parse_date(Scanner *s, CHTTP_Date *out)
{
struct { HTTP_String str; HTTP_WeekDay val; } week_day_table[] = {
{ HTTP_STR("Mon, "), HTTP_WEEKDAY_MON },
{ HTTP_STR("Tue, "), HTTP_WEEKDAY_TUE },
{ HTTP_STR("Wed, "), HTTP_WEEKDAY_WED },
{ HTTP_STR("Thu, "), HTTP_WEEKDAY_THU },
{ HTTP_STR("Fri, "), HTTP_WEEKDAY_FRI },
{ HTTP_STR("Sat, "), HTTP_WEEKDAY_SAT },
{ HTTP_STR("Sun, "), HTTP_WEEKDAY_SUN },
struct { CHTTP_String str; CHTTP_WeekDay val; } week_day_table[] = {
{ CHTTP_STR("Mon, "), CHTTP_WEEKDAY_MON },
{ CHTTP_STR("Tue, "), CHTTP_WEEKDAY_TUE },
{ CHTTP_STR("Wed, "), CHTTP_WEEKDAY_WED },
{ CHTTP_STR("Thu, "), CHTTP_WEEKDAY_THU },
{ CHTTP_STR("Fri, "), CHTTP_WEEKDAY_FRI },
{ CHTTP_STR("Sat, "), CHTTP_WEEKDAY_SAT },
{ CHTTP_STR("Sun, "), CHTTP_WEEKDAY_SUN },
};
bool found = false;
for (int i = 0; i < HTTP_COUNT(week_day_table); i++)
for (int i = 0; i < CHTTP_COUNT(week_day_table); i++)
if (consume_str(s, week_day_table[i].str)) {
out->week_day = week_day_table[i].val;
found = true;
@@ -1354,23 +1355,23 @@ static int parse_date(Scanner *s, HTTP_Date *out)
+ (s->src[s->cur+1] - '0') * 1;
s->cur += 2;
struct { HTTP_String str; HTTP_Month val; } month_table[] = {
{ HTTP_STR(" Jan "), HTTP_MONTH_JAN },
{ HTTP_STR(" Feb "), HTTP_MONTH_FEB },
{ HTTP_STR(" Mar "), HTTP_MONTH_MAR },
{ HTTP_STR(" Apr "), HTTP_MONTH_APR },
{ HTTP_STR(" May "), HTTP_MONTH_MAY },
{ HTTP_STR(" Jun "), HTTP_MONTH_JUN },
{ HTTP_STR(" Jul "), HTTP_MONTH_JUL },
{ HTTP_STR(" Aug "), HTTP_MONTH_AUG },
{ HTTP_STR(" Sep "), HTTP_MONTH_SEP },
{ HTTP_STR(" Oct "), HTTP_MONTH_OCT },
{ HTTP_STR(" Nov "), HTTP_MONTH_NOV },
{ HTTP_STR(" Dec "), HTTP_MONTH_DEC },
struct { CHTTP_String str; CHTTP_Month val; } month_table[] = {
{ CHTTP_STR(" Jan "), CHTTP_MONTH_JAN },
{ CHTTP_STR(" Feb "), CHTTP_MONTH_FEB },
{ CHTTP_STR(" Mar "), CHTTP_MONTH_MAR },
{ CHTTP_STR(" Apr "), CHTTP_MONTH_APR },
{ CHTTP_STR(" May "), CHTTP_MONTH_MAY },
{ CHTTP_STR(" Jun "), CHTTP_MONTH_JUN },
{ CHTTP_STR(" Jul "), CHTTP_MONTH_JUL },
{ CHTTP_STR(" Aug "), CHTTP_MONTH_AUG },
{ CHTTP_STR(" Sep "), CHTTP_MONTH_SEP },
{ CHTTP_STR(" Oct "), CHTTP_MONTH_OCT },
{ CHTTP_STR(" Nov "), CHTTP_MONTH_NOV },
{ CHTTP_STR(" Dec "), CHTTP_MONTH_DEC },
};
found = false;
for (int i = 0; i < HTTP_COUNT(month_table); i++)
for (int i = 0; i < CHTTP_COUNT(month_table); i++)
if (consume_str(s, month_table[i].str)) {
out->month = month_table[i].val;
found = true;
@@ -1436,7 +1437,7 @@ static bool is_cookie_octet(char c)
(c >= 0x5D && c <= 0x7E);
}
int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
{
Scanner s = { str.ptr, str.len, 0 };
@@ -1447,7 +1448,7 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
do
s.cur++;
while (s.cur < s.len && is_tchar(s.src[s.cur]));
out->name = (HTTP_String) { s.src + off, s.cur - off };
out->name = (CHTTP_String) { s.src + off, s.cur - off };
// cookie-pair = cookie-name "=" cookie-value
if (s.cur == s.len || s.src[s.cur] != '=')
@@ -1462,36 +1463,35 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
s.cur++;
if (s.cur == s.len || s.src[s.cur] != '"')
return -1; // Missing closing double quote
out->value = (HTTP_String) { s.src + off, s.cur - off };
out->value = (CHTTP_String) { s.src + off, s.cur - off };
s.cur++; // Consume closing double quote
} else {
int off = s.cur;
while (s.cur < s.len && is_cookie_octet(s.src[s.cur]))
s.cur++;
out->value = (HTTP_String) { s.src + off, s.cur - off };
out->value = (CHTTP_String) { s.src + off, s.cur - off };
}
// *( ";" SP cookie-av )
//
// cookie-av = expires-av / max-age-av / domain-av /
// path-av / secure-av / httponly-av /
// samesite-av / extension-av
// extension-av
out->secure = false;
out->http_only = false;
out->chttp_only = false;
out->have_date = false;
out->have_max_age = false;
out->have_domain = false;
out->have_path = false;
out->have_samesite = false;
while (consume_str(&s, HTTP_STR("; "))) {
if (consume_str(&s, HTTP_STR("Expires="))) {
while (consume_str(&s, CHTTP_STR("; "))) {
if (consume_str(&s, CHTTP_STR("Expires="))) {
// expires-av = "Expires=" sane-cookie-date
if (parse_date(&s, &out->date) < 0)
return -1;
out->have_date = true;
} else if (consume_str(&s, HTTP_STR("Max-Age="))) {
} else if (consume_str(&s, CHTTP_STR("Max-Age="))) {
// max-age-av = "Max-Age=" non-zero-digit *DIGIT
@@ -1508,7 +1508,7 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
out->have_max_age = true;
out->max_age = value;
} else if (consume_str(&s, HTTP_STR("Domain="))) {
} else if (consume_str(&s, CHTTP_STR("Domain="))) {
// domain-av = "Domain=" domain-value
// domain-value = <subdomain>
@@ -1527,13 +1527,6 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
// concatenated by dots. Each label may contain letters, digits,
// hyphens, but the first character must be a letter and the last
// one can't be a hyphen.
//
// RFC 6265 Section 5.2.3: If the first character of the attribute-value
// string is ".", then ignore the leading "."
// Skip leading dot if present
if (s.cur < s.len && s.src[s.cur] == '.')
s.cur++;
int off = s.cur;
if (s.cur == s.len || !is_alpha(s.src[s.cur]))
@@ -1565,9 +1558,9 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
}
out->have_domain = true;
out->domain = (HTTP_String) { s.src + off, s.cur - off };
out->domain = (CHTTP_String) { s.src + off, s.cur - off };
} else if (consume_str(&s, HTTP_STR("Path="))) {
} else if (consume_str(&s, CHTTP_STR("Path="))) {
// path-av = "Path=" path-value
// path-value = <any CHAR except CTLs or ";">
@@ -1577,42 +1570,20 @@ int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
s.cur++;
out->have_path = true;
out->path = (HTTP_String) { s.src + off, s.cur - off };
out->path = (CHTTP_String) { s.src + off, s.cur - off };
} else if (consume_str(&s, HTTP_STR("Secure"))) {
} else if (consume_str(&s, CHTTP_STR("Secure"))) {
// secure-av = "Secure"
out->secure = true;
} else if (consume_str(&s, HTTP_STR("HttpOnly"))) {
} else if (consume_str(&s, CHTTP_STR("HttpOnly"))) {
// httponly-av = "HttpOnly"
out->http_only = true;
} else if (consume_str(&s, HTTP_STR("SameSite="))) {
// samesite-av = "SameSite=" samesite-value
// samesite-value = "Strict" / "Lax" / "None"
if (consume_str(&s, HTTP_STR("Strict"))) {
out->have_samesite = true;
out->samesite = HTTP_SAMESITE_STRICT;
} else if (consume_str(&s, HTTP_STR("Lax"))) {
out->have_samesite = true;
out->samesite = HTTP_SAMESITE_LAX;
} else if (consume_str(&s, HTTP_STR("None"))) {
out->have_samesite = true;
out->samesite = HTTP_SAMESITE_NONE;
} else {
// Invalid SameSite value, skip to next attribute
while (s.cur < s.len && s.src[s.cur] != ';')
s.cur++;
}
out->chttp_only = true;
} else {
// extension-av: Unknown attribute, skip to next semicolon
// This allows forward compatibility with new cookie attributes
while (s.cur < s.len && s.src[s.cur] != ';')
s.cur++;
return -1; // Invalid attribute
}
}
+87 -96
View File
@@ -1,164 +1,155 @@
#define HTTP_MAX_HEADERS 32
#define CHTTP_MAX_HEADERS 32
typedef struct {
unsigned int data;
} HTTP_IPv4;
} CHTTP_IPv4;
typedef struct {
unsigned short data[8];
} HTTP_IPv6;
} CHTTP_IPv6;
typedef enum {
HTTP_HOST_MODE_VOID = 0,
HTTP_HOST_MODE_NAME,
HTTP_HOST_MODE_IPV4,
HTTP_HOST_MODE_IPV6,
} HTTP_HostMode;
CHTTP_HOST_MODE_VOID = 0,
CHTTP_HOST_MODE_NAME,
CHTTP_HOST_MODE_IPV4,
CHTTP_HOST_MODE_IPV6,
} CHTTP_HostMode;
typedef struct {
HTTP_HostMode mode;
HTTP_String text;
CHTTP_HostMode mode;
CHTTP_String text;
union {
HTTP_String name;
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
CHTTP_String name;
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
};
} HTTP_Host;
} CHTTP_Host;
typedef struct {
HTTP_String userinfo;
HTTP_Host host;
CHTTP_String userinfo;
CHTTP_Host host;
int port;
} HTTP_Authority;
} CHTTP_Authority;
// ZII
typedef struct {
HTTP_String scheme;
HTTP_Authority authority;
HTTP_String path;
HTTP_String query;
HTTP_String fragment;
} HTTP_URL;
CHTTP_String scheme;
CHTTP_Authority authority;
CHTTP_String path;
CHTTP_String query;
CHTTP_String fragment;
} CHTTP_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;
CHTTP_METHOD_GET,
CHTTP_METHOD_HEAD,
CHTTP_METHOD_POST,
CHTTP_METHOD_PUT,
CHTTP_METHOD_DELETE,
CHTTP_METHOD_CONNECT,
CHTTP_METHOD_OPTIONS,
CHTTP_METHOD_TRACE,
CHTTP_METHOD_PATCH,
} CHTTP_Method;
typedef struct {
HTTP_String name;
HTTP_String value;
} HTTP_Header;
CHTTP_String name;
CHTTP_String value;
} CHTTP_Header;
typedef struct {
bool secure;
HTTP_Method method;
HTTP_URL url;
CHTTP_Method method;
CHTTP_URL url;
int minor;
int num_headers;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Request;
CHTTP_Header headers[CHTTP_MAX_HEADERS];
CHTTP_String body;
} CHTTP_Request;
typedef struct {
void* context;
int minor;
int status;
HTTP_String reason;
CHTTP_String reason;
int num_headers;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Response;
CHTTP_Header headers[CHTTP_MAX_HEADERS];
CHTTP_String body;
} CHTTP_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 chttp_parse_ipv4 (char *src, int len, CHTTP_IPv4 *ipv4);
int chttp_parse_ipv6 (char *src, int len, CHTTP_IPv6 *ipv6);
int chttp_parse_url (char *src, int len, CHTTP_URL *url);
int chttp_parse_request (char *src, int len, CHTTP_Request *req);
int chttp_parse_response (char *src, int len, CHTTP_Response *res);
int http_find_header (HTTP_Header *headers, int num_headers, HTTP_String name);
int chttp_find_header (CHTTP_Header *headers, int num_headers, CHTTP_String name);
HTTP_String http_get_cookie (HTTP_Request *req, HTTP_String name);
HTTP_String http_get_param (HTTP_String body, HTTP_String str, char *mem, int cap);
int http_get_param_i (HTTP_String body, HTTP_String str);
CHTTP_String chttp_get_cookie (CHTTP_Request *req, CHTTP_String name);
CHTTP_String chttp_get_param (CHTTP_String body, CHTTP_String str, char *mem, int cap);
int chttp_get_param_i (CHTTP_String body, CHTTP_String str);
// Checks whether the request was meant for the host with the given
// domain an port. If port is -1, the default value of 80 is assumed.
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
bool chttp_match_host(CHTTP_Request *req, CHTTP_String domain, int port);
// Date and cookie types for Set-Cookie header parsing
typedef enum {
HTTP_WEEKDAY_MON,
HTTP_WEEKDAY_TUE,
HTTP_WEEKDAY_WED,
HTTP_WEEKDAY_THU,
HTTP_WEEKDAY_FRI,
HTTP_WEEKDAY_SAT,
HTTP_WEEKDAY_SUN,
} HTTP_WeekDay;
CHTTP_WEEKDAY_MON,
CHTTP_WEEKDAY_TUE,
CHTTP_WEEKDAY_WED,
CHTTP_WEEKDAY_THU,
CHTTP_WEEKDAY_FRI,
CHTTP_WEEKDAY_SAT,
CHTTP_WEEKDAY_SUN,
} CHTTP_WeekDay;
typedef enum {
HTTP_MONTH_JAN,
HTTP_MONTH_FEB,
HTTP_MONTH_MAR,
HTTP_MONTH_APR,
HTTP_MONTH_MAY,
HTTP_MONTH_JUN,
HTTP_MONTH_JUL,
HTTP_MONTH_AUG,
HTTP_MONTH_SEP,
HTTP_MONTH_OCT,
HTTP_MONTH_NOV,
HTTP_MONTH_DEC,
} HTTP_Month;
CHTTP_MONTH_JAN,
CHTTP_MONTH_FEB,
CHTTP_MONTH_MAR,
CHTTP_MONTH_APR,
CHTTP_MONTH_MAY,
CHTTP_MONTH_JUN,
CHTTP_MONTH_JUL,
CHTTP_MONTH_AUG,
CHTTP_MONTH_SEP,
CHTTP_MONTH_OCT,
CHTTP_MONTH_NOV,
CHTTP_MONTH_DEC,
} CHTTP_Month;
typedef struct {
HTTP_WeekDay week_day;
CHTTP_WeekDay week_day;
int day;
HTTP_Month month;
CHTTP_Month month;
int year;
int hour;
int minute;
int second;
} HTTP_Date;
typedef enum {
HTTP_SAMESITE_NONE,
HTTP_SAMESITE_LAX,
HTTP_SAMESITE_STRICT,
} HTTP_SameSite;
} CHTTP_Date;
typedef struct {
HTTP_String name;
HTTP_String value;
CHTTP_String name;
CHTTP_String value;
bool secure;
bool http_only;
bool chttp_only;
bool have_date;
HTTP_Date date;
CHTTP_Date date;
bool have_max_age;
uint32_t max_age;
bool have_domain;
HTTP_String domain;
CHTTP_String domain;
bool have_path;
HTTP_String path;
bool have_samesite;
HTTP_SameSite samesite;
} HTTP_SetCookie;
CHTTP_String path;
} CHTTP_SetCookie;
// Parses a Set-Cookie header value
// Returns 0 on success, -1 on error
int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out);
int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out);
+6 -4
View File
@@ -45,6 +45,8 @@ void client_secure_context_free(ClientSecureContext *ctx)
{
#ifdef HTTPS_ENABLED
SSL_CTX_free(ctx->p);
#else
(void) ctx;
#endif
}
@@ -74,7 +76,7 @@ static int servername_callback(SSL *ssl, int *ad, void *arg)
#endif
int server_secure_context_init(ServerSecureContext *ctx,
HTTP_String cert_file, HTTP_String key_file)
CHTTP_String cert_file, CHTTP_String key_file)
{
#ifdef HTTPS_ENABLED
SSL_CTX *p = SSL_CTX_new(TLS_server_method());
@@ -101,7 +103,7 @@ int server_secure_context_init(ServerSecureContext *ctx,
key_buffer[key_file.len] = '\0';
// Load certificate and private key
if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) {
if (SSL_CTX_use_certificate_chain_file(p, cert_buffer) != 1) {
SSL_CTX_free(p);
return -1;
}
@@ -143,7 +145,7 @@ void server_secure_context_free(ServerSecureContext *ctx)
}
int server_secure_context_add_certificate(ServerSecureContext *ctx,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
{
#ifdef HTTPS_ENABLED
if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT)
@@ -171,7 +173,7 @@ int server_secure_context_add_certificate(ServerSecureContext *ctx,
memcpy(key_buffer, key_file.ptr, key_file.len);
key_buffer[key_file.len] = '\0';
if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) {
if (SSL_CTX_use_certificate_chain_file(p, cert_buffer) != 1) {
SSL_CTX_free(p);
return -1;
}
+2 -2
View File
@@ -34,7 +34,7 @@ typedef struct {
} ServerSecureContext;
int server_secure_context_init(ServerSecureContext *ctx,
HTTP_String cert_file, HTTP_String key_file);
CHTTP_String cert_file, CHTTP_String key_file);
void server_secure_context_free(ServerSecureContext *ctx);
int server_secure_context_add_certificate(ServerSecureContext *ctx,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
+145 -123
View File
@@ -1,33 +1,34 @@
static void http_server_conn_init(HTTP_ServerConn *conn,
static void chttp_server_conn_init(CHTTP_ServerConn *conn,
SocketHandle handle, uint32_t input_buffer_limit,
uint32_t output_buffer_limit)
{
conn->state = HTTP_SERVER_CONN_BUFFERING;
conn->state = CHTTP_SERVER_CONN_BUFFERING;
conn->handle = handle;
conn->closing = false;
byte_queue_init(&conn->input, input_buffer_limit);
byte_queue_init(&conn->output, output_buffer_limit);
}
static void http_server_conn_free(HTTP_ServerConn *conn)
static void chttp_server_conn_free(CHTTP_ServerConn *conn)
{
byte_queue_free(&conn->output);
byte_queue_free(&conn->input);
conn->state = CHTTP_SERVER_CONN_FREE;
}
int http_server_init(HTTP_Server *server)
int chttp_server_init(CHTTP_Server *server)
{
server->input_buffer_limit = 1<<20;
server->output_buffer_limit = 1<<20;
server->output_buffer_limit = 1<<24;
server->trace_bytes = false;
server->reuse_addr = false;
server->backlog = 32;
server->num_conns = 0;
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) {
server->conns[i].state = HTTP_SERVER_CONN_FREE;
for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++) {
server->conns[i].state = CHTTP_SERVER_CONN_FREE;
server->conns[i].gen = 0;
}
@@ -35,77 +36,77 @@ int http_server_init(HTTP_Server *server)
server->ready_head = 0;
return socket_manager_init(&server->sockets,
server->socket_pool, HTTP_SERVER_CAPACITY);
server->socket_pool, CHTTP_SERVER_CAPACITY);
}
void http_server_free(HTTP_Server *server)
void chttp_server_free(CHTTP_Server *server)
{
socket_manager_free(&server->sockets);
for (int i = 0, j = 0; j < server->num_conns; i++) {
HTTP_ServerConn *conn = &server->conns[i];
if (conn->state == HTTP_SERVER_CONN_FREE)
CHTTP_ServerConn *conn = &server->conns[i];
if (conn->state == CHTTP_SERVER_CONN_FREE)
continue;
j++;
http_server_conn_free(conn);
chttp_server_conn_free(conn);
}
}
void http_server_set_input_limit(HTTP_Server *server, uint32_t limit)
void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit)
{
server->input_buffer_limit = limit;
}
void http_server_set_output_limit(HTTP_Server *server, uint32_t limit)
void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit)
{
server->output_buffer_limit = limit;
}
void http_server_set_trace_bytes(HTTP_Server *server, bool value)
void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value)
{
server->trace_bytes = value;
}
void http_server_set_reuse_addr(HTTP_Server *server, bool reuse)
void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse)
{
server->reuse_addr = reuse;
}
void http_server_set_backlog(HTTP_Server *server, int backlog)
void chttp_server_set_backlog(CHTTP_Server *server, int backlog)
{
server->backlog = backlog;
}
int http_server_listen_tcp(HTTP_Server *server,
HTTP_String addr, Port port)
int chttp_server_listen_tcp(CHTTP_Server *server,
CHTTP_String addr, Port port)
{
return socket_manager_listen_tcp(&server->sockets,
addr, port, server->backlog, server->reuse_addr);
}
int http_server_listen_tls(HTTP_Server *server,
HTTP_String addr, Port port, HTTP_String cert_file_name,
HTTP_String key_file_name)
int chttp_server_listen_tls(CHTTP_Server *server,
CHTTP_String addr, Port port, CHTTP_String cert_file_name,
CHTTP_String key_file_name)
{
return socket_manager_listen_tls(&server->sockets,
addr, port, server->backlog, server->reuse_addr,
cert_file_name, key_file_name);
}
int http_server_add_certificate(HTTP_Server *server,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
int chttp_server_add_certificate(CHTTP_Server *server,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
{
return socket_manager_add_certificate(&server->sockets,
domain, cert_file, key_file);
}
int http_server_wakeup(HTTP_Server *server)
int chttp_server_wakeup(CHTTP_Server *server)
{
return socket_manager_wakeup(&server->sockets);
}
void http_server_register_events(HTTP_Server *server,
void chttp_server_register_events(CHTTP_Server *server,
EventRegister *reg)
{
socket_manager_register_events(&server->sockets, reg);
@@ -117,12 +118,12 @@ void http_server_register_events(HTTP_Server *server,
// to the ready queue. If the request is invalid,
// close the socket.
static void
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
check_request_buffer(CHTTP_Server *server, CHTTP_ServerConn *conn)
{
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
assert(conn->state == CHTTP_SERVER_CONN_BUFFERING);
ByteView src = byte_queue_read_buf(&conn->input);
int ret = http_parse_request(src.ptr, src.len, &conn->request);
int ret = chttp_parse_request(src.ptr, src.len, &conn->request);
if (ret < 0) {
// Invalid request
@@ -144,22 +145,25 @@ check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
// Ready
assert(ret > 0);
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
// Stop receiving I/O events while we are building the response
socket_silent(&server->sockets, conn->handle, true);
conn->state = CHTTP_SERVER_CONN_WAIT_STATUS;
conn->request_len = ret;
conn->response_offset = byte_queue_offset(&conn->output);
// Push to the ready queue
assert(server->num_ready < HTTP_SERVER_CAPACITY);
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
assert(server->num_ready < CHTTP_SERVER_CAPACITY);
int tail = (server->ready_head + server->num_ready) % CHTTP_SERVER_CAPACITY;
server->ready[tail] = conn - server->conns;
server->num_ready++;
}
}
static void
http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
{
if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
if (conn->state == CHTTP_SERVER_CONN_FLUSHING) {
ByteView src = byte_queue_read_buf(&conn->output);
@@ -168,7 +172,7 @@ http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
num = socket_send(&server->sockets, conn->handle, src.ptr, src.len);
if (server->trace_bytes)
print_bytes(HTTP_STR("<< "), (HTTP_String) { src.ptr, num });
print_bytes(CHTTP_STR("<< "), (CHTTP_String) { src.ptr, num });
byte_queue_read_ack(&conn->output, num);
@@ -185,13 +189,13 @@ http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
socket_close(&server->sockets, conn->handle);
return;
}
conn->state = HTTP_SERVER_CONN_BUFFERING;
conn->state = CHTTP_SERVER_CONN_BUFFERING;
}
}
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
if (conn->state == CHTTP_SERVER_CONN_BUFFERING) {
int min_recv = 1<<10;
int min_recv = 1<<9;
byte_queue_write_setmincap(&conn->input, min_recv);
// Note that it's extra important that we don't
@@ -205,7 +209,7 @@ http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
if (server->trace_bytes)
print_bytes(HTTP_STR(">> "), (HTTP_String) { dst.ptr, num });
print_bytes(CHTTP_STR(">> "), (CHTTP_String) { dst.ptr, num });
byte_queue_write_ack(&conn->input, num);
@@ -217,38 +221,50 @@ http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
}
}
void http_server_process_events(HTTP_Server *server,
void chttp_server_process_events(CHTTP_Server *server,
EventRegister reg)
{
SocketEvent events[HTTP_SERVER_CAPACITY];
SocketEvent events[CHTTP_SERVER_CAPACITY];
int num_events = socket_manager_translate_events(&server->sockets, events, reg);
for (int i = 0; i < num_events; i++) {
HTTP_ServerConn *conn = events[i].user;
CHTTP_ServerConn *conn = events[i].user;
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
http_server_conn_free(conn);
if (conn) {
chttp_server_conn_free(conn); // TODO: what if this was in the ready queue?
server->num_conns--;
}
} else if (events[i].type == SOCKET_EVENT_CREATION_TIMEOUT) {
// TODO: This is too abrupt
socket_close(&server->sockets, events[i].handle);
} else if (events[i].type == SOCKET_EVENT_RECV_TIMEOUT) {
// TODO: This is too abrupt
socket_close(&server->sockets, events[i].handle);
} else if (events[i].type == SOCKET_EVENT_READY) {
if (events[i].user == NULL) {
if (server->num_conns == HTTP_SERVER_CAPACITY) {
if (server->num_conns == CHTTP_SERVER_CAPACITY) {
socket_close(&server->sockets, events[i].handle);
continue;
}
int j = 0;
while (server->conns[j].state != HTTP_SERVER_CONN_FREE) {
while (server->conns[j].state != CHTTP_SERVER_CONN_FREE) {
j++;
assert(i < HTTP_SERVER_CAPACITY);
assert(j < CHTTP_SERVER_CAPACITY);
}
conn = &server->conns[j];
http_server_conn_init(conn,
chttp_server_conn_init(conn,
events[i].handle,
server->input_buffer_limit,
server->output_buffer_limit);
@@ -258,44 +274,43 @@ void http_server_process_events(HTTP_Server *server,
}
while (socket_ready(&server->sockets, events[i].handle)
&& conn->state != HTTP_SERVER_CONN_WAIT_STATUS)
http_server_conn_process_events(server, conn);
&& conn->state != CHTTP_SERVER_CONN_WAIT_STATUS)
chttp_server_conn_process_events(server, conn);
}
}
}
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder)
bool chttp_server_next_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder)
{
if (server->num_ready == 0)
return false;
HTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]];
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
CHTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]];
server->ready_head = (server->ready_head + 1) % CHTTP_SERVER_CAPACITY;
server->num_ready--;
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
assert(conn->state == CHTTP_SERVER_CONN_WAIT_STATUS);
*request = &conn->request;
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
*builder = (CHTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
return true;
}
void http_server_wait_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder)
void chttp_server_wait_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder)
{
for (;;) {
void *ptrs[HTTP_SERVER_POLL_CAPACITY];
struct pollfd polled[HTTP_SERVER_POLL_CAPACITY];
void *ptrs[CHTTP_SERVER_POLL_CAPACITY];
struct pollfd polled[CHTTP_SERVER_POLL_CAPACITY];
EventRegister reg = { ptrs, polled, 0 };
http_server_register_events(server, &reg);
EventRegister reg = { ptrs, polled, 0, -1 };
chttp_server_register_events(server, &reg);
if (reg.num_polled > 0)
POLL(reg.polled, reg.num_polled, -1);
POLL(reg.polled, reg.num_polled, reg.timeout);
http_server_process_events(server, reg);
chttp_server_process_events(server, reg);
if (http_server_next_request(server, request, builder))
if (chttp_server_next_request(server, request, builder))
break;
}
}
@@ -306,17 +321,17 @@ void http_server_wait_request(HTTP_Server *server,
// can be returned, as any builder is invalidated by
// incrementing the connection's generation counter
// when a response is completed.
static HTTP_ServerConn*
builder_to_conn(HTTP_ResponseBuilder builder)
static CHTTP_ServerConn*
builder_to_conn(CHTTP_ResponseBuilder builder)
{
HTTP_Server *server = builder.server;
CHTTP_Server *server = builder.server;
if (server == NULL)
return NULL;
if (builder.index > HTTP_SERVER_CAPACITY)
if (builder.index > CHTTP_SERVER_CAPACITY)
return NULL;
HTTP_ServerConn *conn = &server->conns[builder.index];
CHTTP_ServerConn *conn = &server->conns[builder.index];
if (builder.gen != conn->gen)
return NULL;
@@ -391,41 +406,32 @@ get_status_text(int code)
}
static void
write_status(HTTP_ServerConn *conn, int status)
write_status(CHTTP_ServerConn *conn, int status)
{
byte_queue_write_fmt(&conn->output,
"HTTP/1.1 %d %s\r\n",
status, get_status_text(status));
}
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
void chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
if (conn->state != CHTTP_SERVER_CONN_WAIT_STATUS) {
// Reset all response content and start from scrach.
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
conn->state = CHTTP_SERVER_CONN_WAIT_STATUS;
}
write_status(conn, status);
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
conn->state = CHTTP_SERVER_CONN_WAIT_HEADER;
}
void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str)
static bool is_header_valid(CHTTP_String str)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
return;
// Validate header: must contain a colon and no control characters
// (to prevent HTTP response splitting attacks)
bool has_colon = false;
for (int i = 0; i < str.len; i++) {
char c = str.ptr[i];
@@ -433,43 +439,56 @@ void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str)
has_colon = true;
// Reject control characters (especially \r and \n)
if (c < 0x20 && c != '\t')
return;
return false;
}
if (!has_colon)
return has_colon;
}
void chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CHTTP_SERVER_CONN_WAIT_HEADER)
return;
// Header must contain a colon and no control characters
// to prevent HTTP response splitting attacks
if (!is_header_valid(str)) return; // Silently drop it
byte_queue_write(&conn->output, str.ptr, str.len);
byte_queue_write(&conn->output, "\r\n", 2);
}
static void append_special_headers(HTTP_ServerConn *conn)
static void append_special_headers(CHTTP_ServerConn *conn)
{
HTTP_String s;
CHTTP_String s;
if (conn->closing) {
s = HTTP_STR("Connection: Close\r\n");
s = CHTTP_STR("Connection: Close\r\n");
byte_queue_write(&conn->output, s.ptr, s.len);
} else {
s = HTTP_STR("Connection: Keep-Alive\r\n");
s = CHTTP_STR("Connection: Keep-Alive\r\n");
byte_queue_write(&conn->output, s.ptr, s.len);
}
s = HTTP_STR("Content-Length: ");
s = CHTTP_STR("Content-Length: ");
byte_queue_write(&conn->output, s.ptr, s.len);
conn->content_length_value_offset = byte_queue_offset(&conn->output);
#define TEN_SPACES " "
_Static_assert(sizeof(TEN_SPACES) == 10+1);
_Static_assert(sizeof(TEN_SPACES) == 10+1, "");
s = HTTP_STR(TEN_SPACES "\r\n");
s = CHTTP_STR(TEN_SPACES "\r\n");
byte_queue_write(&conn->output, s.ptr, s.len);
byte_queue_write(&conn->output, "\r\n", 2);
conn->content_length_offset = byte_queue_offset(&conn->output);
}
static void patch_special_headers(HTTP_ServerConn *conn)
static void patch_special_headers(CHTTP_ServerConn *conn)
{
int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset);
@@ -480,52 +499,52 @@ static void patch_special_headers(HTTP_ServerConn *conn)
byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len);
}
void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str)
void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write(&conn->output, str.ptr, str.len);
}
void http_response_builder_body_cap(HTTP_ResponseBuilder builder, int cap)
void chttp_response_builder_body_cap(CHTTP_ResponseBuilder builder, int cap)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write_setmincap(&conn->output, cap);
}
char *http_response_builder_body_buf(HTTP_ResponseBuilder builder, int *cap)
char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return NULL;
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
return NULL;
ByteView tmp = byte_queue_write_buf(&conn->output);
@@ -533,42 +552,45 @@ char *http_response_builder_body_buf(HTTP_ResponseBuilder builder, int *cap)
return tmp.ptr;
}
void http_response_builder_body_ack(HTTP_ResponseBuilder builder, int num)
void chttp_response_builder_body_ack(CHTTP_ResponseBuilder builder, int num)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write_ack(&conn->output, num);
}
void http_response_builder_send(HTTP_ResponseBuilder builder)
void chttp_response_builder_send(CHTTP_ResponseBuilder builder)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
if (conn->state == CHTTP_SERVER_CONN_WAIT_STATUS) {
write_status(conn, 500);
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
conn->state = CHTTP_SERVER_CONN_WAIT_HEADER;
}
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
}
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
assert(conn->state == CHTTP_SERVER_CONN_WAIT_BODY);
patch_special_headers(conn);
// Remove the buffered request
byte_queue_read_ack(&conn->input, conn->request_len);
conn->state = HTTP_SERVER_CONN_FLUSHING;
conn->state = CHTTP_SERVER_CONN_FLUSHING;
conn->gen++;
http_server_conn_process_events(builder.server, conn);
// Enable back I/O events
socket_silent(&builder.server->sockets, conn->handle, false);
chttp_server_conn_process_events(builder.server, conn);
}
+48 -48
View File
@@ -1,49 +1,49 @@
#ifndef HTTP_SERVER_CAPACITY
#ifndef CHTTP_SERVER_CAPACITY
// The maximum ammount of requests that can be handled
// in parallel.
#define HTTP_SERVER_CAPACITY (1<<9)
#define CHTTP_SERVER_CAPACITY (1<<9)
#endif
// Maximum number of descriptors the server will want
// to wait on. It's one per connection plus two for the
// TCP and TLS listener, plus one for the wakeup self-pipe.
#define HTTP_SERVER_POLL_CAPACITY (HTTP_SERVER_CAPACITY+3)
#define CHTTP_SERVER_POLL_CAPACITY (CHTTP_SERVER_CAPACITY+3)
typedef enum {
// This struct is unused
HTTP_SERVER_CONN_FREE,
CHTTP_SERVER_CONN_FREE,
// No request was buffered yet.
HTTP_SERVER_CONN_BUFFERING,
CHTTP_SERVER_CONN_BUFFERING,
// A request was just buffered and is waiting for
// the user to build a response. To be specific,
// it's waiting for the user to set a response status.
HTTP_SERVER_CONN_WAIT_STATUS,
CHTTP_SERVER_CONN_WAIT_STATUS,
// A request is buffered and a status was set. Now
// the user can set a header or append the first
// bytes of the response body.
HTTP_SERVER_CONN_WAIT_HEADER,
CHTTP_SERVER_CONN_WAIT_HEADER,
// A request is buffered and some bytes were appended
// to the response. Now the user can either append more
// bytes or send out the response.
HTTP_SERVER_CONN_WAIT_BODY,
CHTTP_SERVER_CONN_WAIT_BODY,
// A response has been produced and it's being flushed.
HTTP_SERVER_CONN_FLUSHING,
CHTTP_SERVER_CONN_FLUSHING,
} HTTP_ServerConnState;
} CHTTP_ServerConnState;
// This structure represents the HTTP connection to
// a client.
typedef struct {
// If false, this struct is unused
HTTP_ServerConnState state;
CHTTP_ServerConnState state;
// Handle to the socket
SocketHandle handle;
@@ -68,7 +68,7 @@ typedef struct {
// When the state is WAIT_STATUS, WAIT_HEADER,
// or WAIT_BODY, this contains the parsed version
// of the buffered request.
HTTP_Request request;
CHTTP_Request request;
// Length of the buffered request when the request
// field is valid.
@@ -93,7 +93,7 @@ typedef struct {
// it from the offset reached when the response is marked
// as done.
ByteQueueOffset content_length_offset;
} HTTP_ServerConn;
} CHTTP_ServerConn;
typedef struct {
@@ -109,13 +109,13 @@ typedef struct {
// Array of connections. The counter contains the
// number of structs such that state=FREE.
int num_conns;
HTTP_ServerConn conns[HTTP_SERVER_CAPACITY];
CHTTP_ServerConn conns[CHTTP_SERVER_CAPACITY];
// Queue of indices referring to connections that
// are in the WAIT_STATUS state.
int num_ready;
int ready_head;
int ready[HTTP_SERVER_CAPACITY];
int ready[CHTTP_SERVER_CAPACITY];
// Asynchronous TCP and TLS socket abstraction
SocketManager sockets;
@@ -125,74 +125,74 @@ typedef struct {
// manager with a pointer to it. This allows
// allocating the exact number of sockets we
// will need.
Socket socket_pool[HTTP_SERVER_CAPACITY];
Socket socket_pool[CHTTP_SERVER_CAPACITY];
} HTTP_Server;
} CHTTP_Server;
// Initialize the HTTP server object. By default, it won't
// listen for connections. You need to call
//
// http_server_listen_tcp
// http_server_listen_tls
// chttp_server_listen_tcp
// chttp_server_listen_tls
//
// to listen for connection. Note that you can have a
// single server listening for HTTP and HTTPS requests
// by calling both.
int http_server_init(HTTP_Server *server);
int chttp_server_init(CHTTP_Server *server);
// Release resources associated to the server.
void http_server_free(HTTP_Server *server);
void chttp_server_free(CHTTP_Server *server);
// Set input and output buffer size limit for any
// given connection. The default value is 1MB
void http_server_set_input_limit(HTTP_Server *server, uint32_t limit);
void http_server_set_output_limit(HTTP_Server *server, uint32_t limit);
void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit);
void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit);
// TODO: Comment
void http_server_set_trace_bytes(HTTP_Server *server, bool value);
void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value);
// TODO: Comment
void http_server_set_reuse_addr(HTTP_Server *server, bool reuse);
void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse);
// TODO: comment
void http_server_set_backlog(HTTP_Server *server, int backlog);
void chttp_server_set_backlog(CHTTP_Server *server, int backlog);
// Enable listening for plain HTTP requests at the
// specified interface.
int http_server_listen_tcp(HTTP_Server *server,
HTTP_String addr, Port port);
int chttp_server_listen_tcp(CHTTP_Server *server,
CHTTP_String addr, Port port);
// Enable listening for HTTPS requests at the specified
// interfact, using the specified certificate and key
// to verify the connection.
int http_server_listen_tls(HTTP_Server *server, HTTP_String addr, Port port,
HTTP_String cert_file_name, HTTP_String key_file_name);
int chttp_server_listen_tls(CHTTP_Server *server, CHTTP_String addr, Port port,
CHTTP_String cert_file_name, CHTTP_String key_file_name);
// Add the certificate for an additional domain when
// the server is listening for HTTPS requests.
int http_server_add_certificate(HTTP_Server *server,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
int chttp_server_add_certificate(CHTTP_Server *server,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
// When a thread is blocked waiting for server events,
// other threads can call this function to wake it up.
int http_server_wakeup(HTTP_Server *server);
int chttp_server_wakeup(CHTTP_Server *server);
// Resets the event register with the list of descriptors
// the server wants monitored.
void http_server_register_events(HTTP_Server *server,
void chttp_server_register_events(CHTTP_Server *server,
EventRegister *reg);
// The caller has waited for poll() to return and some
// I/O events to be triggered, so now the HTTP server
// can continue its buffering and flushing operations.
void http_server_process_events(HTTP_Server *server,
void chttp_server_process_events(CHTTP_Server *server,
EventRegister reg);
typedef struct {
HTTP_Server *server;
CHTTP_Server *server;
uint16_t index;
uint16_t gen;
} HTTP_ResponseBuilder;
} CHTTP_ResponseBuilder;
// After some I/O events were processes, some requests
// may be availabe. This function returns one of the
@@ -202,32 +202,32 @@ typedef struct {
// respond in batches.
// For each request returned by this function, the user
// must build a response using the response builder API.
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder);
bool chttp_server_next_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder);
// TODO: comment
void http_server_wait_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder);
void chttp_server_wait_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder);
// This function is called to set the status code of
// a request's response. If this function is called
// after the other response builder functions, it will
// reset the response and set a new status.
void http_response_builder_status(HTTP_ResponseBuilder builder, int status);
void chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status);
// Append a header to the response. This can only be
// used after having set the status and before appending
// to the body.
void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str);
void chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str);
// Append some bytes to the response's body
void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str);
void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str);
// TODO: comment
void http_response_builder_body_cap(HTTP_ResponseBuilder builder, int cap);
char *http_response_builder_body_buf(HTTP_ResponseBuilder builder, int *cap);
void http_response_builder_body_ack(HTTP_ResponseBuilder builder, int num);
void chttp_response_builder_body_cap(CHTTP_ResponseBuilder builder, int cap);
char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap);
void chttp_response_builder_body_ack(CHTTP_ResponseBuilder builder, int num);
// Mark the response as complete. This will invalidate
// the response builder handle.
void http_response_builder_send(HTTP_ResponseBuilder builder);
void chttp_response_builder_send(CHTTP_ResponseBuilder builder);
+187 -64
View File
@@ -39,7 +39,7 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b, bool *global_c
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData))
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET && *global_cleanup)
@@ -49,7 +49,7 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b, bool *global_c
if (sock == INVALID_SOCKET) {
if (*global_cleanup)
WSACleanup();
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
// Bind to loopback address with port 0 (dynamic port assignment)
@@ -64,21 +64,21 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b, bool *global_c
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
// Optional: Set socket to non-blocking mode
@@ -88,20 +88,20 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b, bool *global_c
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
*a = sock;
*b = sock;
return HTTP_OK;
return CHTTP_OK;
#else
*global_cleanup = false;
int fds[2];
if (pipe(fds) < 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
*a = fds[0];
*b = fds[1];
return HTTP_OK;
return CHTTP_OK;
#endif
}
@@ -110,23 +110,23 @@ static int set_socket_blocking(NATIVE_SOCKET sock, bool value)
#ifdef _WIN32
u_long mode = !value;
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
return HTTP_ERROR_UNSPECIFIED;
return HTTP_OK;
return CHTTP_ERROR_UNSPECIFIED;
return CHTTP_OK;
#endif
#ifdef __linux__
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
if (value) flags &= ~O_NONBLOCK;
else flags |= O_NONBLOCK;
if (fcntl(sock, F_SETFL, flags) < 0)
return HTTP_ERROR_UNSPECIFIED;
return HTTP_OK;
return CHTTP_ERROR_UNSPECIFIED;
return CHTTP_OK;
#endif
}
static NATIVE_SOCKET create_listen_socket(HTTP_String addr,
static NATIVE_SOCKET create_listen_socket(CHTTP_String addr,
Port port, bool reuse_addr, int backlog)
{
NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -193,6 +193,9 @@ static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b)
int socket_manager_init(SocketManager *sm, Socket *socks,
int num_socks)
{
sm->creation_timeout = 60000;
sm->recv_timeout = 3000;
sm->plain_sock = NATIVE_SOCKET_INVALID;
sm->secure_sock = NATIVE_SOCKET_INVALID;
@@ -212,7 +215,7 @@ int socket_manager_init(SocketManager *sm, Socket *socks,
socks[i].state = SOCKET_STATE_FREE;
socks[i].gen = 1;
}
return HTTP_OK;
return CHTTP_OK;
}
void socket_manager_free(SocketManager *sm)
@@ -237,58 +240,68 @@ void socket_manager_free(SocketManager *sm)
#endif
}
void socket_manager_set_creation_timeout(SocketManager *sm, int timeout)
{
sm->creation_timeout = (timeout < 0) ? INVALID_TIME : (Time) timeout;
}
void socket_manager_set_recv_timeout(SocketManager *sm, int timeout)
{
sm->recv_timeout = (timeout < 0) ? INVALID_TIME : (Time) timeout;
}
int socket_manager_listen_tcp(SocketManager *sm,
HTTP_String addr, Port port, int backlog,
CHTTP_String addr, Port port, int backlog,
bool reuse_addr)
{
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog);
if (sm->plain_sock == NATIVE_SOCKET_INVALID)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_OK;
return CHTTP_OK;
}
int socket_manager_listen_tls(SocketManager *sm,
HTTP_String addr, Port port, int backlog,
bool reuse_addr, HTTP_String cert_file,
HTTP_String key_file)
CHTTP_String addr, Port port, int backlog,
bool reuse_addr, CHTTP_String cert_file,
CHTTP_String key_file)
{
#ifndef HTTPS_ENABLED
return HTTP_ERROR_NOTLS;
return CHTTP_ERROR_NOTLS;
#endif
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog);
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
if (server_secure_context_init(&sm->server_secure_context,
cert_file, key_file) < 0) {
CLOSE_NATIVE_SOCKET(sm->secure_sock);
sm->secure_sock = NATIVE_SOCKET_INVALID;
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
return HTTP_OK;
return CHTTP_OK;
}
int socket_manager_add_certificate(SocketManager *sm,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
{
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
int ret = server_secure_context_add_certificate(
&sm->server_secure_context, domain, cert_file, key_file);
if (ret < 0)
return ret;
return HTTP_OK;
return CHTTP_OK;
}
static bool is_secure(Socket *s)
@@ -297,6 +310,7 @@ static bool is_secure(Socket *s)
return s->server_secure_context != NULL
|| s->client_secure_context != NULL;
#else
(void) s;
return false;
#endif
}
@@ -427,13 +441,13 @@ static void socket_update(Socket *s)
struct sockaddr_in buf;
buf.sin_family = AF_INET;
buf.sin_port = htons(addr.port);
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4));
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(CHTTP_IPv4));
ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf));
} else {
struct sockaddr_in6 buf;
buf.sin6_family = AF_INET6;
buf.sin6_port = htons(addr.port);
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6));
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(CHTTP_IPv6));
ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf));
}
@@ -524,6 +538,9 @@ static void socket_update(Socket *s)
break;
}
SSL_set_verify(s->ssl, s->dont_verify_cert
? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL);
AddressAndPort addr;
if (s->num_addr > 1)
addr = s->addrs[s->next_addr];
@@ -687,15 +704,14 @@ int socket_manager_wakeup(SocketManager *sm)
// NOTE: It's assumed send/write operate atomically
// on The descriptor.
char byte = 1;
int ret = 0;
#ifdef _WIN32
if (send(sm->signal_sock, &byte, 1, 0) < 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
#else
if (write(sm->signal_sock, &byte, 1) < 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
#endif
return HTTP_OK;
return CHTTP_OK;
}
void socket_manager_register_events(
@@ -735,17 +751,34 @@ void socket_manager_register_events(
// is ready to be processed exists, return an empty
// event registration list so that those entries can
// be processed immediately.
// TODO: comment about deadline
Time deadline = INVALID_TIME;
for (int i = 0, j = 0; j < sm->num_used; i++) {
Socket *s = &sm->sockets[i];
if (s->state == SOCKET_STATE_FREE)
continue;
j++;
if (s->silent)
continue;
if (s->creation_timeout != INVALID_TIME) {
Time creation_deadline = s->creation_time + s->creation_timeout;
if (deadline == INVALID_TIME || creation_deadline < deadline)
deadline = creation_deadline;
}
if (s->recv_timeout != INVALID_TIME) {
Time recv_deadline = s->last_recv_time + s->recv_timeout;
if (deadline == INVALID_TIME || recv_deadline < deadline)
deadline = recv_deadline;
}
// If at least one socket can be processed, return an
// empty list.
if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) {
reg->num_polled = 0;
return;
if (s->state == SOCKET_STATE_DIED ||
s->state == SOCKET_STATE_ESTABLISHED_READY) {
deadline = 0;
}
if (s->events) {
@@ -756,6 +789,20 @@ void socket_manager_register_events(
reg->num_polled++;
}
}
if (deadline == INVALID_TIME) {
reg->timeout = -1;
} else {
Time current_time = get_current_time();
if (current_time == INVALID_TIME) {
reg->timeout = 1000;
} else if (deadline < current_time) {
reg->timeout = 0;
} else {
reg->timeout = deadline - current_time;
}
}
}
static SocketHandle
@@ -779,6 +826,8 @@ int socket_manager_translate_events(
SocketManager *sm, SocketEvent *events,
EventRegister reg)
{
Time current_time = get_current_time();
int num_events = 0;
for (int i = 0; i < reg.num_polled; i++) {
@@ -800,10 +849,6 @@ int socket_manager_translate_events(
if (sm->num_used == sm->max_used)
continue;
// Determine whether the event came from
// the encrypted listener or not.
bool secure = (reg.polled[i].fd == sm->secure_sock);
Socket *s = sm->sockets;
while (s->state != SOCKET_STATE_FREE) {
s++;
@@ -823,7 +868,16 @@ int socket_manager_translate_events(
s->sock = sock;
s->events = 0;
s->user = NULL;
s->silent = false;
s->creation_time = current_time;
s->last_recv_time = current_time;
s->creation_timeout = sm->creation_timeout;
s->recv_timeout = sm->recv_timeout;
#ifdef HTTPS_ENABLED
// Determine whether the event came from
// the encrypted listener or not.
bool secure = (reg.polled[i].fd == sm->secure_sock);
s->ssl = NULL;
s->server_secure_context = NULL;
s->client_secure_context = NULL;
@@ -854,7 +908,10 @@ int socket_manager_translate_events(
#endif
} else {
Socket *s = reg.ptrs[i];
assert(!s->silent);
socket_update(s);
}
}
@@ -865,7 +922,34 @@ int socket_manager_translate_events(
continue;
j++;
if (s->state == SOCKET_STATE_DIED) {
if (s->silent)
continue;
if (s->creation_timeout != INVALID_TIME
&& current_time != INVALID_TIME
&& current_time > s->creation_time + s->creation_timeout) {
s->creation_time = INVALID_TIME;
events[num_events++] = (SocketEvent) {
SOCKET_EVENT_CREATION_TIMEOUT,
socket_to_handle(sm, s),
s->user
};
} else if (s->recv_timeout != INVALID_TIME
&& current_time != INVALID_TIME
&& current_time > s->last_recv_time + s->recv_timeout) {
s->recv_timeout = INVALID_TIME;
events[num_events++] = (SocketEvent) {
SOCKET_EVENT_RECV_TIMEOUT,
socket_to_handle(sm, s),
s->user
};
} else if (s->state == SOCKET_STATE_DIED) {
events[num_events++] = (SocketEvent) {
SOCKET_EVENT_DISCONNECT,
@@ -882,9 +966,14 @@ int socket_manager_translate_events(
if (s->num_addr > 1)
free(s->addrs);
}
#ifdef HTTPS_ENABLED
if (s->ssl)
SSL_free(s->ssl);
#endif // HTTPS_ENABLED
sm->num_used--;
} else if (s->state == SOCKET_STATE_ESTABLISHED_READY) {
events[num_events++] = (SocketEvent) {
SOCKET_EVENT_READY,
socket_to_handle(sm, s),
@@ -916,7 +1005,7 @@ static int resolve_connect_targets(ConnectTarget *targets,
RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1);
if (name == NULL) {
free_addr_list(resolved, num_resolved);
return HTTP_ERROR_OOM;
return CHTTP_ERROR_OOM;
}
name->refs = 0;
memcpy(name->data, targets[i].name.ptr, targets[i].name.len);
@@ -926,7 +1015,7 @@ static int resolve_connect_targets(ConnectTarget *targets,
// 512 bytes is more than enough for a DNS hostname (max 253 chars)
char hostname[1<<9];
if (targets[i].name.len >= (int) sizeof(hostname))
return HTTP_ERROR_OOM;
return CHTTP_ERROR_OOM;
memcpy(hostname, targets[i].name.ptr, targets[i].name.len);
hostname[targets[i].name.len] = '\0';
#endif
@@ -938,12 +1027,12 @@ static int resolve_connect_targets(ConnectTarget *targets,
free(name);
#endif
free_addr_list(resolved, num_resolved);
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
}
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
if (rp->ai_family == AF_INET) {
HTTP_IPv4 ipv4 = *(HTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr;
CHTTP_IPv4 ipv4 = *(CHTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr;
if (num_resolved < max_resolved) {
resolved[num_resolved].is_ipv4 = true;
resolved[num_resolved].ipv4 = ipv4;
@@ -955,7 +1044,7 @@ static int resolve_connect_targets(ConnectTarget *targets,
num_resolved++;
}
} else if (rp->ai_family == AF_INET6) {
HTTP_IPv6 ipv6 = *(HTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr;
CHTTP_IPv6 ipv6 = *(CHTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr;
if (num_resolved < max_resolved) {
resolved[num_resolved].is_ipv4 = false;
resolved[num_resolved].ipv6 = ipv6;
@@ -1007,20 +1096,25 @@ static int resolve_connect_targets(ConnectTarget *targets,
#define MAX_CONNECT_TARGETS 16
int socket_connect(SocketManager *sm, int num_targets,
ConnectTarget *targets, bool secure, void *user)
ConnectTarget *targets, bool secure, bool dont_verify_cert,
void *user)
{
Time current_time = get_current_time();
if (current_time == INVALID_TIME)
return CHTTP_ERROR_UNSPECIFIED;
if (sm->num_used == sm->max_used)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
#ifdef HTTPS_ENABLED
if (!sm->at_least_one_secure_connect) {
if (client_secure_context_init(&sm->client_secure_context) < 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
sm->at_least_one_secure_connect = true;
}
#else
if (secure)
return HTTP_ERROR_NOTLS;
return CHTTP_ERROR_NOTLS;
#endif
AddressAndPort resolved[MAX_CONNECT_TARGETS];
@@ -1028,7 +1122,7 @@ int socket_connect(SocketManager *sm, int num_targets,
targets, num_targets, resolved, MAX_CONNECT_TARGETS);
if (num_resolved <= 0)
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_ERROR_UNSPECIFIED;
Socket *s = sm->sockets;
while (s->state != SOCKET_STATE_FREE) {
@@ -1045,7 +1139,7 @@ int socket_connect(SocketManager *sm, int num_targets,
s->next_addr = 0;
s->addrs = malloc(num_resolved * sizeof(AddressAndPort));
if (s->addrs == NULL)
return HTTP_ERROR_OOM;
return CHTTP_ERROR_OOM;
for (int i = 0; i < num_resolved; i++)
s->addrs[i] = resolved[i];
}
@@ -1053,17 +1147,27 @@ int socket_connect(SocketManager *sm, int num_targets,
UPDATE_STATE(s->state, SOCKET_STATE_PENDING);
s->sock = NATIVE_SOCKET_INVALID;
s->user = user;
s->silent = false;
s->creation_time = current_time;
s->last_recv_time = current_time;
s->creation_timeout = sm->creation_timeout;
s->recv_timeout = sm->recv_timeout;
#ifdef HTTPS_ENABLED
s->server_secure_context = NULL;
s->client_secure_context = NULL;
s->ssl = NULL;
if (secure)
s->dont_verify_cert = false;
if (secure) {
s->client_secure_context = &sm->client_secure_context;
s->dont_verify_cert = dont_verify_cert;
}
#else
(void) dont_verify_cert;
#endif
sm->num_used++;
socket_update(s);
return HTTP_OK;
return CHTTP_OK;
}
static bool would_block(void)
@@ -1098,8 +1202,9 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
return 0;
}
int ret;
if (!is_secure(s)) {
int ret = recv(s->sock, dst, max, 0);
ret = recv(s->sock, dst, max, 0);
if (ret == 0) {
UPDATE_STATE(s->state, SOCKET_STATE_DIED);
s->events = 0;
@@ -1113,10 +1218,9 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
}
ret = 0;
}
return ret;
} else {
#ifdef HTTPS_ENABLED
int ret = SSL_read(s->ssl, dst, max);
ret = SSL_read(s->ssl, dst, max);
if (ret <= 0) {
int err = SSL_get_error(s->ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
@@ -1131,9 +1235,18 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
}
ret = 0;
}
return ret;
#else
// Unreachable
ret = 0;
#endif
}
if (ret > 0 && s->recv_timeout != INVALID_TIME) {
Time current_time = get_current_time();
if (current_time != INVALID_TIME)
s->last_recv_time = current_time;
}
return ret;
}
int socket_send(SocketManager *sm, SocketHandle handle,
@@ -1180,13 +1293,15 @@ int socket_send(SocketManager *sm, SocketHandle handle,
ret = 0;
}
return ret;
#else
// Unreachable
return 0;
#endif
}
}
void socket_close(SocketManager *sm, SocketHandle handle)
{
int ret;
Socket *s = handle_to_socket(sm, handle);
if (s == NULL)
return;
@@ -1217,7 +1332,6 @@ void socket_set_user(SocketManager *sm, SocketHandle handle, void *user)
bool socket_ready(SocketManager *sm, SocketHandle handle)
{
bool ready = false;
Socket *s = handle_to_socket(sm, handle);
if (s == NULL)
return false;
@@ -1227,3 +1341,12 @@ bool socket_ready(SocketManager *sm, SocketHandle handle)
return false;
}
void socket_silent(SocketManager *sm, SocketHandle handle, bool value)
{
Socket *s = handle_to_socket(sm, handle);
if (s == NULL)
return;
s->silent = value;
}
+36 -12
View File
@@ -50,7 +50,7 @@
#ifdef _WIN32
#define NATIVE_SOCKET SOCKET
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
#define NATIVE_SOCKET_INVALID INVALID_SOCKET
#define CLOSE_NATIVE_SOCKET closesocket
#else
#define NATIVE_SOCKET int
@@ -65,6 +65,8 @@ typedef uint16_t Port;
typedef enum {
SOCKET_EVENT_READY,
SOCKET_EVENT_CREATION_TIMEOUT,
SOCKET_EVENT_RECV_TIMEOUT,
SOCKET_EVENT_DISCONNECT,
} SocketEventType;
@@ -123,8 +125,8 @@ typedef struct {
// Internal use only
typedef struct {
union {
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
};
bool is_ipv4;
Port port;
@@ -149,6 +151,9 @@ typedef struct {
// Native socket events that need to be monitored
int events;
// If this is set, the raw socket handle shouldn't be monitored
bool silent;
// Generation counter to invalidate any SocketHandle
// referring to this socket when it is freed.
// Note that this counter may wrap but always skips
@@ -159,6 +164,12 @@ typedef struct {
// User-provided context pointer
void *user;
Time creation_time;
Time last_recv_time;
Time recv_timeout;
Time creation_timeout;
// A single connect operation may involve
// trying to establish a connection towards
// one of a set of addresses.
@@ -173,6 +184,7 @@ typedef struct {
ClientSecureContext *client_secure_context;
ServerSecureContext *server_secure_context;
SSL *ssl;
bool dont_verify_cert;
#endif
} Socket;
@@ -182,6 +194,10 @@ typedef struct {
// header.
typedef struct {
// TODO: comment
Time recv_timeout;
Time creation_timeout;
// TCP listener sockets. The first is intended
// for plaintext, while the second is for TLS.
// The socket manager will accept and add new
@@ -230,6 +246,9 @@ int socket_manager_init(SocketManager *sm, Socket *socks,
// Deinitialize a socket manager
void socket_manager_free(SocketManager *sm);
void socket_manager_set_creation_timeout(SocketManager *sm, int timeout);
void socket_manager_set_recv_timeout(SocketManager *sm, int timeout);
// Configure the socket manager to listen on
// the specified interface for TCP connections.
// Incoming connections will be automatically
@@ -237,7 +256,7 @@ void socket_manager_free(SocketManager *sm);
// can only be used once per manager.
// Returns 0 on success, -1 on error.
int socket_manager_listen_tcp(SocketManager *sm,
HTTP_String addr, Port port, int backlog,
CHTTP_String addr, Port port, int backlog,
bool reuse_addr);
// Same as the previous function, but incoming
@@ -248,9 +267,9 @@ int socket_manager_listen_tcp(SocketManager *sm,
// and secure connections.
// Returns 0 on success, -1 on error.
int socket_manager_listen_tls(SocketManager *sm,
HTTP_String addr, Port port, int backlog,
bool reuse_addr, HTTP_String cert_file,
HTTP_String key_file);
CHTTP_String addr, Port port, int backlog,
bool reuse_addr, CHTTP_String cert_file,
CHTTP_String key_file);
// If the socket manager was configures to accept
// TLS connections, this adds additional certificates
@@ -258,7 +277,7 @@ int socket_manager_listen_tls(SocketManager *sm,
// authenticity.
// Returns 0 on success, -1 on error.
int socket_manager_add_certificate(SocketManager *sm,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
// When a thread is blocked on a poll() call for
// descriptors associated to this socket manager,
@@ -271,6 +290,7 @@ typedef struct {
void **ptrs;
struct pollfd *polled;
int num_polled;
int timeout;
} EventRegister;
// Resets the event register with the list of descriptors
@@ -301,9 +321,9 @@ typedef struct {
ConnectTargetType type;
Port port;
union {
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
HTTP_String name;
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
CHTTP_String name;
};
} ConnectTarget;
@@ -312,7 +332,8 @@ typedef struct {
// one succedes. If secure=true, the socket uses TLS.
// Returns 0 on success, -1 on error.
int socket_connect(SocketManager *sm, int num_targets,
ConnectTarget *targets, bool secure, void *user);
ConnectTarget *targets, bool secure, bool dont_verify_cert,
void *user);
int socket_recv(SocketManager *sm, SocketHandle handle,
char *dst, int max);
@@ -333,3 +354,6 @@ void socket_set_user(SocketManager *sm, SocketHandle handle, void *user);
// Returns true iff the socket is ready for reading or
// writing.
bool socket_ready(SocketManager *sm, SocketHandle handle);
// When a socket is marked as silent it will not generate events
void socket_silent(SocketManager *sm, SocketHandle handle, bool value);
+41
View File
@@ -0,0 +1,41 @@
Time get_current_time(void)
{
#ifdef _WIN32
{
int64_t count;
int64_t freq;
int ok;
ok = QueryPerformanceCounter((LARGE_INTEGER*) &count);
if (!ok) return INVALID_TIME;
ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq);
if (!ok) return INVALID_TIME;
uint64_t res = 1000 * (double) count / freq;
return res;
}
#else
{
struct timespec time;
if (clock_gettime(CLOCK_REALTIME, &time))
return INVALID_TIME;
uint64_t res;
uint64_t sec = time.tv_sec;
if (sec > UINT64_MAX / 1000)
return INVALID_TIME;
res = sec * 1000;
uint64_t nsec = time.tv_nsec;
if (res > UINT64_MAX - nsec / 1000000)
return INVALID_TIME;
res += nsec / 1000000;
return res;
}
#endif
}
+6
View File
@@ -0,0 +1,6 @@
typedef uint64_t Time;
#define INVALID_TIME ((Time) UINT64_MAX-1)
Time get_current_time(void);