Author SHA1 Message Date
Claude ddae5e721f Enhance cookie jar implementation with comprehensive RFC 6265 support
This commit adds missing cookie handling cases to improve RFC 6265 compliance:

Cookie Expiration:
- Add expiration checking for both Expires and Max-Age attributes
- Max-Age takes precedence over Expires per RFC 6265
- Automatic removal of expired cookies when processing responses
- Store creation_time for accurate Max-Age calculation

Cookie Management:
- Update/replace existing cookies with same name, domain, and path
- Prevent duplicate cookie entries in the jar
- Consolidate multiple cookies into single Cookie header when sending

Domain Handling:
- Add domain validation to prevent servers from setting cookies for unrelated domains
- Case-insensitive domain matching per RFC 6265
- Handle leading dots in Domain attribute (strip per RFC 6265 Section 5.2.3)
- Subdomain matching for cookies without exact_domain flag

Path Handling:
- Implement RFC 6265 Section 5.1.4 default path computation
- Use request path minus last component when Path attribute not specified
- Fallback to "/" for edge cases

Parser Improvements:
- Add SameSite attribute support (None, Lax, Strict)
- Gracefully ignore unknown/extension cookie attributes instead of failing
- Forward compatibility with future cookie attributes

Other:
- Add time.h include for time() and time_t
- Add http_date_to_time() helper for expiration comparison
- Add is_cookie_expired() helper for expiration checks
- Add compute_default_cookie_path() per RFC 6265
2025-11-27 09:04:10 +00:00
32 changed files with 2227 additions and 2576 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
CFLAGS = -ggdb -Wall -Wextra
CFLAGS = -ggdb
LFLAGS =
ifeq ($(shell uname -s),Linux)
+6 -150
View File
@@ -1,156 +1,12 @@
# cHTTP
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
This is an HTTP library for C, featuring an HTTP(S) server, HTTP(S) client, and much more!
## 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:
* 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.
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.
+752 -814
View File
File diff suppressed because it is too large Load Diff
+265 -304
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -2,14 +2,14 @@
int main(void)
{
CHTTP_Response *response;
HTTP_Response *response;
int ret = chttp_get(CHTTP_STR("http://coz.is/"), NULL, 0, &response);
if (ret == CHTTP_OK) {
int ret = http_get(HTTP_STR("http://coz.is/"), NULL, 0, &response);
if (ret == HTTP_OK) {
printf("Received %d bytes\n", response->body.len);
chttp_free_response(response);
http_free_response(response);
} else {
printf("Request failure: %s\n", chttp_strerror(ret));
printf("Request failure: %s\n", http_strerror(ret));
}
return 0;
}
+14 -14
View File
@@ -4,33 +4,33 @@ int main(void)
{
int ret;
CHTTP_Server server;
ret = chttp_server_init(&server);
HTTP_Server server;
ret = http_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080);
ret = http_server_listen_tcp(&server, HTTP_STR("127.0.0.1"), 8080);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
return -1;
}
for (;;) {
CHTTP_Request *request;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &request, &builder);
HTTP_Request *request;
HTTP_ResponseBuilder builder;
http_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);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
http_response_builder_send(builder);
}
chttp_server_free(&server);
http_server_free(&server);
return 0;
}
+56 -65
View File
@@ -9,52 +9,45 @@
#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;
CHTTP_String remote = CHTTP_STR("http://coz.is");
HTTP_String remote = HTTP_STR("http://coz.is");
CHTTP_Client client;
ret = chttp_client_init(&client);
HTTP_Client client;
ret = http_client_init(&client);
if (ret < 0) {
printf("Couldn't initialize client (%s)\n", chttp_strerror(ret));
printf("Couldn't initialize client (%s)\n", http_strerror(ret));
return -1;
}
CHTTP_Server server;
ret = chttp_server_init(&server);
HTTP_Server server;
ret = http_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
ret = chttp_server_listen_tcp(&server, CHTTP_STR("127.0.0.1"), 8080);
ret = http_server_listen_tcp(&server, HTTP_STR("127.0.0.1"), 8080);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
return -1;
}
bool used[CHTTP_SERVER_CAPACITY];
for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++)
bool used[HTTP_SERVER_CAPACITY];
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
used[i] = false;
CHTTP_ResponseBuilder pending[CHTTP_SERVER_CAPACITY];
HTTP_ResponseBuilder pending[HTTP_SERVER_CAPACITY];
int num_pending = 0;
for (;;) {
#define POLL_CAPACITY (CHTTP_CLIENT_POLL_CAPACITY + CHTTP_SERVER_POLL_CAPACITY)
#define POLL_CAPACITY (HTTP_CLIENT_POLL_CAPACITY + HTTP_SERVER_POLL_CAPACITY)
void *ptrs[POLL_CAPACITY];
struct pollfd polled[POLL_CAPACITY];
@@ -62,84 +55,82 @@ int main()
EventRegister server_reg = {
ptrs,
polled,
0,
-1
0
};
chttp_server_register_events(&server, &server_reg);
http_server_register_events(&server, &server_reg);
EventRegister client_reg = {
ptrs + server_reg.num_polled,
polled + server_reg.num_polled,
0,
-1
0
};
chttp_client_register_events(&client, &client_reg);
http_client_register_events(&client, &client_reg);
int num_polled = server_reg.num_polled
+ client_reg.num_polled;
int timeout = pick_timeout(server_reg.timeout, client_reg.timeout);
POLL(polled, num_polled, timeout);
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 result;
void *user;
CHTTP_Response *response;
chttp_client_process_events(&client, client_reg);
while (chttp_client_next_response(&client, &result, &user, &response)) {
HTTP_Response *response;
http_client_process_events(&client, client_reg);
while (http_client_next_response(&client, &result, &user, &response)) {
CHTTP_ResponseBuilder *builder = (CHTTP_ResponseBuilder*) user;
HTTP_ResponseBuilder *builder = (HTTP_ResponseBuilder*) user;
int i = builder - pending;
assert(i > -1);
assert(i < CHTTP_SERVER_CAPACITY);
assert(i < HTTP_SERVER_CAPACITY);
if (result == CHTTP_OK) {
chttp_response_builder_status(*builder, response->status);
chttp_response_builder_body(*builder, response->body);
chttp_response_builder_send(*builder);
if (result == HTTP_OK) {
http_response_builder_status(*builder, response->status);
http_response_builder_body(*builder, response->body);
http_response_builder_send(*builder);
} else {
chttp_response_builder_status(*builder, 500);
chttp_response_builder_send(*builder);
http_response_builder_status(*builder, 500);
http_response_builder_send(*builder);
}
used[i] = false;
num_pending--;
}
CHTTP_Request *request;
CHTTP_ResponseBuilder response_builder;
chttp_server_process_events(&server, server_reg);
while (chttp_server_next_request(&server, &request, &response_builder)) {
HTTP_Request *request;
HTTP_ResponseBuilder response_builder;
http_server_process_events(&server, server_reg);
while (http_server_next_request(&server, &request, &response_builder)) {
int i = 0;
while (used[i]) {
i++;
assert(i < CHTTP_SERVER_CAPACITY);
assert(i < HTTP_SERVER_CAPACITY);
}
CHTTP_String path = request->url.path;
HTTP_String path = request->url.path;
if (path.len == 0)
path = CHTTP_STR("/");
path = HTTP_STR("/");
char target[1<<10];
int target_len = snprintf(target, sizeof(target),
"%.*s%.*s", CHTTP_UNPACK(remote), CHTTP_UNPACK(path));
"%.*s%.*s", HTTP_UNPACK(remote), HTTP_UNPACK(path));
if (target_len < 0 || target_len >= (int) sizeof(target)) {
chttp_response_builder_status(response_builder, 500);
chttp_response_builder_send(response_builder);
http_response_builder_status(response_builder, 500);
http_response_builder_send(response_builder);
continue;
}
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);
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);
if (ret < 0) {
chttp_response_builder_status(response_builder, 500);
chttp_response_builder_send(response_builder);
http_response_builder_status(response_builder, 500);
http_response_builder_send(response_builder);
continue;
}
@@ -149,7 +140,7 @@ int main()
}
}
chttp_server_free(&server);
chttp_client_free(&client);
http_server_free(&server);
http_client_free(&client);
return 0;
}
+23 -23
View File
@@ -16,21 +16,21 @@ int main(void)
int ret;
CHTTP_Server server;
ret = chttp_server_init(&server);
HTTP_Server server;
ret = http_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
HTTP_String local_addr = HTTP_STR("127.0.0.1");
uint16_t local_port = 8080;
ret = chttp_server_listen_tcp(&server, local_addr, local_port);
ret = http_server_listen_tcp(&server, local_addr, local_port);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
return -1;
}
@@ -48,29 +48,29 @@ int main(void)
for (;;) {
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
if (chttp_match_host(req, CHTTP_STR("websiteB.com"), local_port)) {
if (http_match_host(req, HTTP_STR("websiteB.com"), local_port)) {
// Website B
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
http_response_builder_send(builder);
} else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), local_port)) {
} else if (http_match_host(req, HTTP_STR("websiteC.com"), local_port)) {
// Website C
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
http_response_builder_send(builder);
} else {
// Serve websiteA by default
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
}
}
chttp_server_free(&server);
http_server_free(&server);
return 0;
}
+17 -17
View File
@@ -4,39 +4,39 @@
int main(void)
{
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
HTTP_String local_addr = HTTP_STR("127.0.0.1");
uint16_t local_port = 8443;
CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem");
CHTTP_String key_file = CHTTP_STR("websiteA_key.pem");
HTTP_String cert_file = HTTP_STR("websiteA_cert.pem");
HTTP_String key_file = HTTP_STR("websiteA_key.pem");
CHTTP_Server server;
int ret = chttp_server_init(&server);
HTTP_Server server;
int ret = http_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
http_server_set_reuse_addr(&server, true);
http_server_set_trace_bytes(&server, true);
ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
ret = http_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
return -1;
}
for (;;) {
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
}
chttp_server_free(&server);
http_server_free(&server);
return 0;
}
+53 -53
View File
@@ -38,47 +38,47 @@ int main(void)
if (ret < 0)
return -1;
CHTTP_Server server;
ret = chttp_server_init(&server);
HTTP_Server server;
ret = http_server_init(&server);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize server (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't initialize server (%s)\n", http_strerror(ret));
return -1;
}
chttp_server_set_reuse_addr(&server, true);
chttp_server_set_trace_bytes(&server, true);
http_server_set_reuse_addr(&server, true);
http_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.
CHTTP_String local_addr = CHTTP_STR("127.0.0.1");
HTTP_String local_addr = HTTP_STR("127.0.0.1");
uint16_t local_port = 8443;
CHTTP_String cert_file = CHTTP_STR("websiteA_cert.pem");
CHTTP_String key_file = CHTTP_STR("websiteA_key.pem");
HTTP_String cert_file = HTTP_STR("websiteA_cert.pem");
HTTP_String key_file = HTTP_STR("websiteA_key.pem");
ret = chttp_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
ret = http_server_listen_tls(&server, local_addr, local_port, cert_file, key_file);
if (ret < 0) {
fprintf(stderr, "Couldn't start listening (%s)\n", chttp_strerror(ret));
fprintf(stderr, "Couldn't start listening (%s)\n", http_strerror(ret));
return -1;
}
// Then we can add an arbitrary number of additional
// certificates using the add_website function
ret = chttp_server_add_certificate(&server,
CHTTP_STR("websiteB.com"),
CHTTP_STR("websiteB_cert.pem"),
CHTTP_STR("websiteB_key.pem"));
ret = http_server_add_certificate(&server,
HTTP_STR("websiteB.com"),
HTTP_STR("websiteB_cert.pem"),
HTTP_STR("websiteB_key.pem"));
if (ret < 0)
return -1;
ret = chttp_server_add_certificate(&server,
CHTTP_STR("websiteC.com"),
CHTTP_STR("websiteC_cert.pem"),
CHTTP_STR("websiteC_key.pem"));
ret = http_server_add_certificate(&server,
HTTP_STR("websiteC.com"),
HTTP_STR("websiteC_cert.pem"),
HTTP_STR("websiteC_key.pem"));
if (ret < 0)
return -1;
@@ -109,67 +109,67 @@ int main(void)
for (;;) {
CHTTP_Request *req;
CHTTP_ResponseBuilder builder;
chttp_server_wait_request(&server, &req, &builder);
HTTP_Request *req;
HTTP_ResponseBuilder builder;
http_server_wait_request(&server, &req, &builder);
if (chttp_match_host(req, CHTTP_STR("websiteB.com"), 8080) ||
chttp_match_host(req, CHTTP_STR("websiteB.com"), 8443)) {
if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) ||
http_match_host(req, HTTP_STR("websiteB.com"), 8443)) {
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteB.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
http_response_builder_send(builder);
} else if (chttp_match_host(req, CHTTP_STR("websiteC.com"), 8080) ||
chttp_match_host(req, CHTTP_STR("websiteC.com"), 8443)) {
} else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) ||
http_match_host(req, HTTP_STR("websiteC.com"), 8443)) {
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteC.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
http_response_builder_send(builder);
} else {
// Serve websiteA.com by default to be consistent
// with the certificate setup
chttp_response_builder_status(builder, 200);
chttp_response_builder_body(builder, CHTTP_STR("Hello from websiteA.com!"));
chttp_response_builder_send(builder);
http_response_builder_status(builder, 200);
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
http_response_builder_send(builder);
}
}
chttp_server_free(&server);
http_server_free(&server);
return 0;
}
int setup_test_certificates(void)
{
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")
int ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization A"),
HTTP_STR("websiteA.com"),
HTTP_STR("websiteA_cert.pem"),
HTTP_STR("websiteA_key.pem")
);
if (ret < 0)
return -1;
ret = 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")
ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization B"),
HTTP_STR("websiteB.com"),
HTTP_STR("websiteB_cert.pem"),
HTTP_STR("websiteB_key.pem")
);
if (ret < 0)
return -1;
ret = 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")
ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization C"),
HTTP_STR("websiteC.com"),
HTTP_STR("websiteC_cert.pem"),
HTTP_STR("websiteC_key.pem")
);
if (ret < 0)
return -1;
+15 -15
View File
@@ -4,37 +4,37 @@ int main(void)
{
int ret;
CHTTP_Client client;
ret = chttp_client_init(&client);
HTTP_Client client;
ret = http_client_init(&client);
if (ret < 0) {
printf("Couldn't initialize client (%s)\n", chttp_strerror(ret));
printf("Couldn't initialize client (%s)\n", http_strerror(ret));
return -1;
}
CHTTP_RequestBuilder builder = chttp_client_get_builder(&client);
HTTP_RequestBuilder builder = http_client_get_builder(&client);
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!"));
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!"));
ret = chttp_request_builder_send(builder);
ret = http_request_builder_send(builder);
if (ret < 0) {
printf("Couldn't build request (%s)\n", chttp_strerror(ret));
printf("Couldn't build request (%s)\n", http_strerror(ret));
return -1;
}
int result;
void *user;
CHTTP_Response *response;
chttp_client_wait_response(&client, &result, &user, &response);
HTTP_Response *response;
http_client_wait_response(&client, &result, &user, &response);
if (result == CHTTP_OK) {
if (result == HTTP_OK) {
printf("Received %d bytes\n", response->body.len);
} else {
printf("Couldn't receive response (%s)\n", chttp_strerror(result));
printf("Couldn't receive response (%s)\n", http_strerror(result));
}
chttp_free_response(response);
chttp_client_free(&client);
http_free_response(response);
http_client_free(&client);
return 0;
}
+3 -3
View File
@@ -1,3 +1,3 @@
[ ] 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
- 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
+1 -6
View File
@@ -53,13 +53,10 @@ 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")
@@ -67,18 +64,16 @@ 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 CHTTP_DONT_INCLUDE\n")
source.append_text("#ifndef HTTP_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 chttp_streq(CHTTP_String s1, CHTTP_String s2)
bool http_streq(HTTP_String s1, HTTP_String s2)
{
if (s1.len != s2.len)
return false;
@@ -18,7 +18,7 @@ static char to_lower(char c)
return c;
}
bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2)
bool http_streqcase(HTTP_String s1, HTTP_String s2)
{
if (s1.len != s2.len)
return false;
@@ -30,7 +30,7 @@ bool chttp_streqcase(CHTTP_String s1, CHTTP_String s2)
return true;
}
CHTTP_String chttp_trim(CHTTP_String s)
HTTP_String http_trim(HTTP_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(CHTTP_String prefix, CHTTP_String src)
void print_bytes(HTTP_String prefix, HTTP_String src)
{
if (src.len == 0)
return;
@@ -95,16 +95,16 @@ void print_bytes(CHTTP_String prefix, CHTTP_String src)
putc('\n', stream);
}
char *chttp_strerror(int code)
char *http_strerror(int code)
{
switch (code) {
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";
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";
}
return "???";
}
+22 -22
View File
@@ -1,56 +1,56 @@
enum {
CHTTP_OK = 0,
HTTP_OK = 0,
// A generic error occurred
CHTTP_ERROR_UNSPECIFIED = -1,
HTTP_ERROR_UNSPECIFIED = -1,
// Out of memory
CHTTP_ERROR_OOM = -2,
HTTP_ERROR_OOM = -2,
// Invalid URL
CHTTP_ERROR_BADURL = -3,
HTTP_ERROR_BADURL = -3,
// Parallel request limit reached
CHTTP_ERROR_REQLIMIT = -4,
HTTP_ERROR_REQLIMIT = -4,
// Invalid handle
CHTTP_ERROR_BADHANDLE = -5,
HTTP_ERROR_BADHANDLE = -5,
// TLS support not built-in
CHTTP_ERROR_NOTLS = -6,
HTTP_ERROR_NOTLS = -6,
};
// String type used throughout cHTTP.
typedef struct {
char *ptr;
int len;
} CHTTP_String;
} HTTP_String;
// Compare two strings and return true iff they have
// the same contents.
bool chttp_streq(CHTTP_String s1, CHTTP_String s2);
bool http_streq(HTTP_String s1, HTTP_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 chttp_streqcase(CHTTP_String s1, CHTTP_String s2);
bool http_streqcase(HTTP_String s1, HTTP_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.
CHTTP_String chttp_trim(CHTTP_String s);
HTTP_String http_trim(HTTP_String s);
// Print the contents of a byte string with the given prefix.
// This is primarily used for debugging purposes.
void print_bytes(CHTTP_String prefix, CHTTP_String src);
void print_bytes(HTTP_String prefix, HTTP_String src);
// TODO: comment
char *chttp_strerror(int code);
char *http_strerror(int code);
// Macro to simplify converting string literals to
// CHTTP_String.
// HTTP_String.
//
// Instead of doing this:
//
@@ -58,7 +58,7 @@ char *chttp_strerror(int code);
//
// You do this:
//
// CHTTP_String s = CHTTP_STR("some string")
// HTTP_String s = HTTP_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 *chttp_strerror(int code);
// #define S(X) ...
//
// But I don't want to cause collisions with user code.
#define CHTTP_STR(X) ((CHTTP_String) {(X), sizeof(X)-1})
#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1})
// Returns the number of items of a static array.
#define CHTTP_COUNT(X) (int) (sizeof(X) / sizeof((X)[0]))
#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0]))
// 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
// 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
// TODO: comment
#define CHTTP_UNREACHABLE __builtin_trap()
#define HTTP_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;
char *data = malloc(size);
uint8_t *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 ((size_t) len > dst.len) {
if (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
char *dst = queue->data + queue->head + (off - queue->curs);
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
memcpy(dst, src, len);
}
+5 -5
View File
@@ -15,19 +15,19 @@ enum {
};
typedef struct {
char *ptr;
size_t len;
uint8_t *ptr;
size_t len;
} ByteView;
// Fields are for internal use only
typedef struct {
uint64_t curs;
char* data;
uint8_t* data;
uint32_t head;
uint32_t size;
uint32_t used;
uint32_t limit;
char* read_target;
uint8_t* 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, chttp_queue_full
// grow further. When the limit is reached, http_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, CHTTP_String C, CHTTP_String O, CHTTP_String CN, int days)
static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days)
{
X509 *x509 = X509_new();
if (!x509)
@@ -63,7 +63,7 @@ static X509 *create_certificate(EVP_PKEY *pkey, CHTTP_String C, CHTTP_String O,
return x509;
}
static int save_private_key(EVP_PKEY *pkey, CHTTP_String file)
static int save_private_key(EVP_PKEY *pkey, HTTP_String file)
{
char copy[1<<10];
if (file.len >= (int) sizeof(copy))
@@ -85,7 +85,7 @@ static int save_private_key(EVP_PKEY *pkey, CHTTP_String file)
return 0;
}
static int save_certificate(X509 *x509, CHTTP_String file)
static int save_certificate(X509 *x509, HTTP_String file)
{
char copy[1<<10];
if (file.len >= (int) sizeof(copy))
@@ -107,8 +107,8 @@ static int save_certificate(X509 *x509, CHTTP_String file)
return 0;
}
int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file)
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file)
{
EVP_PKEY *pkey = generate_rsa_key_pair(2048);
if (pkey == NULL)
@@ -139,8 +139,8 @@ int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String C
#else
int chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file)
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_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 chttp_create_test_certificate(CHTTP_String C, CHTTP_String O, CHTTP_String CN,
CHTTP_String cert_file, CHTTP_String key_file);
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file);
+349 -248
View File
File diff suppressed because it is too large Load Diff
+77 -87
View File
@@ -1,26 +1,26 @@
#ifndef CHTTP_CLIENT_CAPACITY
#ifndef HTTP_CLIENT_CAPACITY
// The maximum ammount of requests that can be performed
// in parallel.
#define CHTTP_CLIENT_CAPACITY (1<<7)
#define HTTP_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 CHTTP_CLIENT_POLL_CAPACITY (CHTTP_CLIENT_CAPACITY+1)
#define HTTP_CLIENT_POLL_CAPACITY (HTTP_CLIENT_CAPACITY+1)
#ifndef CHTTP_COOKIE_JAR_CAPACITY
#ifndef HTTP_COOKIE_JAR_CAPACITY
// Maximum number of cookies that can be associated to a
// single client.
#define CHTTP_COOKIE_JAR_CAPACITY 128
#define HTTP_COOKIE_JAR_CAPACITY 128
#endif
typedef struct {
// Cookie name and value
CHTTP_String name;
CHTTP_String value;
HTTP_String name;
HTTP_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;
CHTTP_String domain;
HTTP_String domain;
// If "exact_path" is set, the cookie is only
// compatible with requests to paths that match
@@ -36,40 +36,47 @@ typedef struct {
// then any path that starts with "path" is
// compatible with the cookie.
bool exact_path;
CHTTP_String path;
HTTP_String path;
// This cookie can only be sent over HTTPS
bool secure;
} CHTTP_CookieJarEntry;
// 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;
typedef struct {
int count;
CHTTP_CookieJarEntry items[CHTTP_COOKIE_JAR_CAPACITY];
} CHTTP_CookieJar;
HTTP_CookieJarEntry items[HTTP_COOKIE_JAR_CAPACITY];
} HTTP_CookieJar;
typedef enum {
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;
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;
// Fields of this struct are private
typedef struct CHTTP_Client CHTTP_Client;
typedef struct HTTP_Client HTTP_Client;
typedef struct {
CHTTP_ClientConnState state;
HTTP_ClientConnState state;
// Handle to the socket
SocketHandle handle;
// Pointer back to the client
CHTTP_Client *client;
HTTP_Client *client;
// Generation counter for request builder validation
uint16_t gen;
@@ -81,15 +88,12 @@ typedef struct {
// TODO: comment
bool trace_bytes;
// TODO: comment
bool dont_verify_cert;
// Allocated copy of the URL string
CHTTP_String url_buffer;
HTTP_String url_buffer;
// Parsed URL for connection establishment
// All url.* pointers reference into url_buffer
CHTTP_URL url;
HTTP_URL url;
// Data received from the server
ByteQueue input;
@@ -104,21 +108,11 @@ typedef struct {
int result;
// Parsed response once complete
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;
HTTP_Response response;
} HTTP_ClientConn;
// Fields of this struct are private
struct CHTTP_Client {
struct HTTP_Client {
// Size limit of the input and output buffer of each
// connection.
@@ -126,18 +120,18 @@ struct CHTTP_Client {
uint32_t output_buffer_limit;
// List of cookies created during this session
CHTTP_CookieJar cookie_jar;
HTTP_CookieJar cookie_jar;
// Array of connections. The counter contains the
// number of structs such that state!=FREE.
int num_conns;
CHTTP_ClientConn conns[CHTTP_CLIENT_CAPACITY];
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
// Queue of indices referring to connections that
// are in the COMPLETE state.
int num_ready;
int ready_head;
int ready[CHTTP_CLIENT_CAPACITY];
int ready[HTTP_CLIENT_CAPACITY];
// Asynchronous TCP and TLS socket abstraction
SocketManager sockets;
@@ -147,80 +141,76 @@ struct CHTTP_Client {
// manager with a pointer to it. This allows
// allocating the exact number of sockets we
// will need.
Socket socket_pool[CHTTP_CLIENT_CAPACITY];
Socket socket_pool[HTTP_CLIENT_CAPACITY];
};
// Initialize an HTTP client object. This allows one to
// perform a number of requests in parallel.
int chttp_client_init(CHTTP_Client *client);
int http_client_init(HTTP_Client *client);
// Release resources associated to a client object.
void chttp_client_free(CHTTP_Client *client);
void http_client_free(HTTP_Client *client);
// Set input and output buffer size limit for any
// given connection. The default value is 1MB
void chttp_client_set_input_limit(CHTTP_Client *client, uint32_t limit);
void chttp_client_set_output_limit(CHTTP_Client *client, uint32_t limit);
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit);
void http_client_set_output_limit(HTTP_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 chttp_client_wakeup(CHTTP_Client *client);
int http_client_wakeup(HTTP_Client *client);
typedef struct {
CHTTP_Client *client;
HTTP_Client *client;
uint16_t index;
uint16_t gen;
} CHTTP_RequestBuilder;
} HTTP_RequestBuilder;
// Create a new request builder object.
CHTTP_RequestBuilder chttp_client_get_builder(CHTTP_Client *client);
HTTP_RequestBuilder http_client_get_builder(HTTP_Client *client);
// TODO: comment
void chttp_request_builder_set_user(CHTTP_RequestBuilder builder,
void http_request_builder_set_user(HTTP_RequestBuilder builder,
void *user);
// TODO: comment
void chttp_request_builder_trace(CHTTP_RequestBuilder builder,
void http_request_builder_trace(HTTP_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 chttp_request_builder_method(CHTTP_RequestBuilder builder,
CHTTP_Method method);
void http_request_builder_method(HTTP_RequestBuilder builder,
HTTP_Method method);
// Set the URL of the current request. This must be set after
// the method and before any header/body
void chttp_request_builder_target(CHTTP_RequestBuilder builder,
CHTTP_String url);
void http_request_builder_target(HTTP_RequestBuilder builder,
HTTP_String url);
// After the URL, the user may set zero or more headers.
void chttp_request_builder_header(CHTTP_RequestBuilder builder,
CHTTP_String str);
void http_request_builder_header(HTTP_RequestBuilder builder,
HTTP_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 chttp_request_builder_body(CHTTP_RequestBuilder builder,
CHTTP_String str);
void http_request_builder_body(HTTP_RequestBuilder builder,
HTTP_String str);
// Mark this request as complete. This invalidates the
// builder.
// Returns 0 on success, -1 on error.
int chttp_request_builder_send(CHTTP_RequestBuilder builder);
int http_request_builder_send(HTTP_RequestBuilder builder);
// Resets the event register with the list of descriptors
// the client wants monitored.
void chttp_client_register_events(CHTTP_Client *client,
void http_client_register_events(HTTP_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 chttp_client_process_events(CHTTP_Client *client,
void http_client_process_events(HTTP_Client *client,
EventRegister reg);
// After some I/O events were processes, some responses
@@ -228,33 +218,33 @@ void chttp_client_process_events(CHTTP_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
// chttp_free_response function.
// http_free_response function.
// TODO: Better comment talking about output arguments
bool chttp_client_next_response(CHTTP_Client *client,
int *result, void **user, CHTTP_Response **response);
bool http_client_next_response(HTTP_Client *client,
int *result, void **user, HTTP_Response **response);
// TODO: comment
void chttp_client_wait_response(CHTTP_Client *client,
int *result, void **user, CHTTP_Response **response);
void http_client_wait_response(HTTP_Client *client,
int *result, void **user, HTTP_Response **response);
// Free a response object. You can't access its fields
// again after this.
void chttp_free_response(CHTTP_Response *response);
void http_free_response(HTTP_Response *response);
// Perform a blocking GET request
int chttp_get(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_Response **response);
int http_get(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_Response **response);
// Perform a blocking POST request
int chttp_post(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_String body,
CHTTP_Response **response);
int http_post(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_String body,
HTTP_Response **response);
// Perform a blocking PUT request
int chttp_put(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_String body,
CHTTP_Response **response);
int http_put(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_String body,
HTTP_Response **response);
// Perform a blocking DELETE request
int chttp_delete(CHTTP_String url, CHTTP_String *headers,
int num_headers, CHTTP_Response **response);
int http_delete(HTTP_String url, HTTP_String *headers,
int num_headers, HTTP_Response **response);
+1 -2
View File
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -12,7 +13,6 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <time.h>
#include <limits.h>
#include <stdarg.h>
#include <unistd.h>
@@ -27,5 +27,4 @@
#ifdef HTTPS_ENABLED
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#endif
+188 -159
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, CHTTP_String *path, int abempty)
static int parse_path(Scanner *s, HTTP_String *path, int abempty)
{
int start = s->cur;
@@ -175,7 +175,7 @@ static int parse_path(Scanner *s, CHTTP_String *path, int abempty)
// (do nothing)
}
*path = (CHTTP_String) {
*path = (HTTP_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, CHTTP_IPv4 *ipv4)
static int parse_ipv4(Scanner *s, HTTP_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, CHTTP_IPv6 *ipv6)
static int parse_ipv6(Scanner *s, HTTP_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, CHTTP_String *regname)
static int parse_regname(Scanner *s, HTTP_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, CHTTP_String *regname)
return 0;
}
static int parse_host(Scanner *s, CHTTP_Host *host)
static int parse_host(Scanner *s, HTTP_Host *host)
{
int ret;
if (s->cur < s->len && s->src[s->cur] == '[') {
@@ -380,13 +380,13 @@ static int parse_host(Scanner *s, CHTTP_Host *host)
s->cur++;
int start = s->cur;
CHTTP_IPv6 ipv6;
HTTP_IPv6 ipv6;
ret = parse_ipv6(s, &ipv6);
if (ret < 0) return ret;
host->mode = CHTTP_HOST_MODE_IPV6;
host->mode = HTTP_HOST_MODE_IPV6;
host->ipv6 = ipv6;
host->text = (CHTTP_String) { s->src + start, s->cur - start };
host->text = (HTTP_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, CHTTP_Host *host)
} else {
int start = s->cur;
CHTTP_IPv4 ipv4;
HTTP_IPv4 ipv4;
ret = parse_ipv4(s, &ipv4);
if (ret >= 0) {
host->mode = CHTTP_HOST_MODE_IPV4;
host->mode = HTTP_HOST_MODE_IPV4;
host->ipv4 = ipv4;
} else {
s->cur = start;
CHTTP_String regname;
HTTP_String regname;
ret = parse_regname(s, &regname);
if (ret < 0) return ret;
host->mode = CHTTP_HOST_MODE_NAME;
host->mode = HTTP_HOST_MODE_NAME;
host->name = regname;
}
host->text = (CHTTP_String) { s->src + start, s->cur - start };
host->text = (HTTP_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, CHTTP_Authority *authority)
static int parse_authority(Scanner *s, HTTP_Authority *authority)
{
CHTTP_String userinfo;
HTTP_String userinfo;
{
int start = s->cur;
CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo);
if (s->cur < s->len && s->src[s->cur] == '@') {
userinfo = (CHTTP_String) {
userinfo = (HTTP_String) {
s->src + start,
s->cur - start
};
@@ -456,11 +456,11 @@ static int parse_authority(Scanner *s, CHTTP_Authority *authority)
} else {
// Rollback
s->cur = start;
userinfo = (CHTTP_String) {NULL, 0};
userinfo = (HTTP_String) {NULL, 0};
}
}
CHTTP_Host host;
HTTP_Host host;
{
int ret = parse_host(s, &host);
if (ret < 0)
@@ -487,9 +487,9 @@ static int parse_authority(Scanner *s, CHTTP_Authority *authority)
return 0;
}
static int parse_uri(Scanner *s, CHTTP_URL *url, int allow_fragment)
static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
{
CHTTP_String scheme = {0};
HTTP_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, CHTTP_URL *url, int allow_fragment)
do
s->cur++;
while (s->cur < s->len && is_scheme_body(s->src[s->cur]));
scheme = (CHTTP_String) {
scheme = (HTTP_String) {
s->src + start,
s->cur - start,
};
@@ -508,7 +508,7 @@ static int parse_uri(Scanner *s, CHTTP_URL *url, int allow_fragment)
}
int abempty = 0;
CHTTP_Authority authority = {0};
HTTP_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, CHTTP_URL *url, int allow_fragment)
abempty = 1;
}
CHTTP_String path;
HTTP_String path;
int ret = parse_path(s, &path, abempty);
if (ret < 0) return ret;
CHTTP_String query = {0};
HTTP_String query = {0};
if (s->cur < s->len && s->src[s->cur] == '?') {
int start = s->cur;
do
s->cur++;
while (s->cur < s->len && is_query(s->src[s->cur]));
query = (CHTTP_String) {
query = (HTTP_String) {
s->src + start,
s->cur - start,
};
}
CHTTP_String fragment = {0};
HTTP_String fragment = {0};
if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') {
int start = s->cur;
do
s->cur++;
while (s->cur < s->len && is_fragment(s->src[s->cur]));
fragment = (CHTTP_String) {
fragment = (HTTP_String) {
s->src + start,
s->cur - start,
};
@@ -562,7 +562,7 @@ static int parse_uri(Scanner *s, CHTTP_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, CHTTP_Host *host, int *port)
static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
{
int ret;
@@ -591,23 +591,23 @@ static int parse_authority_form(Scanner *s, CHTTP_Host *host, int *port)
return 0;
}
static int parse_origin_form(Scanner *s, CHTTP_String *path, CHTTP_String *query)
static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query)
{
int ret, start;
start = s->cur;
ret = consume_absolute_path(s);
if (ret < 0) return ret;
*path = (CHTTP_String) { s->src + start, s->cur - start };
*path = (HTTP_String) { s->src + start, s->cur - start };
if (s->cur < s->len && s->src[s->cur] == '?') {
start = s->cur;
do
s->cur++;
while (s->cur < s->len && is_query(s->src[s->cur]));
*query = (CHTTP_String) { s->src + start, s->cur - start };
*query = (HTTP_String) { s->src + start, s->cur - start };
} else
*query = (CHTTP_String) { NULL, 0 };
*query = (HTTP_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, CHTTP_URL *url)
static int parse_request_target(Scanner *s, HTTP_URL *url)
{
int ret;
memset(url, 0, sizeof(CHTTP_URL));
memset(url, 0, sizeof(HTTP_URL));
// asterisk-form
ret = parse_asterisk_form(s);
@@ -644,7 +644,7 @@ static int parse_request_target(Scanner *s, CHTTP_URL *url)
return -1;
}
bool consume_str(Scanner *scan, CHTTP_String token)
bool consume_str(Scanner *scan, HTTP_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, CHTTP_Header *headers, int max_headers)
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
{
int num_headers = 0;
while (!consume_str(s, CHTTP_STR("\r\n"))) {
while (!consume_str(s, HTTP_STR("\r\n"))) {
// RFC 9112:
// field-line = field-name ":" OWS field-value OWS
@@ -687,7 +687,7 @@ static int parse_headers(Scanner *s, CHTTP_Header *headers, int max_headers)
do
s->cur++;
while (s->cur < s->len && is_tchar(s->src[s->cur]));
CHTTP_String name = { s->src + start, s->cur - start };
HTTP_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, CHTTP_Header *headers, int max_headers)
start = s->cur;
CONSUME_OPTIONAL_SEQUENCE(s, is_header_body);
CHTTP_String body = { s->src + start, s->cur - start };
body = chttp_trim(body);
HTTP_String body = { s->src + start, s->cur - start };
body = http_trim(body);
if (num_headers < max_headers)
headers[num_headers++] = (CHTTP_Header) { name, body };
headers[num_headers++] = (HTTP_Header) { name, body };
if (!consume_str(s, CHTTP_STR("\r\n"))) {
if (!consume_str(s, HTTP_STR("\r\n"))) {
return -1;
}
}
@@ -722,7 +722,7 @@ static bool is_space(char c)
}
static int
parse_transfer_encoding(CHTTP_String src, TransferEncodingOption *dst, int max)
parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
{
Scanner s = { src.ptr, src.len, 0 };
@@ -733,10 +733,10 @@ parse_transfer_encoding(CHTTP_String src, TransferEncodingOption *dst, int max)
TransferEncodingOption opt;
if (0) {}
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 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 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,
CHTTP_Header *headers, int num_headers,
CHTTP_String *body, bool body_expected)
HTTP_Header *headers, int num_headers,
HTTP_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 = chttp_find_header(headers, num_headers, CHTTP_STR("Transfer-Encoding"));
int header_index = http_find_header(headers, num_headers, HTTP_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 (chttp_find_header(headers, num_headers, CHTTP_STR("Content-Length")) != -1)
if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1)
return -1;
CHTTP_String value = headers[header_index].value;
HTTP_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, CHTTP_COUNT(opts));
int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts));
if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED)
return -1;
CHTTP_String chunks_maybe[128];
CHTTP_String *chunks = chunks_maybe;
HTTP_String chunks_maybe[128];
HTTP_String *chunks = chunks_maybe;
int num_chunks = 0;
int max_chunks = CHTTP_COUNT(chunks_maybe);
int max_chunks = HTTP_COUNT(chunks_maybe);
#define FREE_CHUNK_LIST \
if (chunks != chunks_maybe) \
@@ -905,7 +905,7 @@ static int parse_body(Scanner *s,
max_chunks *= 2;
CHTTP_String *new_chunks = malloc(max_chunks * sizeof(CHTTP_String));
HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_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++] = (CHTTP_String) { chunk_ptr, chunk_len };
chunks[num_chunks++] = (HTTP_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 = (CHTTP_String) {
*body = (HTTP_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 = chttp_find_header(headers, num_headers, CHTTP_STR("Content-Length"));
header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length"));
if (header_index != -1) {
// Have Content-Length
CHTTP_String value = headers[header_index].value;
HTTP_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 = (CHTTP_String) { s->src + s->cur, len };
*body = (HTTP_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 = (CHTTP_String) { NULL, 0 };
*body = (HTTP_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, CHTTP_Request *req)
static int parse_request(Scanner *s, HTTP_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, CHTTP_Request *req)
req->secure = false;
if (0) {}
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 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 return -1;
{
@@ -1021,47 +1021,47 @@ static int parse_request(Scanner *s, CHTTP_Request *req)
s->cur = s2.cur;
}
if (consume_str(s, CHTTP_STR(" HTTP/1.1\r\n"))) {
if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) {
req->minor = 1;
} else if (consume_str(s, CHTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, CHTTP_STR(" HTTP/1\r\n"))) {
} else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) {
req->minor = 0;
} else {
return -1;
}
int num_headers = parse_headers(s, req->headers, CHTTP_MAX_HEADERS);
int num_headers = parse_headers(s, req->headers, HTTP_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 == CHTTP_METHOD_GET ||
req->method == CHTTP_METHOD_HEAD ||
req->method == CHTTP_METHOD_DELETE ||
req->method == CHTTP_METHOD_OPTIONS ||
req->method == CHTTP_METHOD_TRACE)
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)
body_expected = false;
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
}
int chttp_find_header(CHTTP_Header *headers, int num_headers, CHTTP_String name)
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
{
for (int i = 0; i < num_headers; i++)
if (chttp_streqcase(name, headers[i].name))
if (http_streqcase(name, headers[i].name))
return i;
return -1;
}
static int parse_response(Scanner *s, CHTTP_Response *res)
static int parse_response(Scanner *s, HTTP_Response *res)
{
if (!contains_head(s->src + s->cur, s->len - s->cur))
return 0;
if (consume_str(s, CHTTP_STR("HTTP/1.1 "))) {
if (consume_str(s, HTTP_STR("HTTP/1.1 "))) {
res->minor = 1;
} else if (consume_str(s, CHTTP_STR("HTTP/1.0 ")) || consume_str(s, CHTTP_STR("HTTP/1 "))) {
} else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) {
res->minor = 0;
} else {
return -1;
@@ -1093,7 +1093,7 @@ static int parse_response(Scanner *s, CHTTP_Response *res)
return -1;
s->cur += 2;
int num_headers = parse_headers(s, res->headers, CHTTP_MAX_HEADERS);
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
if (num_headers < 0)
return num_headers;
res->num_headers = num_headers;
@@ -1113,7 +1113,7 @@ static int parse_response(Scanner *s, CHTTP_Response *res)
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
}
int chttp_parse_ipv4(char *src, int len, CHTTP_IPv4 *ipv4)
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
{
Scanner s = {src, len, 0};
int ret = parse_ipv4(&s, ipv4);
@@ -1121,7 +1121,7 @@ int chttp_parse_ipv4(char *src, int len, CHTTP_IPv4 *ipv4)
return s.cur;
}
int chttp_parse_ipv6(char *src, int len, CHTTP_IPv6 *ipv6)
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
{
Scanner s = {src, len, 0};
int ret = parse_ipv6(&s, ipv6);
@@ -1129,7 +1129,7 @@ int chttp_parse_ipv6(char *src, int len, CHTTP_IPv6 *ipv6)
return s.cur;
}
int chttp_parse_url(char *src, int len, CHTTP_URL *url)
int http_parse_url(char *src, int len, HTTP_URL *url)
{
Scanner s = {src, len, 0};
int ret = parse_uri(&s, url, 1);
@@ -1138,7 +1138,7 @@ int chttp_parse_url(char *src, int len, CHTTP_URL *url)
return ret;
}
int chttp_parse_request(char *src, int len, CHTTP_Request *req)
int http_parse_request(char *src, int len, HTTP_Request *req)
{
Scanner s = {src, len, 0};
int ret = parse_request(&s, req);
@@ -1147,7 +1147,7 @@ int chttp_parse_request(char *src, int len, CHTTP_Request *req)
return ret;
}
int chttp_parse_response(char *src, int len, CHTTP_Response *res)
int http_parse_response(char *src, int len, HTTP_Response *res)
{
Scanner s = {src, len, 0};
int ret = parse_response(&s, res);
@@ -1156,14 +1156,14 @@ int chttp_parse_response(char *src, int len, CHTTP_Response *res)
return ret;
}
CHTTP_String chttp_get_cookie(CHTTP_Request *req, CHTTP_String name)
HTTP_String http_get_cookie(HTTP_Request *req, HTTP_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 (!chttp_streqcase(req->headers[i].name, CHTTP_STR("Cookie")))
if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie")))
continue;
char *src = req->headers[i].value.ptr;
@@ -1181,7 +1181,7 @@ CHTTP_String chttp_get_cookie(CHTTP_Request *req, CHTTP_String name)
while (cur < len && src[cur] != '=')
cur++;
CHTTP_String cookie_name = { src + off, cur - off };
HTTP_String cookie_name = { src + off, cur - off };
if (cur == len)
break;
@@ -1191,9 +1191,9 @@ CHTTP_String chttp_get_cookie(CHTTP_Request *req, CHTTP_String name)
while (cur < len && src[cur] != ';')
cur++;
CHTTP_String cookie_value = { src + off, cur - off };
HTTP_String cookie_value = { src + off, cur - off };
if (chttp_streq(name, cookie_name))
if (http_streq(name, cookie_name))
return cookie_value;
if (cur == len)
@@ -1202,10 +1202,10 @@ CHTTP_String chttp_get_cookie(CHTTP_Request *req, CHTTP_String name)
}
}
return CHTTP_STR("");
return HTTP_STR("");
}
CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int cap)
HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap)
{
// This is just a best-effort implementation
@@ -1218,29 +1218,29 @@ CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int
while (cur < len) {
CHTTP_String name;
HTTP_String name;
{
int off = cur;
while (cur < len && src[cur] != '=' && src[cur] != '&')
cur++;
name = (CHTTP_String) { src + off, cur - off };
name = (HTTP_String) { src + off, cur - off };
}
CHTTP_String body = CHTTP_STR("");
HTTP_String body = HTTP_STR("");
if (cur < len) {
cur++;
if (src[cur-1] == '=') {
int off = cur;
while (cur < len && src[cur] != '&')
cur++;
body = (CHTTP_String) { src + off, cur - off };
body = (HTTP_String) { src + off, cur - off };
if (cur < len)
cur++;
}
}
if (chttp_streq(str, name)) {
if (http_streq(str, name)) {
bool percent_encoded = false;
for (int i = 0; i < body.len; i++)
@@ -1253,9 +1253,9 @@ CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int
return body;
if (body.len > cap)
return (CHTTP_String) { NULL, 0 };
return (HTTP_String) { NULL, 0 };
CHTTP_String decoded = { mem, 0 };
HTTP_String decoded = { mem, 0 };
for (int i = 0; i < body.len; i++) {
char c = body.ptr[i];
@@ -1266,7 +1266,7 @@ CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int
if (body.len - i < 3
|| !is_hex_digit(body.ptr[i+1])
|| !is_hex_digit(body.ptr[i+2]))
return (CHTTP_String) { NULL, 0 };
return (HTTP_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 @@ CHTTP_String chttp_get_param(CHTTP_String body, CHTTP_String str, char *mem, int
}
}
return CHTTP_STR("");
return HTTP_STR("");
}
int chttp_get_param_i(CHTTP_String body, CHTTP_String str)
int http_get_param_i(HTTP_String body, HTTP_String str)
{
char buf[128];
CHTTP_String out = chttp_get_param(body, str, buf, (int) sizeof(buf));
HTTP_String out = http_get_param(body, str, buf, (int) sizeof(buf));
if (out.len == 0 || !is_digit(out.ptr[0]))
return -1;
@@ -1305,39 +1305,38 @@ int chttp_get_param_i(CHTTP_String body, CHTTP_String str)
return res;
}
bool chttp_match_host(CHTTP_Request *req, CHTTP_String domain, int port)
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
{
int idx = chttp_find_header(req->headers, req->num_headers, CHTTP_STR("Host"));
if (idx < 0)
return false;
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
assert(idx != -1); // Requests without the host header are always rejected
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 = (CHTTP_String) { tmp, ret };
domain = (HTTP_String) { tmp, ret };
}
CHTTP_String host = req->headers[idx].value;
return chttp_streqcase(host, domain);
HTTP_String host = req->headers[idx].value;
return http_streqcase(host, domain);
}
// <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
static int parse_date(Scanner *s, CHTTP_Date *out)
static int parse_date(Scanner *s, HTTP_Date *out)
{
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 },
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 },
};
bool found = false;
for (int i = 0; i < CHTTP_COUNT(week_day_table); i++)
for (int i = 0; i < HTTP_COUNT(week_day_table); i++)
if (consume_str(s, week_day_table[i].str)) {
out->week_day = week_day_table[i].val;
found = true;
@@ -1355,23 +1354,23 @@ static int parse_date(Scanner *s, CHTTP_Date *out)
+ (s->src[s->cur+1] - '0') * 1;
s->cur += 2;
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 },
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 },
};
found = false;
for (int i = 0; i < CHTTP_COUNT(month_table); i++)
for (int i = 0; i < HTTP_COUNT(month_table); i++)
if (consume_str(s, month_table[i].str)) {
out->month = month_table[i].val;
found = true;
@@ -1437,7 +1436,7 @@ static bool is_cookie_octet(char c)
(c >= 0x5D && c <= 0x7E);
}
int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out)
{
Scanner s = { str.ptr, str.len, 0 };
@@ -1448,7 +1447,7 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
do
s.cur++;
while (s.cur < s.len && is_tchar(s.src[s.cur]));
out->name = (CHTTP_String) { s.src + off, s.cur - off };
out->name = (HTTP_String) { s.src + off, s.cur - off };
// cookie-pair = cookie-name "=" cookie-value
if (s.cur == s.len || s.src[s.cur] != '=')
@@ -1463,35 +1462,36 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
s.cur++;
if (s.cur == s.len || s.src[s.cur] != '"')
return -1; // Missing closing double quote
out->value = (CHTTP_String) { s.src + off, s.cur - off };
out->value = (HTTP_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 = (CHTTP_String) { s.src + off, s.cur - off };
out->value = (HTTP_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 /
// extension-av
// samesite-av / extension-av
out->secure = false;
out->chttp_only = false;
out->http_only = false;
out->have_date = false;
out->have_max_age = false;
out->have_domain = false;
out->have_path = false;
while (consume_str(&s, CHTTP_STR("; "))) {
if (consume_str(&s, CHTTP_STR("Expires="))) {
out->have_samesite = false;
while (consume_str(&s, HTTP_STR("; "))) {
if (consume_str(&s, HTTP_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, CHTTP_STR("Max-Age="))) {
} else if (consume_str(&s, HTTP_STR("Max-Age="))) {
// max-age-av = "Max-Age=" non-zero-digit *DIGIT
@@ -1508,7 +1508,7 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
out->have_max_age = true;
out->max_age = value;
} else if (consume_str(&s, CHTTP_STR("Domain="))) {
} else if (consume_str(&s, HTTP_STR("Domain="))) {
// domain-av = "Domain=" domain-value
// domain-value = <subdomain>
@@ -1527,6 +1527,13 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_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]))
@@ -1558,9 +1565,9 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
}
out->have_domain = true;
out->domain = (CHTTP_String) { s.src + off, s.cur - off };
out->domain = (HTTP_String) { s.src + off, s.cur - off };
} else if (consume_str(&s, CHTTP_STR("Path="))) {
} else if (consume_str(&s, HTTP_STR("Path="))) {
// path-av = "Path=" path-value
// path-value = <any CHAR except CTLs or ";">
@@ -1570,20 +1577,42 @@ int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out)
s.cur++;
out->have_path = true;
out->path = (CHTTP_String) { s.src + off, s.cur - off };
out->path = (HTTP_String) { s.src + off, s.cur - off };
} else if (consume_str(&s, CHTTP_STR("Secure"))) {
} else if (consume_str(&s, HTTP_STR("Secure"))) {
// secure-av = "Secure"
out->secure = true;
} else if (consume_str(&s, CHTTP_STR("HttpOnly"))) {
} else if (consume_str(&s, HTTP_STR("HttpOnly"))) {
// httponly-av = "HttpOnly"
out->chttp_only = true;
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++;
}
} else {
return -1; // Invalid attribute
// 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++;
}
}
+96 -87
View File
@@ -1,155 +1,164 @@
#define CHTTP_MAX_HEADERS 32
#define HTTP_MAX_HEADERS 32
typedef struct {
unsigned int data;
} CHTTP_IPv4;
} HTTP_IPv4;
typedef struct {
unsigned short data[8];
} CHTTP_IPv6;
} HTTP_IPv6;
typedef enum {
CHTTP_HOST_MODE_VOID = 0,
CHTTP_HOST_MODE_NAME,
CHTTP_HOST_MODE_IPV4,
CHTTP_HOST_MODE_IPV6,
} CHTTP_HostMode;
HTTP_HOST_MODE_VOID = 0,
HTTP_HOST_MODE_NAME,
HTTP_HOST_MODE_IPV4,
HTTP_HOST_MODE_IPV6,
} HTTP_HostMode;
typedef struct {
CHTTP_HostMode mode;
CHTTP_String text;
HTTP_HostMode mode;
HTTP_String text;
union {
CHTTP_String name;
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
HTTP_String name;
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
};
} CHTTP_Host;
} HTTP_Host;
typedef struct {
CHTTP_String userinfo;
CHTTP_Host host;
HTTP_String userinfo;
HTTP_Host host;
int port;
} CHTTP_Authority;
} HTTP_Authority;
// ZII
typedef struct {
CHTTP_String scheme;
CHTTP_Authority authority;
CHTTP_String path;
CHTTP_String query;
CHTTP_String fragment;
} CHTTP_URL;
HTTP_String scheme;
HTTP_Authority authority;
HTTP_String path;
HTTP_String query;
HTTP_String fragment;
} HTTP_URL;
typedef enum {
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;
HTTP_METHOD_GET,
HTTP_METHOD_HEAD,
HTTP_METHOD_POST,
HTTP_METHOD_PUT,
HTTP_METHOD_DELETE,
HTTP_METHOD_CONNECT,
HTTP_METHOD_OPTIONS,
HTTP_METHOD_TRACE,
HTTP_METHOD_PATCH,
} HTTP_Method;
typedef struct {
CHTTP_String name;
CHTTP_String value;
} CHTTP_Header;
HTTP_String name;
HTTP_String value;
} HTTP_Header;
typedef struct {
bool secure;
CHTTP_Method method;
CHTTP_URL url;
HTTP_Method method;
HTTP_URL url;
int minor;
int num_headers;
CHTTP_Header headers[CHTTP_MAX_HEADERS];
CHTTP_String body;
} CHTTP_Request;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Request;
typedef struct {
void* context;
int minor;
int status;
CHTTP_String reason;
HTTP_String reason;
int num_headers;
CHTTP_Header headers[CHTTP_MAX_HEADERS];
CHTTP_String body;
} CHTTP_Response;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Response;
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_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_find_header (CHTTP_Header *headers, int num_headers, CHTTP_String name);
int http_find_header (HTTP_Header *headers, int num_headers, HTTP_String name);
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);
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);
// 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 chttp_match_host(CHTTP_Request *req, CHTTP_String domain, int port);
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
// Date and cookie types for Set-Cookie header parsing
typedef enum {
CHTTP_WEEKDAY_MON,
CHTTP_WEEKDAY_TUE,
CHTTP_WEEKDAY_WED,
CHTTP_WEEKDAY_THU,
CHTTP_WEEKDAY_FRI,
CHTTP_WEEKDAY_SAT,
CHTTP_WEEKDAY_SUN,
} CHTTP_WeekDay;
HTTP_WEEKDAY_MON,
HTTP_WEEKDAY_TUE,
HTTP_WEEKDAY_WED,
HTTP_WEEKDAY_THU,
HTTP_WEEKDAY_FRI,
HTTP_WEEKDAY_SAT,
HTTP_WEEKDAY_SUN,
} HTTP_WeekDay;
typedef enum {
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;
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;
typedef struct {
CHTTP_WeekDay week_day;
HTTP_WeekDay week_day;
int day;
CHTTP_Month month;
HTTP_Month month;
int year;
int hour;
int minute;
int second;
} CHTTP_Date;
} HTTP_Date;
typedef enum {
HTTP_SAMESITE_NONE,
HTTP_SAMESITE_LAX,
HTTP_SAMESITE_STRICT,
} HTTP_SameSite;
typedef struct {
CHTTP_String name;
CHTTP_String value;
HTTP_String name;
HTTP_String value;
bool secure;
bool chttp_only;
bool http_only;
bool have_date;
CHTTP_Date date;
HTTP_Date date;
bool have_max_age;
uint32_t max_age;
bool have_domain;
CHTTP_String domain;
HTTP_String domain;
bool have_path;
CHTTP_String path;
} CHTTP_SetCookie;
HTTP_String path;
bool have_samesite;
HTTP_SameSite samesite;
} HTTP_SetCookie;
// Parses a Set-Cookie header value
// Returns 0 on success, -1 on error
int chttp_parse_set_cookie(CHTTP_String str, CHTTP_SetCookie *out);
int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out);
+4 -6
View File
@@ -45,8 +45,6 @@ void client_secure_context_free(ClientSecureContext *ctx)
{
#ifdef HTTPS_ENABLED
SSL_CTX_free(ctx->p);
#else
(void) ctx;
#endif
}
@@ -76,7 +74,7 @@ static int servername_callback(SSL *ssl, int *ad, void *arg)
#endif
int server_secure_context_init(ServerSecureContext *ctx,
CHTTP_String cert_file, CHTTP_String key_file)
HTTP_String cert_file, HTTP_String key_file)
{
#ifdef HTTPS_ENABLED
SSL_CTX *p = SSL_CTX_new(TLS_server_method());
@@ -103,7 +101,7 @@ int server_secure_context_init(ServerSecureContext *ctx,
key_buffer[key_file.len] = '\0';
// Load certificate and private key
if (SSL_CTX_use_certificate_chain_file(p, cert_buffer) != 1) {
if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(p);
return -1;
}
@@ -145,7 +143,7 @@ void server_secure_context_free(ServerSecureContext *ctx)
}
int server_secure_context_add_certificate(ServerSecureContext *ctx,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
{
#ifdef HTTPS_ENABLED
if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT)
@@ -173,7 +171,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_chain_file(p, cert_buffer) != 1) {
if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 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,
CHTTP_String cert_file, CHTTP_String key_file);
HTTP_String cert_file, HTTP_String key_file);
void server_secure_context_free(ServerSecureContext *ctx);
int server_secure_context_add_certificate(ServerSecureContext *ctx,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
+124 -146
View File
@@ -1,34 +1,33 @@
static void chttp_server_conn_init(CHTTP_ServerConn *conn,
static void http_server_conn_init(HTTP_ServerConn *conn,
SocketHandle handle, uint32_t input_buffer_limit,
uint32_t output_buffer_limit)
{
conn->state = CHTTP_SERVER_CONN_BUFFERING;
conn->state = HTTP_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 chttp_server_conn_free(CHTTP_ServerConn *conn)
static void http_server_conn_free(HTTP_ServerConn *conn)
{
byte_queue_free(&conn->output);
byte_queue_free(&conn->input);
conn->state = CHTTP_SERVER_CONN_FREE;
}
int chttp_server_init(CHTTP_Server *server)
int http_server_init(HTTP_Server *server)
{
server->input_buffer_limit = 1<<20;
server->output_buffer_limit = 1<<24;
server->output_buffer_limit = 1<<20;
server->trace_bytes = false;
server->reuse_addr = false;
server->backlog = 32;
server->num_conns = 0;
for (int i = 0; i < CHTTP_SERVER_CAPACITY; i++) {
server->conns[i].state = CHTTP_SERVER_CONN_FREE;
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) {
server->conns[i].state = HTTP_SERVER_CONN_FREE;
server->conns[i].gen = 0;
}
@@ -36,77 +35,77 @@ int chttp_server_init(CHTTP_Server *server)
server->ready_head = 0;
return socket_manager_init(&server->sockets,
server->socket_pool, CHTTP_SERVER_CAPACITY);
server->socket_pool, HTTP_SERVER_CAPACITY);
}
void chttp_server_free(CHTTP_Server *server)
void http_server_free(HTTP_Server *server)
{
socket_manager_free(&server->sockets);
for (int i = 0, j = 0; j < server->num_conns; i++) {
CHTTP_ServerConn *conn = &server->conns[i];
if (conn->state == CHTTP_SERVER_CONN_FREE)
HTTP_ServerConn *conn = &server->conns[i];
if (conn->state == HTTP_SERVER_CONN_FREE)
continue;
j++;
chttp_server_conn_free(conn);
http_server_conn_free(conn);
}
}
void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit)
void http_server_set_input_limit(HTTP_Server *server, uint32_t limit)
{
server->input_buffer_limit = limit;
}
void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit)
void http_server_set_output_limit(HTTP_Server *server, uint32_t limit)
{
server->output_buffer_limit = limit;
}
void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value)
void http_server_set_trace_bytes(HTTP_Server *server, bool value)
{
server->trace_bytes = value;
}
void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse)
void http_server_set_reuse_addr(HTTP_Server *server, bool reuse)
{
server->reuse_addr = reuse;
}
void chttp_server_set_backlog(CHTTP_Server *server, int backlog)
void http_server_set_backlog(HTTP_Server *server, int backlog)
{
server->backlog = backlog;
}
int chttp_server_listen_tcp(CHTTP_Server *server,
CHTTP_String addr, Port port)
int http_server_listen_tcp(HTTP_Server *server,
HTTP_String addr, Port port)
{
return socket_manager_listen_tcp(&server->sockets,
addr, port, server->backlog, server->reuse_addr);
}
int chttp_server_listen_tls(CHTTP_Server *server,
CHTTP_String addr, Port port, CHTTP_String cert_file_name,
CHTTP_String key_file_name)
int http_server_listen_tls(HTTP_Server *server,
HTTP_String addr, Port port, HTTP_String cert_file_name,
HTTP_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 chttp_server_add_certificate(CHTTP_Server *server,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
int http_server_add_certificate(HTTP_Server *server,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
{
return socket_manager_add_certificate(&server->sockets,
domain, cert_file, key_file);
}
int chttp_server_wakeup(CHTTP_Server *server)
int http_server_wakeup(HTTP_Server *server)
{
return socket_manager_wakeup(&server->sockets);
}
void chttp_server_register_events(CHTTP_Server *server,
void http_server_register_events(HTTP_Server *server,
EventRegister *reg)
{
socket_manager_register_events(&server->sockets, reg);
@@ -118,12 +117,12 @@ void chttp_server_register_events(CHTTP_Server *server,
// to the ready queue. If the request is invalid,
// close the socket.
static void
check_request_buffer(CHTTP_Server *server, CHTTP_ServerConn *conn)
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
{
assert(conn->state == CHTTP_SERVER_CONN_BUFFERING);
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
ByteView src = byte_queue_read_buf(&conn->input);
int ret = chttp_parse_request(src.ptr, src.len, &conn->request);
int ret = http_parse_request(src.ptr, src.len, &conn->request);
if (ret < 0) {
// Invalid request
@@ -145,25 +144,22 @@ check_request_buffer(CHTTP_Server *server, CHTTP_ServerConn *conn)
// Ready
assert(ret > 0);
// 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->state = HTTP_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 < CHTTP_SERVER_CAPACITY);
int tail = (server->ready_head + server->num_ready) % CHTTP_SERVER_CAPACITY;
assert(server->num_ready < HTTP_SERVER_CAPACITY);
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
server->ready[tail] = conn - server->conns;
server->num_ready++;
}
}
static void
chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn)
{
if (conn->state == CHTTP_SERVER_CONN_FLUSHING) {
if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
ByteView src = byte_queue_read_buf(&conn->output);
@@ -172,7 +168,7 @@ chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
num = socket_send(&server->sockets, conn->handle, src.ptr, src.len);
if (server->trace_bytes)
print_bytes(CHTTP_STR("<< "), (CHTTP_String) { src.ptr, num });
print_bytes(HTTP_STR("<< "), (HTTP_String) { src.ptr, num });
byte_queue_read_ack(&conn->output, num);
@@ -189,13 +185,13 @@ chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
socket_close(&server->sockets, conn->handle);
return;
}
conn->state = CHTTP_SERVER_CONN_BUFFERING;
conn->state = HTTP_SERVER_CONN_BUFFERING;
}
}
if (conn->state == CHTTP_SERVER_CONN_BUFFERING) {
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
int min_recv = 1<<9;
int min_recv = 1<<10;
byte_queue_write_setmincap(&conn->input, min_recv);
// Note that it's extra important that we don't
@@ -209,7 +205,7 @@ chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
if (server->trace_bytes)
print_bytes(CHTTP_STR(">> "), (CHTTP_String) { dst.ptr, num });
print_bytes(HTTP_STR(">> "), (HTTP_String) { dst.ptr, num });
byte_queue_write_ack(&conn->input, num);
@@ -221,50 +217,38 @@ chttp_server_conn_process_events(CHTTP_Server *server, CHTTP_ServerConn *conn)
}
}
void chttp_server_process_events(CHTTP_Server *server,
void http_server_process_events(HTTP_Server *server,
EventRegister reg)
{
SocketEvent events[CHTTP_SERVER_CAPACITY];
SocketEvent events[HTTP_SERVER_CAPACITY];
int num_events = socket_manager_translate_events(&server->sockets, events, reg);
for (int i = 0; i < num_events; i++) {
CHTTP_ServerConn *conn = events[i].user;
HTTP_ServerConn *conn = events[i].user;
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
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);
http_server_conn_free(conn);
server->num_conns--;
} else if (events[i].type == SOCKET_EVENT_READY) {
if (events[i].user == NULL) {
if (server->num_conns == CHTTP_SERVER_CAPACITY) {
if (server->num_conns == HTTP_SERVER_CAPACITY) {
socket_close(&server->sockets, events[i].handle);
continue;
}
int j = 0;
while (server->conns[j].state != CHTTP_SERVER_CONN_FREE) {
while (server->conns[j].state != HTTP_SERVER_CONN_FREE) {
j++;
assert(j < CHTTP_SERVER_CAPACITY);
assert(i < HTTP_SERVER_CAPACITY);
}
conn = &server->conns[j];
chttp_server_conn_init(conn,
http_server_conn_init(conn,
events[i].handle,
server->input_buffer_limit,
server->output_buffer_limit);
@@ -274,43 +258,44 @@ void chttp_server_process_events(CHTTP_Server *server,
}
while (socket_ready(&server->sockets, events[i].handle)
&& conn->state != CHTTP_SERVER_CONN_WAIT_STATUS)
chttp_server_conn_process_events(server, conn);
&& conn->state != HTTP_SERVER_CONN_WAIT_STATUS)
http_server_conn_process_events(server, conn);
}
}
}
bool chttp_server_next_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder)
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder)
{
if (server->num_ready == 0)
return false;
CHTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]];
server->ready_head = (server->ready_head + 1) % CHTTP_SERVER_CAPACITY;
HTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]];
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
server->num_ready--;
assert(conn->state == CHTTP_SERVER_CONN_WAIT_STATUS);
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
*request = &conn->request;
*builder = (CHTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
return true;
}
void chttp_server_wait_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder)
void http_server_wait_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder)
{
for (;;) {
void *ptrs[CHTTP_SERVER_POLL_CAPACITY];
struct pollfd polled[CHTTP_SERVER_POLL_CAPACITY];
void *ptrs[HTTP_SERVER_POLL_CAPACITY];
struct pollfd polled[HTTP_SERVER_POLL_CAPACITY];
EventRegister reg = { ptrs, polled, 0, -1 };
chttp_server_register_events(server, &reg);
EventRegister reg = { ptrs, polled, 0 };
http_server_register_events(server, &reg);
POLL(reg.polled, reg.num_polled, reg.timeout);
if (reg.num_polled > 0)
POLL(reg.polled, reg.num_polled, -1);
chttp_server_process_events(server, reg);
http_server_process_events(server, reg);
if (chttp_server_next_request(server, request, builder))
if (http_server_next_request(server, request, builder))
break;
}
}
@@ -321,17 +306,17 @@ void chttp_server_wait_request(CHTTP_Server *server,
// can be returned, as any builder is invalidated by
// incrementing the connection's generation counter
// when a response is completed.
static CHTTP_ServerConn*
builder_to_conn(CHTTP_ResponseBuilder builder)
static HTTP_ServerConn*
builder_to_conn(HTTP_ResponseBuilder builder)
{
CHTTP_Server *server = builder.server;
HTTP_Server *server = builder.server;
if (server == NULL)
return NULL;
if (builder.index > CHTTP_SERVER_CAPACITY)
if (builder.index > HTTP_SERVER_CAPACITY)
return NULL;
CHTTP_ServerConn *conn = &server->conns[builder.index];
HTTP_ServerConn *conn = &server->conns[builder.index];
if (builder.gen != conn->gen)
return NULL;
@@ -406,32 +391,41 @@ get_status_text(int code)
}
static void
write_status(CHTTP_ServerConn *conn, int status)
write_status(HTTP_ServerConn *conn, int status)
{
byte_queue_write_fmt(&conn->output,
"HTTP/1.1 %d %s\r\n",
status, get_status_text(status));
}
void chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status)
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CHTTP_SERVER_CONN_WAIT_STATUS) {
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
// Reset all response content and start from scrach.
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
conn->state = CHTTP_SERVER_CONN_WAIT_STATUS;
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
}
write_status(conn, status);
conn->state = CHTTP_SERVER_CONN_WAIT_HEADER;
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
}
static bool is_header_valid(CHTTP_String str)
void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_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];
@@ -439,56 +433,43 @@ static bool is_header_valid(CHTTP_String str)
has_colon = true;
// Reject control characters (especially \r and \n)
if (c < 0x20 && c != '\t')
return false;
return;
}
return has_colon;
}
void chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
if (!has_colon)
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(CHTTP_ServerConn *conn)
static void append_special_headers(HTTP_ServerConn *conn)
{
CHTTP_String s;
HTTP_String s;
if (conn->closing) {
s = CHTTP_STR("Connection: Close\r\n");
s = HTTP_STR("Connection: Close\r\n");
byte_queue_write(&conn->output, s.ptr, s.len);
} else {
s = CHTTP_STR("Connection: Keep-Alive\r\n");
s = HTTP_STR("Connection: Keep-Alive\r\n");
byte_queue_write(&conn->output, s.ptr, s.len);
}
s = CHTTP_STR("Content-Length: ");
s = HTTP_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 = CHTTP_STR(TEN_SPACES "\r\n");
s = HTTP_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(CHTTP_ServerConn *conn)
static void patch_special_headers(HTTP_ServerConn *conn)
{
int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset);
@@ -499,52 +480,52 @@ static void patch_special_headers(CHTTP_ServerConn *conn)
byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len);
}
void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str)
void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write(&conn->output, str.ptr, str.len);
}
void chttp_response_builder_body_cap(CHTTP_ResponseBuilder builder, int cap)
void http_response_builder_body_cap(HTTP_ResponseBuilder builder, int cap)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write_setmincap(&conn->output, cap);
}
char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap)
char *http_response_builder_body_buf(HTTP_ResponseBuilder builder, int *cap)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return NULL;
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
return NULL;
ByteView tmp = byte_queue_write_buf(&conn->output);
@@ -552,45 +533,42 @@ char *chttp_response_builder_body_buf(CHTTP_ResponseBuilder builder, int *cap)
return tmp.ptr;
}
void chttp_response_builder_body_ack(CHTTP_ResponseBuilder builder, int num)
void http_response_builder_body_ack(HTTP_ResponseBuilder builder, int num)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CHTTP_SERVER_CONN_WAIT_BODY)
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write_ack(&conn->output, num);
}
void chttp_response_builder_send(CHTTP_ResponseBuilder builder)
void http_response_builder_send(HTTP_ResponseBuilder builder)
{
CHTTP_ServerConn *conn = builder_to_conn(builder);
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == CHTTP_SERVER_CONN_WAIT_STATUS) {
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
write_status(conn, 500);
conn->state = CHTTP_SERVER_CONN_WAIT_HEADER;
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
}
if (conn->state == CHTTP_SERVER_CONN_WAIT_HEADER) {
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = CHTTP_SERVER_CONN_WAIT_BODY;
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
assert(conn->state == CHTTP_SERVER_CONN_WAIT_BODY);
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
patch_special_headers(conn);
// Remove the buffered request
byte_queue_read_ack(&conn->input, conn->request_len);
conn->state = CHTTP_SERVER_CONN_FLUSHING;
conn->state = HTTP_SERVER_CONN_FLUSHING;
conn->gen++;
// Enable back I/O events
socket_silent(&builder.server->sockets, conn->handle, false);
chttp_server_conn_process_events(builder.server, conn);
http_server_conn_process_events(builder.server, conn);
}
+48 -48
View File
@@ -1,49 +1,49 @@
#ifndef CHTTP_SERVER_CAPACITY
#ifndef HTTP_SERVER_CAPACITY
// The maximum ammount of requests that can be handled
// in parallel.
#define CHTTP_SERVER_CAPACITY (1<<9)
#define HTTP_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 CHTTP_SERVER_POLL_CAPACITY (CHTTP_SERVER_CAPACITY+3)
#define HTTP_SERVER_POLL_CAPACITY (HTTP_SERVER_CAPACITY+3)
typedef enum {
// This struct is unused
CHTTP_SERVER_CONN_FREE,
HTTP_SERVER_CONN_FREE,
// No request was buffered yet.
CHTTP_SERVER_CONN_BUFFERING,
HTTP_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.
CHTTP_SERVER_CONN_WAIT_STATUS,
HTTP_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.
CHTTP_SERVER_CONN_WAIT_HEADER,
HTTP_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.
CHTTP_SERVER_CONN_WAIT_BODY,
HTTP_SERVER_CONN_WAIT_BODY,
// A response has been produced and it's being flushed.
CHTTP_SERVER_CONN_FLUSHING,
HTTP_SERVER_CONN_FLUSHING,
} CHTTP_ServerConnState;
} HTTP_ServerConnState;
// This structure represents the HTTP connection to
// a client.
typedef struct {
// If false, this struct is unused
CHTTP_ServerConnState state;
HTTP_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.
CHTTP_Request request;
HTTP_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;
} CHTTP_ServerConn;
} HTTP_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;
CHTTP_ServerConn conns[CHTTP_SERVER_CAPACITY];
HTTP_ServerConn conns[HTTP_SERVER_CAPACITY];
// Queue of indices referring to connections that
// are in the WAIT_STATUS state.
int num_ready;
int ready_head;
int ready[CHTTP_SERVER_CAPACITY];
int ready[HTTP_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[CHTTP_SERVER_CAPACITY];
Socket socket_pool[HTTP_SERVER_CAPACITY];
} CHTTP_Server;
} HTTP_Server;
// Initialize the HTTP server object. By default, it won't
// listen for connections. You need to call
//
// chttp_server_listen_tcp
// chttp_server_listen_tls
// http_server_listen_tcp
// http_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 chttp_server_init(CHTTP_Server *server);
int http_server_init(HTTP_Server *server);
// Release resources associated to the server.
void chttp_server_free(CHTTP_Server *server);
void http_server_free(HTTP_Server *server);
// Set input and output buffer size limit for any
// given connection. The default value is 1MB
void chttp_server_set_input_limit(CHTTP_Server *server, uint32_t limit);
void chttp_server_set_output_limit(CHTTP_Server *server, uint32_t limit);
void http_server_set_input_limit(HTTP_Server *server, uint32_t limit);
void http_server_set_output_limit(HTTP_Server *server, uint32_t limit);
// TODO: Comment
void chttp_server_set_trace_bytes(CHTTP_Server *server, bool value);
void http_server_set_trace_bytes(HTTP_Server *server, bool value);
// TODO: Comment
void chttp_server_set_reuse_addr(CHTTP_Server *server, bool reuse);
void http_server_set_reuse_addr(HTTP_Server *server, bool reuse);
// TODO: comment
void chttp_server_set_backlog(CHTTP_Server *server, int backlog);
void http_server_set_backlog(HTTP_Server *server, int backlog);
// Enable listening for plain HTTP requests at the
// specified interface.
int chttp_server_listen_tcp(CHTTP_Server *server,
CHTTP_String addr, Port port);
int http_server_listen_tcp(HTTP_Server *server,
HTTP_String addr, Port port);
// Enable listening for HTTPS requests at the specified
// interfact, using the specified certificate and key
// to verify the connection.
int chttp_server_listen_tls(CHTTP_Server *server, CHTTP_String addr, Port port,
CHTTP_String cert_file_name, CHTTP_String key_file_name);
int http_server_listen_tls(HTTP_Server *server, HTTP_String addr, Port port,
HTTP_String cert_file_name, HTTP_String key_file_name);
// Add the certificate for an additional domain when
// the server is listening for HTTPS requests.
int chttp_server_add_certificate(CHTTP_Server *server,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
int http_server_add_certificate(HTTP_Server *server,
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
// When a thread is blocked waiting for server events,
// other threads can call this function to wake it up.
int chttp_server_wakeup(CHTTP_Server *server);
int http_server_wakeup(HTTP_Server *server);
// Resets the event register with the list of descriptors
// the server wants monitored.
void chttp_server_register_events(CHTTP_Server *server,
void http_server_register_events(HTTP_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 chttp_server_process_events(CHTTP_Server *server,
void http_server_process_events(HTTP_Server *server,
EventRegister reg);
typedef struct {
CHTTP_Server *server;
HTTP_Server *server;
uint16_t index;
uint16_t gen;
} CHTTP_ResponseBuilder;
} HTTP_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 chttp_server_next_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder);
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder);
// TODO: comment
void chttp_server_wait_request(CHTTP_Server *server,
CHTTP_Request **request, CHTTP_ResponseBuilder *builder);
void http_server_wait_request(HTTP_Server *server,
HTTP_Request **request, HTTP_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 chttp_response_builder_status(CHTTP_ResponseBuilder builder, int status);
void http_response_builder_status(HTTP_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 chttp_response_builder_header(CHTTP_ResponseBuilder builder, CHTTP_String str);
void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str);
// Append some bytes to the response's body
void chttp_response_builder_body(CHTTP_ResponseBuilder builder, CHTTP_String str);
void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str);
// TODO: comment
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);
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);
// Mark the response as complete. This will invalidate
// the response builder handle.
void chttp_response_builder_send(CHTTP_ResponseBuilder builder);
void http_response_builder_send(HTTP_ResponseBuilder builder);
+64 -187
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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
}
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
}
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(sock);
if (*global_cleanup)
WSACleanup();
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
}
*a = sock;
*b = sock;
return CHTTP_OK;
return HTTP_OK;
#else
*global_cleanup = false;
int fds[2];
if (pipe(fds) < 0)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
*a = fds[0];
*b = fds[1];
return CHTTP_OK;
return HTTP_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 CHTTP_ERROR_UNSPECIFIED;
return CHTTP_OK;
return HTTP_ERROR_UNSPECIFIED;
return HTTP_OK;
#endif
#ifdef __linux__
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
if (value) flags &= ~O_NONBLOCK;
else flags |= O_NONBLOCK;
if (fcntl(sock, F_SETFL, flags) < 0)
return CHTTP_ERROR_UNSPECIFIED;
return CHTTP_OK;
return HTTP_ERROR_UNSPECIFIED;
return HTTP_OK;
#endif
}
static NATIVE_SOCKET create_listen_socket(CHTTP_String addr,
static NATIVE_SOCKET create_listen_socket(HTTP_String addr,
Port port, bool reuse_addr, int backlog)
{
NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -193,9 +193,6 @@ 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;
@@ -215,7 +212,7 @@ int socket_manager_init(SocketManager *sm, Socket *socks,
socks[i].state = SOCKET_STATE_FREE;
socks[i].gen = 1;
}
return CHTTP_OK;
return HTTP_OK;
}
void socket_manager_free(SocketManager *sm)
@@ -240,68 +237,58 @@ 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,
CHTTP_String addr, Port port, int backlog,
HTTP_String addr, Port port, int backlog,
bool reuse_addr)
{
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog);
if (sm->plain_sock == NATIVE_SOCKET_INVALID)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
return CHTTP_OK;
return HTTP_OK;
}
int socket_manager_listen_tls(SocketManager *sm,
CHTTP_String addr, Port port, int backlog,
bool reuse_addr, CHTTP_String cert_file,
CHTTP_String key_file)
HTTP_String addr, Port port, int backlog,
bool reuse_addr, HTTP_String cert_file,
HTTP_String key_file)
{
#ifndef HTTPS_ENABLED
return CHTTP_ERROR_NOTLS;
return HTTP_ERROR_NOTLS;
#endif
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog);
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
}
return CHTTP_OK;
return HTTP_OK;
}
int socket_manager_add_certificate(SocketManager *sm,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file)
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
{
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
int ret = server_secure_context_add_certificate(
&sm->server_secure_context, domain, cert_file, key_file);
if (ret < 0)
return ret;
return CHTTP_OK;
return HTTP_OK;
}
static bool is_secure(Socket *s)
@@ -310,7 +297,6 @@ static bool is_secure(Socket *s)
return s->server_secure_context != NULL
|| s->client_secure_context != NULL;
#else
(void) s;
return false;
#endif
}
@@ -441,13 +427,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(CHTTP_IPv4));
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(HTTP_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(CHTTP_IPv6));
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6));
ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf));
}
@@ -538,9 +524,6 @@ 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];
@@ -704,14 +687,15 @@ 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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
#else
if (write(sm->signal_sock, &byte, 1) < 0)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
#endif
return CHTTP_OK;
return HTTP_OK;
}
void socket_manager_register_events(
@@ -751,34 +735,17 @@ 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) {
deadline = 0;
if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) {
reg->num_polled = 0;
return;
}
if (s->events) {
@@ -789,20 +756,6 @@ 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
@@ -826,8 +779,6 @@ 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++) {
@@ -849,6 +800,10 @@ 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++;
@@ -868,16 +823,7 @@ 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;
@@ -908,10 +854,7 @@ int socket_manager_translate_events(
#endif
} else {
Socket *s = reg.ptrs[i];
assert(!s->silent);
socket_update(s);
}
}
@@ -922,34 +865,7 @@ int socket_manager_translate_events(
continue;
j++;
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) {
if (s->state == SOCKET_STATE_DIED) {
events[num_events++] = (SocketEvent) {
SOCKET_EVENT_DISCONNECT,
@@ -966,14 +882,9 @@ 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),
@@ -1005,7 +916,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 CHTTP_ERROR_OOM;
return HTTP_ERROR_OOM;
}
name->refs = 0;
memcpy(name->data, targets[i].name.ptr, targets[i].name.len);
@@ -1015,7 +926,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 CHTTP_ERROR_OOM;
return HTTP_ERROR_OOM;
memcpy(hostname, targets[i].name.ptr, targets[i].name.len);
hostname[targets[i].name.len] = '\0';
#endif
@@ -1027,12 +938,12 @@ static int resolve_connect_targets(ConnectTarget *targets,
free(name);
#endif
free_addr_list(resolved, num_resolved);
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
}
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
if (rp->ai_family == AF_INET) {
CHTTP_IPv4 ipv4 = *(CHTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr;
HTTP_IPv4 ipv4 = *(HTTP_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;
@@ -1044,7 +955,7 @@ static int resolve_connect_targets(ConnectTarget *targets,
num_resolved++;
}
} else if (rp->ai_family == AF_INET6) {
CHTTP_IPv6 ipv6 = *(CHTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr;
HTTP_IPv6 ipv6 = *(HTTP_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;
@@ -1096,25 +1007,20 @@ static int resolve_connect_targets(ConnectTarget *targets,
#define MAX_CONNECT_TARGETS 16
int socket_connect(SocketManager *sm, int num_targets,
ConnectTarget *targets, bool secure, bool dont_verify_cert,
void *user)
ConnectTarget *targets, bool secure, 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 CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
#ifdef HTTPS_ENABLED
if (!sm->at_least_one_secure_connect) {
if (client_secure_context_init(&sm->client_secure_context) < 0)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
sm->at_least_one_secure_connect = true;
}
#else
if (secure)
return CHTTP_ERROR_NOTLS;
return HTTP_ERROR_NOTLS;
#endif
AddressAndPort resolved[MAX_CONNECT_TARGETS];
@@ -1122,7 +1028,7 @@ int socket_connect(SocketManager *sm, int num_targets,
targets, num_targets, resolved, MAX_CONNECT_TARGETS);
if (num_resolved <= 0)
return CHTTP_ERROR_UNSPECIFIED;
return HTTP_ERROR_UNSPECIFIED;
Socket *s = sm->sockets;
while (s->state != SOCKET_STATE_FREE) {
@@ -1139,7 +1045,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 CHTTP_ERROR_OOM;
return HTTP_ERROR_OOM;
for (int i = 0; i < num_resolved; i++)
s->addrs[i] = resolved[i];
}
@@ -1147,27 +1053,17 @@ 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;
s->dont_verify_cert = false;
if (secure) {
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 CHTTP_OK;
return HTTP_OK;
}
static bool would_block(void)
@@ -1202,9 +1098,8 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
return 0;
}
int ret;
if (!is_secure(s)) {
ret = recv(s->sock, dst, max, 0);
int ret = recv(s->sock, dst, max, 0);
if (ret == 0) {
UPDATE_STATE(s->state, SOCKET_STATE_DIED);
s->events = 0;
@@ -1218,9 +1113,10 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
}
ret = 0;
}
return ret;
} else {
#ifdef HTTPS_ENABLED
ret = SSL_read(s->ssl, dst, max);
int ret = SSL_read(s->ssl, dst, max);
if (ret <= 0) {
int err = SSL_get_error(s->ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
@@ -1235,18 +1131,9 @@ int socket_recv(SocketManager *sm, SocketHandle handle,
}
ret = 0;
}
#else
// Unreachable
ret = 0;
return ret;
#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,
@@ -1293,15 +1180,13 @@ 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;
@@ -1332,6 +1217,7 @@ 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;
@@ -1341,12 +1227,3 @@ 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;
}
+12 -36
View File
@@ -50,7 +50,7 @@
#ifdef _WIN32
#define NATIVE_SOCKET SOCKET
#define NATIVE_SOCKET_INVALID INVALID_SOCKET
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
#define CLOSE_NATIVE_SOCKET closesocket
#else
#define NATIVE_SOCKET int
@@ -65,8 +65,6 @@ typedef uint16_t Port;
typedef enum {
SOCKET_EVENT_READY,
SOCKET_EVENT_CREATION_TIMEOUT,
SOCKET_EVENT_RECV_TIMEOUT,
SOCKET_EVENT_DISCONNECT,
} SocketEventType;
@@ -125,8 +123,8 @@ typedef struct {
// Internal use only
typedef struct {
union {
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
};
bool is_ipv4;
Port port;
@@ -151,9 +149,6 @@ 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
@@ -164,12 +159,6 @@ 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.
@@ -184,7 +173,6 @@ typedef struct {
ClientSecureContext *client_secure_context;
ServerSecureContext *server_secure_context;
SSL *ssl;
bool dont_verify_cert;
#endif
} Socket;
@@ -194,10 +182,6 @@ 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
@@ -246,9 +230,6 @@ 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
@@ -256,7 +237,7 @@ void socket_manager_set_recv_timeout(SocketManager *sm, int timeout);
// can only be used once per manager.
// Returns 0 on success, -1 on error.
int socket_manager_listen_tcp(SocketManager *sm,
CHTTP_String addr, Port port, int backlog,
HTTP_String addr, Port port, int backlog,
bool reuse_addr);
// Same as the previous function, but incoming
@@ -267,9 +248,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,
CHTTP_String addr, Port port, int backlog,
bool reuse_addr, CHTTP_String cert_file,
CHTTP_String key_file);
HTTP_String addr, Port port, int backlog,
bool reuse_addr, HTTP_String cert_file,
HTTP_String key_file);
// If the socket manager was configures to accept
// TLS connections, this adds additional certificates
@@ -277,7 +258,7 @@ int socket_manager_listen_tls(SocketManager *sm,
// authenticity.
// Returns 0 on success, -1 on error.
int socket_manager_add_certificate(SocketManager *sm,
CHTTP_String domain, CHTTP_String cert_file, CHTTP_String key_file);
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
// When a thread is blocked on a poll() call for
// descriptors associated to this socket manager,
@@ -290,7 +271,6 @@ typedef struct {
void **ptrs;
struct pollfd *polled;
int num_polled;
int timeout;
} EventRegister;
// Resets the event register with the list of descriptors
@@ -321,9 +301,9 @@ typedef struct {
ConnectTargetType type;
Port port;
union {
CHTTP_IPv4 ipv4;
CHTTP_IPv6 ipv6;
CHTTP_String name;
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
HTTP_String name;
};
} ConnectTarget;
@@ -332,8 +312,7 @@ 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, bool dont_verify_cert,
void *user);
ConnectTarget *targets, bool secure, void *user);
int socket_recv(SocketManager *sm, SocketHandle handle,
char *dst, int max);
@@ -354,6 +333,3 @@ 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
@@ -1,41 +0,0 @@
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
@@ -1,6 +0,0 @@
typedef uint64_t Time;
#define INVALID_TIME ((Time) UINT64_MAX-1)
Time get_current_time(void);