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