3302 lines
85 KiB
C
3302 lines
85 KiB
C
// cHTTP, an HTTP client and server library!
|
|
//
|
|
// This file was generated automatically. Do not modify directly.
|
|
//
|
|
// Refer to the end of this file for the license
|
|
#ifndef HTTP_DONT_INCLUDE
|
|
#include "chttp.h"
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/basic.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool http_streq(HTTP_String s1, HTTP_String s2)
|
|
{
|
|
if (s1.len != s2.len)
|
|
return false;
|
|
|
|
for (int i = 0; i < s1.len; i++)
|
|
if (s1.ptr[i] != s2.ptr[i])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static char to_lower(char c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 'a';
|
|
return c;
|
|
}
|
|
|
|
bool http_streqcase(HTTP_String s1, HTTP_String s2)
|
|
{
|
|
if (s1.len != s2.len)
|
|
return false;
|
|
|
|
for (int i = 0; i < s1.len; i++)
|
|
if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/secure_context.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int global_secure_context_init(void)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int global_secure_context_free(void)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int client_secure_context_init(ClientSecureContext *ctx)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int client_secure_context_free(ClientSecureContext *ctx)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int server_secure_context_init(ServerSecureContext *ctx)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int server_secure_context_free(ServerSecureContext *ctx)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int server_secure_context_add_certificate(ServerSecureContext *ctx,
|
|
String domain, String cert_file)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/socket.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b)
|
|
{
|
|
#ifdef _WIN32
|
|
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (sock == INVALID_SOCKET)
|
|
return -1;
|
|
|
|
// Bind to loopback address with port 0 (dynamic port assignment)
|
|
struct sockaddr_in addr;
|
|
int addr_len = sizeof(addr);
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
|
|
addr.sin_port = 0; // Let system choose port
|
|
|
|
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
// Optional: Set socket to non-blocking mode
|
|
// This prevents send() from blocking if the receive buffer is full
|
|
u_long mode = 1;
|
|
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
|
|
|
*a = sock;
|
|
*b = sock;
|
|
return 0;
|
|
#else
|
|
int fds[2];
|
|
if (pipe(fds) < 0)
|
|
return -1;
|
|
*a = fds[0];
|
|
*b = fds[1];
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int set_socket_blocking(NATIVE_SOCKET sock, bool value)
|
|
{
|
|
#ifdef _WIN32
|
|
u_long mode = !value;
|
|
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
|
return -1;
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
int flags = fcntl(sock, F_GETFL, 0);
|
|
if (flags < 0)
|
|
return -1;
|
|
if (value) flags &= ~O_NONBLOCK;
|
|
else flags |= O_NONBLOCK;
|
|
if (fcntl(sock, F_SETFL, flags) < 0)
|
|
return -1;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static NATIVE_SOCKET create_listen_socket(String addr,
|
|
Port port, bool reuse_addr, int backlog)
|
|
{
|
|
NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock == NATIVE_SOCKET_INVALID)
|
|
return NATIVE_SOCKET_INVALID;
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
if (reuse_addr) {
|
|
int one = 1;
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
|
|
}
|
|
|
|
struct in_addr addr_buf;
|
|
if (addr.len == 0)
|
|
addr_buf.s_addr = htonl(INADDR_ANY);
|
|
else {
|
|
|
|
char copy[100];
|
|
if (addr.len >= (int) sizeof(copy)) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
memcpy(copy, addr.ptr, addr.len);
|
|
copy[addr.len] = '\0';
|
|
|
|
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
}
|
|
|
|
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(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows?
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b)
|
|
{
|
|
#ifdef _WIN32
|
|
closesocket(a);
|
|
(void) b;
|
|
#else
|
|
close(a);
|
|
close(b);
|
|
#endif
|
|
}
|
|
|
|
int socket_manager_init(SocketManager *sm, Socket *socks,
|
|
int num_socks)
|
|
{
|
|
if (mutex_init(&sm->mutex) < 0)
|
|
return -1;
|
|
sm->plain_sock = NATIVE_SOCKET_INVALID;
|
|
sm->secure_sock = NATIVE_SOCKET_INVALID;
|
|
if (create_socket_pair(&sm->wait_sock, &sm->signal_sock) < 0)
|
|
return -1;
|
|
sm->at_least_one_secure_connect = false;
|
|
|
|
sm->num_used = 0;
|
|
sm->max_used = num_socks;
|
|
sm->sockets = socks;
|
|
return 0;
|
|
}
|
|
|
|
void socket_manager_free(SocketManager *sm)
|
|
{
|
|
close_socket_pair(sm->wait_sock, sm->signal_sock);
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
server_secure_context_free(&sm->server_secure_context);
|
|
|
|
if (sm->at_least_one_secure_connect)
|
|
client_secure_context_free(&sm->client_secure_context);
|
|
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(sm->plain_sock);
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(sm->secure_sock);
|
|
|
|
mutex_free(&sm->mutex);
|
|
}
|
|
|
|
int socket_manager_listen_tcp(SocketManager *sm,
|
|
String addr, Port port)
|
|
{
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
bool reuse_addr = false;
|
|
int backlog = 32;
|
|
sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog);
|
|
if (sm->plain_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int socket_manager_listen_tls(SocketManager *sm,
|
|
String addr, Port port, String cert_file_name,
|
|
String key_file_name)
|
|
{
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
bool reuse_addr = false;
|
|
int backlog = 32;
|
|
sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog);
|
|
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
if (server_secure_context_init(&sm->server_secure_context) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sm->secure_sock);
|
|
sm->secure_sock = NATIVE_SOCKET_INVALID;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int socket_manager_add_certificate(SocketManager *sm,
|
|
String domain, String cert_file, String key_file)
|
|
{
|
|
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
int ret = server_secure_context_add_certificate(
|
|
&sm->server_secure_context, domain, cert_file, key_file);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool connect_pending(void)
|
|
{
|
|
#ifdef _WIN32
|
|
return WSAGetLastError() == WSAEWOULDBLOCK;
|
|
#else
|
|
return errno == EINPROGRESS;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
connect_failed_because_of_peer(void)
|
|
{
|
|
#ifdef _WIN32
|
|
int err = WSAGetLastError();
|
|
#else
|
|
int err = errno;
|
|
#endif
|
|
return connect_failed_because_of_peer_2(err);
|
|
}
|
|
|
|
static bool
|
|
connect_failed_because_of_peer_2(int err)
|
|
{
|
|
#ifdef _WIN32
|
|
return err == WSAECONNREFUSED
|
|
|| err == WSAETIMEDOUT
|
|
|| err == WSAENETUNREACH
|
|
|| err == WSAEHOSTUNREACH;
|
|
#else
|
|
return err == ECONNREFUSED
|
|
|| err == ETIMEDOUT
|
|
|| err == ENETUNREACH
|
|
|| err == EHOSTUNREACH;
|
|
#endif
|
|
}
|
|
|
|
// This function moves the socket state machine
|
|
// to the next state until an I/O event would
|
|
// be required to continue.
|
|
static void socket_update(Socket *socket)
|
|
{
|
|
// Each case of this switch encodes a state transition.
|
|
// If the evaluated case requires a given I/O event to
|
|
// continue, the loop will exit so that the caller can
|
|
// wait for that event. If the case can continue to a
|
|
// different case, the again flag is set, which causes
|
|
// a different case to be evaluated.
|
|
bool again;
|
|
do {
|
|
again = false;
|
|
switch (socket->state) {
|
|
case SOCKET_STATE_PENDING:
|
|
{
|
|
// This point may be reached because
|
|
// 1. The socket was just created by a connect
|
|
// operation.
|
|
// 2. Connecting to a host failed and now we
|
|
// need to try the next one.
|
|
// If (2) is true, we have some resources
|
|
// to clean up.
|
|
|
|
if (socket->sock != NATIVE_SOCKET_INVALID) {
|
|
// This is not the first attempt
|
|
|
|
CLOSE_NATIVE_SOCKET(socket->sock);
|
|
|
|
socket->next_addr++;
|
|
if (socket->next_addr == socket->num_addr) {
|
|
assert(0); // TODO
|
|
}
|
|
}
|
|
AddressAndPort addr = socket->addrs[socket->next_addr];
|
|
|
|
int family = (addr.is_ipv4 ? AF_INET : AF_INET6);
|
|
NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0);
|
|
if (sock == NATIVE_SOCKET_INVALID) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
int ret;
|
|
if (addr.is_ipv4) {
|
|
struct sockaddr_in buf;
|
|
buf.sin_family = AF_INET;
|
|
buf.sin_port = htons(addr.port);
|
|
memset(&buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
|
ret = connect(sock, (struct sockaddr*) &connect_buf,
|
|
sizeof(connect_buf));
|
|
} else {
|
|
struct sockaddr_in6 buf;
|
|
buf.sin6_family = AF_INET6;
|
|
buf.sin6_port = htons(addr.port);
|
|
memset(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6));
|
|
ret = connect(sock, (struct sockaddr*) &connect_buf,
|
|
sizeof(connect_buf));
|
|
}
|
|
|
|
if (ret == 0) {
|
|
// Connect resolved immediately
|
|
socket->sock = sock;
|
|
socket->state = SOCKET_STATE_CONNECTED;
|
|
socket->events = 0;
|
|
again = true;
|
|
} else if (connect_pending()) {
|
|
// Connect is pending, which is expected
|
|
socket->sock = sock;
|
|
socket->state = SOCKET_STATE_CONNECTING;
|
|
socket->events = POLLOUT;
|
|
} else if (connect_failed_because_of_peer()) {
|
|
// Conenct failed due to the peer host
|
|
// We should try a different address.
|
|
socket->sock = sock;
|
|
socket->state = SOCKET_STATE_PENDING;
|
|
socket->events = 0;
|
|
again = true;
|
|
} else {
|
|
// An error occurred that we can't recover from
|
|
socket->sock = sock;
|
|
socket->state = SOCKET_STATE_DIED;
|
|
socket->events = 0;
|
|
again = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_CONNECTING:
|
|
{
|
|
// This point is reached when a connect()
|
|
// operation completes.
|
|
|
|
int err = 0;
|
|
socklen_t len = sizeof(err);
|
|
if (getsockopt(socket->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
if (err == 0) {
|
|
// Connection succeded
|
|
socket->state = SOCKET_STATE_CONNECTED;
|
|
socket->events = 0;
|
|
again = true;
|
|
} else if (connect_failed_because_of_peer_2(err)) {
|
|
// Try the next address
|
|
socket->state = SOCKET_STATE_PENDING;
|
|
socket->events = 0;
|
|
again = true;
|
|
} else {
|
|
socket->state = SOCKET_STATE_DIED;
|
|
socket->events = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_CONNECTED:
|
|
{
|
|
// We managed to connect to the peer.
|
|
// We can free the target array if it
|
|
// was allocated dynamically.
|
|
if (socket->num_addr > 1)
|
|
free(socket->addrs);
|
|
|
|
if (!is_secure(socket)) {
|
|
socket->events = 0;
|
|
socket->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
assert(0); // TODO
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_ESTABLISHED_WAIT:
|
|
socket->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
socket->events = 0;
|
|
break;
|
|
|
|
case SOCKET_STATE_SHUTDOWN:
|
|
{
|
|
if (!is_secure(socket)) {
|
|
socket->state = SOCKET_STATE_DIED;
|
|
socket->events = 0;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
assert(0); // TODO
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Do nothing
|
|
break;
|
|
}
|
|
} while (again);
|
|
}
|
|
|
|
int socket_manager_wakeup(SocketManager *sm)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
// TODO
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int socket_manager_register_events_nolock(
|
|
SocketManager *sm, struct pollfd *polled, int max_polled)
|
|
{
|
|
// The poll array must be able to hold descriptors
|
|
// for a socket manager at full capacity. Note that
|
|
// other than having a number of connection sockets,
|
|
// the manager also needs 2 for the listeners and
|
|
// one for the wakeup self-pipe.
|
|
if (max_polled < sm->max_used+3)
|
|
return -1;
|
|
int num_polled = 0;
|
|
|
|
polled[num_polled].fd = sm->wait_sock;
|
|
polled[num_polled].events = 0;
|
|
polled[num_polled].revents = 0;
|
|
num_polled++;
|
|
|
|
// If the manager isn't at full capacity, monitor
|
|
// the listener sockets for incoming connections.
|
|
if (sm->num_used < sm->max_used) {
|
|
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID) {
|
|
polled[num_polled].fd = sm->plain_sock;
|
|
polled[num_polled].events = POLLIN;
|
|
polled[num_polled].revents = 0;
|
|
num_polled++;
|
|
}
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID) {
|
|
polled[num_polled].fd = sm->secure_sock;
|
|
polled[num_polled].events = POLLIN;
|
|
polled[num_polled].revents = 0;
|
|
num_polled++;
|
|
}
|
|
}
|
|
|
|
// Iterate over each socket and register those that
|
|
// are waiting for I/O. If at least one socket that
|
|
// is ready to be processed exists, return an empty
|
|
// event registration list so that those entries can
|
|
// be processed immediately.
|
|
for (int i = 0, j = 0; j < sm->num_used; i++) {
|
|
Socket *s = &sm->sockets[i];
|
|
if (s->state = SOCKET_STATE_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY)
|
|
return 0;
|
|
|
|
if (s->events) {
|
|
polled[num_polled].fd = s->sock;
|
|
polled[num_polled].events = s->events;
|
|
polled[num_polled].revents = 0;
|
|
num_polled++;
|
|
}
|
|
}
|
|
|
|
return num_polled;
|
|
}
|
|
|
|
int socket_manager_register_events(SocketManager *sm,
|
|
struct pollfd *polled, int max_polled)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_manager_register_events_nolock(
|
|
sm, polled, max_polled);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
static SocketHandle
|
|
socket_to_handle(SocketManager *sm, Socket *s)
|
|
{
|
|
assert(0); // TODO
|
|
}
|
|
|
|
static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
assert(0); // TODO
|
|
}
|
|
|
|
static int socket_manager_translate_events_nolock(
|
|
SocketManager *sm, SocketEvent *events, int max_events,
|
|
struct pollfd *polled, int num_polled)
|
|
{
|
|
int num_events = 0;
|
|
for (int i = 0; i < num_polled; i++) {
|
|
|
|
if (polled[i].fd == sm->plain_sock ||
|
|
polled[i].fd == sm->secure_sock) {
|
|
|
|
// We only listen for input events from the listener
|
|
// if the socket pool isn't fool. This ensures that
|
|
// at least one socket struct is available. Note that
|
|
// it's still possible that we were at capacity MAX-1
|
|
// and then got events from both the TCP and TCP/TLS
|
|
// listeners, causing one to be left witout a struct.
|
|
// This means we still need to check for full capacity.
|
|
// Fortunately, poll() is level-triggered, which means
|
|
// we'll handle this at the next iteration.
|
|
if (sm->num_used == sm->max_used)
|
|
continue;
|
|
|
|
// Determine whether the event came from
|
|
// the encrypted listener or not.
|
|
bool secure = (polled[i].fd == sm->secure_sock);
|
|
|
|
Socket *s = sm->sockets;
|
|
while (s->type != SOCKET_FREE) {
|
|
s++;
|
|
assert(s - sm->sockets < + sm->max_used);
|
|
}
|
|
|
|
NATIVE_SOCKET sock = accept(polled[i].fd, NULL, NULL);
|
|
if (sock == NATIVE_SOCKET_INVALID)
|
|
continue;
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
continue;
|
|
}
|
|
|
|
s->state = SOCKET_STATE_ACCEPTED;
|
|
s->sock = sock;
|
|
s->events = 0;
|
|
s->user = NULL;
|
|
|
|
socket_update(s);
|
|
if (s->state == SOCKET_STATE_DIED) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
s->state = SOCKET_STATE_FREE;
|
|
continue;
|
|
}
|
|
|
|
pool->num_used++;
|
|
|
|
} else if (polled[i].fd == sm->wait_sock) {
|
|
|
|
// TODO: consume
|
|
|
|
} else {
|
|
if (polled[i].revents)
|
|
socket_update(s);
|
|
}
|
|
}
|
|
|
|
for (int i = 0, j = 0; j < sm->num_used; i++) {
|
|
Socket *s = &sm->sockets[i];
|
|
if (s->state == SOCKET_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
if (num_events == max_events)
|
|
break;
|
|
|
|
if (s->state == SOCKET_DIED) {
|
|
|
|
events[num_events++] = (SocketEvent) {
|
|
SOCKET_EVENT_DISCONNECT,
|
|
SOCKET_HANDE_INVALID,
|
|
s->user
|
|
};
|
|
|
|
// Free resources associated to socket
|
|
s->state = SOCKET_FREE;
|
|
if (s->sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(s->sock);
|
|
if (s->sock == SOCKET_STATE_PENDING ||
|
|
s->sock == SOCKET_STATE_CONNECTING) {
|
|
if (s->num_addr > 1)
|
|
free(s->addrs);
|
|
}
|
|
s->num_used--;
|
|
|
|
} else if (s->state == SOCKET_STATE_ESTABLISHED_READY) {
|
|
events[num_events++] = (SocketEvent) {
|
|
SOCKET_EVENT_READY,
|
|
socket_to_handle(sm, s),
|
|
s->user
|
|
};
|
|
}
|
|
}
|
|
|
|
return num_events;
|
|
}
|
|
|
|
int socket_manager_translate_events(SocketManager *sm,
|
|
SocketEvent *events, int max_events, struct pollfd *polled,
|
|
int num_polled)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
socket_manager_translate_events_nolock(
|
|
sm, events, max_events, polled, num_polled);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int resolve_connect_targets(ConnectTarget *targets,
|
|
int num_targets, AddressAndPort *resolved, int max_resolved)
|
|
{
|
|
int num_resolved = 0;
|
|
for (int i = 0; i < num_targets; i++) {
|
|
switch (targets[i].type) {
|
|
case CONNECT_TARGET_NAME:
|
|
{
|
|
char portstr[16];
|
|
int len = snprintf(portstr, sizeof(portstr), "%u", port);
|
|
if (len < 0 || len >= (int) sizeof(portstr))
|
|
return -1;
|
|
|
|
struct addrinfo hints = {0};
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
struct addrinfo *res = NULL;
|
|
int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res);
|
|
if (ret != 0)
|
|
return -1;
|
|
|
|
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
|
|
if (rp->ai_family == AF_INET) {
|
|
IPv4 ipv4 = *(IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr;
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = true;
|
|
resolved[num_resolved].ipv4 = ipv4;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
num_resolved++;
|
|
}
|
|
} else if (rp->ai_family == AF_INET6) {
|
|
IPv6 ipv6 = *(IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr;
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = false;
|
|
resolved[num_resolved].ipv4 = ipv6;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
num_resolved++;
|
|
}
|
|
}
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
}
|
|
break;
|
|
case CONNECT_TARGET_IPV4:
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = true;
|
|
resolved[num_resolved].ipv4 = targets[i].ipv4;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
num_resolved++;
|
|
}
|
|
break;
|
|
case CONNECT_TARGET_IPV6:
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = false;
|
|
resolved[num_resolved].ipv6 = targets[i].ipv6;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
num_resolved++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return num_resolved;
|
|
}
|
|
|
|
int socket_connect(SocketManager *sm, int num_targets,
|
|
ConnectTarget *targets, bool secure, void *user)
|
|
{
|
|
if (sm->num_used == sm->max_used)
|
|
return -1;
|
|
|
|
AddressAndPort resolved[MAX_CONNECT_TARGETS];
|
|
int num_resolved = resolve_connect_targets(
|
|
targets, num_targets, resolved, MAX_CONNECT_TARGETS);
|
|
|
|
if (num_resolved <= 0)
|
|
return -1;
|
|
|
|
Socket *s = sm->sockets;
|
|
while (s->type != SOCKET_FREE) {
|
|
s++;
|
|
assert(s - sm->sockets < + sm->max_used);
|
|
}
|
|
|
|
if (num_resolved == 1) {
|
|
s->num_addr = 1;
|
|
s->next_addr = 0;
|
|
s->addr = resolved[0];
|
|
} else {
|
|
s->num_addr = num_resolved;
|
|
s->next_addr = 0;
|
|
s->addrs = malloc(num_resolved * sizeof(AddressAndPort));
|
|
if (s->addrs == NULL)
|
|
return -1;
|
|
for (int i = 0; i < num_resolved; i++)
|
|
s->addrs[i] = resolved[i];
|
|
}
|
|
|
|
s->type = SOCKET_STATE_PENDING;
|
|
s->sock = NATIVE_SOCKET_INVALID;
|
|
s->user = user;
|
|
s->num_used++;
|
|
return 0;
|
|
}
|
|
|
|
static int socket_recv_nolock(SocketManager *sm, SocketHandle handle,
|
|
char *dst, int max)
|
|
{
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
return 0;
|
|
|
|
if (s->state != SOCKET_STATE_ESTABLISHED_READY) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!is_secure(s)) {
|
|
int ret = recv(s->sock, dst, max, 0);
|
|
if (ret == 0) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
} else if (ret < 0) {
|
|
if (would_block()) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (!interrupted()) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return 0;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
int ret = SSL_read(s->ssl, dst, max);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int socket_recv(SocketManager *sm, SocketHandle handle,
|
|
char *dst, int max)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_recv_nolock(sm, handle, dst, max);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
static int socket_send_nolock(SocketManager *sm, SocketHandle handle,
|
|
char *src, int len)
|
|
{
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
return 0;
|
|
|
|
if (s->state != SOCKET_STATE_ESTABLISHED_READY) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!socket_secure(s)) {
|
|
int ret = send(s->sock, src, len, 0);
|
|
if (ret < 0) {
|
|
if (would_block()) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else if (!interrupted()) {
|
|
s->state = SOCKET_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
int ret = SSL_write(s->ssl, src, len);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int socket_send(SocketManager *sm, SocketHandle handle,
|
|
char *src, int len)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_send_nolock(sm, handle, src, len);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
void socket_close(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
// TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance?
|
|
s->state = SOCKET_STATE_SHUTDOWN;
|
|
s->events = 0;
|
|
socket_update(s);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int socket_is_secure(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
|
|
int ret;
|
|
if (s == NULL)
|
|
ret = -1;
|
|
else {
|
|
ret = (s->ssl != NULL);
|
|
}
|
|
#else
|
|
int ret = 0;
|
|
#endif
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
int socket_set_user(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
ret = -1;
|
|
else
|
|
s->user = user;
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/thread.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int mutex_init(Mutex *mutex)
|
|
{
|
|
#ifdef _WIN32
|
|
InitializeCriticalSection(mutex); // TODO: mock?
|
|
return 0;
|
|
#else
|
|
if (pthread_mutex_init(mutex, NULL)) // TODO: mock
|
|
return -1;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int mutex_free(Mutex *mutex)
|
|
{
|
|
#ifdef _WIN32
|
|
DeleteCriticalSection(mutex); // TODO: mock?
|
|
return 0;
|
|
#else
|
|
if (pthread_mutex_destroy(mutex)) // TODO: mock
|
|
return -1;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int mutex_lock(Mutex *mutex)
|
|
{
|
|
#ifdef _WIN32
|
|
EnterCriticalSection(mutex); // TODO: mock?
|
|
return 0;
|
|
#else
|
|
if (pthread_mutex_lock(mutex)) // TODO: mock
|
|
return -1;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int mutex_unlock(Mutex *mutex)
|
|
{
|
|
#ifdef _WIN32
|
|
LeaveCriticalSection(mutex); // TODO: mock?
|
|
return 0;
|
|
#else
|
|
if (pthread_mutex_unlock(mutex)) // TODO: mock
|
|
return -1;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/byte_queue.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void byte_queue_init(ByteQueue *queue, uint32_t limit)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Deinitialize the queue
|
|
void byte_queue_free(ByteQueue *queue)
|
|
{
|
|
if (queue->read_target) {
|
|
if (queue->read_target != queue->data)
|
|
free(queue->read_target);
|
|
queue->read_target = NULL;
|
|
queue->read_target_size = 0;
|
|
}
|
|
|
|
free(queue->data);
|
|
queue->data = NULL;
|
|
}
|
|
|
|
int byte_queue_error(ByteQueue *queue)
|
|
{
|
|
return queue->flags & BYTE_QUEUE_ERROR;
|
|
}
|
|
|
|
int byte_queue_empty(ByteQueue *queue)
|
|
{
|
|
return queue->used == 0;
|
|
}
|
|
|
|
int byte_queue_full(ByteQueue *queue)
|
|
{
|
|
return queue->used == queue->limit;
|
|
}
|
|
|
|
ByteView byte_queue_read_buf(ByteQueue *queue)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return (ByteView) {NULL, 0};
|
|
|
|
assert((queue->flags & BYTE_QUEUE_READ) == 0);
|
|
queue->flags |= BYTE_QUEUE_READ;
|
|
queue->read_target = queue->data;
|
|
queue->read_target_size = queue->size;
|
|
|
|
if (queue->data == NULL)
|
|
return (ByteView) {NULL, 0};
|
|
|
|
return (ByteView) { queue->data + queue->head, queue->used };
|
|
}
|
|
|
|
void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return;
|
|
|
|
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
|
return;
|
|
|
|
queue->flags &= ~BYTE_QUEUE_READ;
|
|
|
|
assert((uint32_t) num <= queue->used);
|
|
queue->head += (uint32_t) num;
|
|
queue->used -= (uint32_t) num;
|
|
queue->curs += (uint32_t) num;
|
|
|
|
if (queue->read_target) {
|
|
if (queue->read_target != queue->data)
|
|
free(queue->read_target);
|
|
queue->read_target = NULL;
|
|
queue->read_target_size = 0;
|
|
}
|
|
}
|
|
|
|
ByteView byte_queue_write_buf(ByteQueue *queue)
|
|
{
|
|
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
|
return (ByteView) {NULL, 0};
|
|
|
|
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
|
queue->flags |= BYTE_QUEUE_WRITE;
|
|
|
|
return (ByteView) {
|
|
queue->data + (queue->head + queue->used),
|
|
queue->size - (queue->head + queue->used),
|
|
};
|
|
}
|
|
|
|
void byte_queue_write_ack(ByteQueue *queue, uint32_t num)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return;
|
|
|
|
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
|
return;
|
|
|
|
queue->flags &= ~BYTE_QUEUE_WRITE;
|
|
queue->used += num;
|
|
}
|
|
|
|
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t 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.
|
|
|
|
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
|
|
|
uint32_t total_free_space = queue->size - queue->used;
|
|
uint32_t free_space_after_data = queue->size - queue->used - queue->head;
|
|
|
|
int moved = 0;
|
|
if (free_space_after_data < mincap) {
|
|
|
|
if (total_free_space < mincap || (queue->read_target == queue->data)) {
|
|
// Resize required
|
|
|
|
if (queue->used + mincap > queue->limit) {
|
|
queue->flags |= BYTE_QUEUE_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
uint32_t size;
|
|
if (queue->size > UINT32_MAX / 2)
|
|
size = UINT32_MAX;
|
|
else
|
|
size = 2 * queue->size;
|
|
|
|
if (size < queue->used + mincap)
|
|
size = queue->used + mincap;
|
|
|
|
if (size > queue->limit)
|
|
size = queue->limit;
|
|
|
|
uint8_t *data = malloc(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)
|
|
free(queue->data);
|
|
|
|
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;
|
|
}
|
|
|
|
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len)
|
|
{
|
|
byte_queue_write_setmincap(queue, len);
|
|
ByteView dst = byte_queue_write_buf(queue);
|
|
if (dst.ptr) {
|
|
memcpy(dst.ptr, ptr, len);
|
|
byte_queue_write_ack(queue, len);
|
|
}
|
|
}
|
|
|
|
ByteQueueOffset byte_queue_offset(ByteQueue *queue)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return (ByteQueueOffset) { 0 };
|
|
return (ByteQueueOffset) { queue->curs + queue->used };
|
|
}
|
|
|
|
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
|
|
void *src, uint32_t len)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return;
|
|
|
|
// Check that the offset is in range
|
|
assert(off >= queue->curs && off - queue->curs < queue->used);
|
|
|
|
// Check that the length is in range
|
|
assert(len <= queue->used - (off - queue->curs));
|
|
|
|
// Perform the patch
|
|
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
|
|
memcpy(dst, src, len);
|
|
}
|
|
|
|
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off)
|
|
{
|
|
return queue->curs + queue->used - off;
|
|
}
|
|
|
|
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset)
|
|
{
|
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
return;
|
|
|
|
uint64_t num = (queue->curs + queue->used) - offset;
|
|
assert(num <= queue->used);
|
|
|
|
queue->used -= num;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/cert.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
|
|
static EVP_PKEY *generate_rsa_key_pair(int key_bits)
|
|
{
|
|
EVP_PKEY_CTX *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;
|
|
}
|
|
|
|
EVP_PKEY *pkey = 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, HTTP_String file)
|
|
{
|
|
char copy[1<<10];
|
|
if (file.len >= (int) sizeof(copy))
|
|
return -1;
|
|
memcpy(copy, file.ptr, file.len);
|
|
copy[file.len] = '\0';
|
|
|
|
FILE *fp = fopen(copy, "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 save_certificate(X509 *x509, HTTP_String file)
|
|
{
|
|
char copy[1<<10];
|
|
if (file.len >= (int) sizeof(copy))
|
|
return -1;
|
|
memcpy(copy, file.ptr, file.len);
|
|
copy[file.len] = '\0';
|
|
|
|
FILE *fp = fopen(copy, "wb");
|
|
if (!fp)
|
|
return -1;
|
|
|
|
// Write certificate in PEM format
|
|
if (!PEM_write_X509(fp, x509)) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
fclose(fp);
|
|
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;
|
|
}
|
|
|
|
#else
|
|
|
|
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
|
HTTP_String cert_file, HTTP_String key_file)
|
|
{
|
|
(void) C;
|
|
(void) O;
|
|
(void) CN;
|
|
(void) cert_file;
|
|
(void) key_file;
|
|
return -1;
|
|
}
|
|
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/client.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int http_client_init(HTTP_Client *client)
|
|
{
|
|
client->num_conns = 0;
|
|
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
|
|
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
|
|
|
|
client->num_ready = 0;
|
|
client->ready_head = 0;
|
|
|
|
if (socket_manager_init(&client->sockets,
|
|
client->socket_pool, HTTP_CLIENT_CAPACITY) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
void http_client_free(HTTP_Client *client)
|
|
{
|
|
socket_manager_free(&client->sockets);
|
|
|
|
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
|
HTTP_ClientConn *conn = &client->conns[i];
|
|
if (conn->state == HTTP_CLIENT_CONN_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
http_client_conn_free(conn);
|
|
}
|
|
}
|
|
|
|
int http_client_wakeup(HTTP_Client *client)
|
|
{
|
|
if (socket_manager_wakeup(&client->sockets) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void http_request_builder_line(HTTP_RequestBuilder builder, xxxx)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void http_request_builder_header(HTTP_RequestBuilder builder, String str)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void http_request_builder_body(HTTP_RequestBuilder builder, String str)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int http_request_builder_send(HTTP_RequestBuilder builder)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
int http_client_register_events(HTTP_Client *client,
|
|
struct pollfd *polled, int max_polled)
|
|
{
|
|
return socket_manager_register_events(
|
|
&client->sockets, polled, max_polled);
|
|
}
|
|
|
|
int http_client_process_events(HTTP_Client *client,
|
|
struct pollfd *polled, int num_polled)
|
|
{
|
|
SocketEvent events[HTTP_CLIENT_CAPACITY];
|
|
int num_events = socket_manager_translate_events(
|
|
&client->sockets, polled, num_polled);
|
|
|
|
for (int i = 0; i < num_events; i++) {
|
|
|
|
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
|
|
|
// TODO
|
|
|
|
} else if (events[i].type == SOCKET_EVENT_READY) {
|
|
|
|
// TODO
|
|
}
|
|
|
|
// TODO
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool http_client_next_response(HTTP_Client *client,
|
|
HTTP_Response **response);
|
|
{
|
|
if (server->num_ready == 0)
|
|
return false;
|
|
|
|
HTTP_ClientConn *conn = &client->conns[client->ready_head];
|
|
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
|
|
client->num_ready--;
|
|
|
|
assert(conn->state == HTTP_CLIENT_CONN_COMPLETE);
|
|
*response = &conn->response;
|
|
return true;
|
|
}
|
|
|
|
void http_free_response(HTTP_Response *res)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/parse.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// From RFC 9112
|
|
// request-target = origin-form
|
|
// / absolute-form
|
|
// / authority-form
|
|
// / asterisk-form
|
|
// origin-form = absolute-path [ "?" query ]
|
|
// absolute-form = absolute-URI
|
|
// authority-form = uri-host ":" port
|
|
// asterisk-form = "*"
|
|
//
|
|
// From RFC 9110
|
|
// URI-reference = <URI-reference, see [URI], Section 4.1>
|
|
// absolute-URI = <absolute-URI, see [URI], Section 4.3>
|
|
// relative-part = <relative-part, see [URI], Section 4.2>
|
|
// authority = <authority, see [URI], Section 3.2>
|
|
// uri-host = <host, see [URI], Section 3.2.2>
|
|
// port = <port, see [URI], Section 3.2.3>
|
|
// path-abempty = <path-abempty, see [URI], Section 3.3>
|
|
// segment = <segment, see [URI], Section 3.3>
|
|
// query = <query, see [URI], Section 3.4>
|
|
//
|
|
// absolute-path = 1*( "/" segment )
|
|
// partial-URI = relative-part [ "?" query ]
|
|
//
|
|
// From RFC 3986:
|
|
// segment = *pchar
|
|
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
// pct-encoded = "%" HEXDIG HEXDIG
|
|
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
// / "*" / "+" / "," / ";" / "="
|
|
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
// query = *( pchar / "/" / "?" )
|
|
// absolute-URI = scheme ":" hier-part [ "?" query ]
|
|
// hier-part = "//" authority path-abempty
|
|
// / path-absolute
|
|
// / path-rootless
|
|
// / path-empty
|
|
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
|
|
typedef struct {
|
|
char *src;
|
|
int len;
|
|
int cur;
|
|
} Scanner;
|
|
|
|
static int is_digit(char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
static int is_alpha(char c)
|
|
{
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
}
|
|
|
|
static int is_hex_digit(char c)
|
|
{
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
}
|
|
|
|
// From RFC 3986:
|
|
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
// / "*" / "+" / "," / ";" / "="
|
|
static int is_sub_delim(char c)
|
|
{
|
|
return c == '!' || c == '$' || c == '&' || c == '\''
|
|
|| c == '(' || c == ')' || c == '*' || c == '+'
|
|
|| c == ',' || c == ';' || c == '=';
|
|
}
|
|
|
|
// From RFC 3986:
|
|
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
static int is_unreserved(char c)
|
|
{
|
|
return is_alpha(c) || is_digit(c)
|
|
|| c == '-' || c == '.'
|
|
|| c == '_' || c == '~';
|
|
}
|
|
|
|
// From RFC 3986:
|
|
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
static int is_pchar(char c)
|
|
{
|
|
return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@';
|
|
}
|
|
|
|
static int is_tchar(char c)
|
|
{
|
|
return is_digit(c) || is_alpha(c)
|
|
|| c == '!' || c == '#' || c == '$'
|
|
|| c == '%' || c == '&' || c == '\''
|
|
|| c == '*' || c == '+' || c == '-'
|
|
|| c == '.' || c == '^' || c == '_'
|
|
|| c == '~';
|
|
}
|
|
|
|
static int is_vchar(char c)
|
|
{
|
|
return c >= ' ' && c <= '~';
|
|
}
|
|
|
|
#define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \
|
|
while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \
|
|
(scanner)->cur++;
|
|
|
|
static int
|
|
consume_absolute_path(Scanner *s)
|
|
{
|
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
|
return -1; // ERROR
|
|
s->cur++;
|
|
|
|
for (;;) {
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
|
break;
|
|
s->cur++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// If abempty=1:
|
|
// path-abempty = *( "/" segment )
|
|
// else:
|
|
// path-absolute = "/" [ segment-nz *( "/" segment ) ]
|
|
// path-rootless = segment-nz *( "/" segment )
|
|
// path-empty = 0<pchar>
|
|
static int parse_path(Scanner *s, HTTP_String *path, int abempty)
|
|
{
|
|
int start = s->cur;
|
|
|
|
if (abempty) {
|
|
|
|
// path-abempty
|
|
while (s->cur < s->len && s->src[s->cur] == '/') {
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_pchar(s->src[s->cur]));
|
|
}
|
|
|
|
} else if (s->cur < s->len && (s->src[s->cur] == '/')) {
|
|
|
|
// path-absolute
|
|
s->cur++;
|
|
if (s->cur < s->len && is_pchar(s->src[s->cur])) {
|
|
s->cur++;
|
|
for (;;) {
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
|
break;
|
|
s->cur++;
|
|
}
|
|
}
|
|
|
|
} else if (s->cur < s->len && is_pchar(s->src[s->cur])) {
|
|
|
|
// path-rootless
|
|
s->cur++;
|
|
for (;;) {
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar)
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
|
break;
|
|
s->cur++;
|
|
}
|
|
|
|
} else {
|
|
// path->empty
|
|
// (do nothing)
|
|
}
|
|
|
|
*path = (HTTP_String) {
|
|
s->src + start,
|
|
s->cur - start,
|
|
};
|
|
if (path->len == 0)
|
|
path->ptr = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// RFC 3986:
|
|
// query = *( pchar / "/" / "?" )
|
|
static int is_query(char c)
|
|
{
|
|
return is_pchar(c) || c == '/' || c == '?';
|
|
}
|
|
|
|
// RFC 3986:
|
|
// fragment = *( pchar / "/" / "?" )
|
|
static int is_fragment(char c)
|
|
{
|
|
return is_pchar(c) || c == '/' || c == '?';
|
|
}
|
|
|
|
static int little_endian(void)
|
|
{
|
|
uint16_t x = 1;
|
|
return *((uint8_t*) &x);
|
|
}
|
|
|
|
static void invert_bytes(void *p, int len)
|
|
{
|
|
char *c = p;
|
|
for (int i = 0; i < len/2; i++) {
|
|
char tmp = c[i];
|
|
c[i] = c[len-i-1];
|
|
c[len-i-1] = tmp;
|
|
}
|
|
}
|
|
|
|
static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4)
|
|
{
|
|
unsigned int out = 0;
|
|
int i = 0;
|
|
for (;;) {
|
|
|
|
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|
return -1;
|
|
|
|
int b = 0;
|
|
do {
|
|
int x = s->src[s->cur++] - '0';
|
|
if (b > (UINT8_MAX - x) / 10)
|
|
return -1;
|
|
b = b * 10 + x;
|
|
} while (s->cur < s->len && is_digit(s->src[s->cur]));
|
|
|
|
out <<= 8;
|
|
out |= (unsigned char) b;
|
|
|
|
i++;
|
|
if (i == 4)
|
|
break;
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != '.')
|
|
return -1;
|
|
s->cur++;
|
|
}
|
|
|
|
if (little_endian())
|
|
invert_bytes(&out, 4);
|
|
|
|
ipv4->data = out;
|
|
return 0;
|
|
}
|
|
|
|
static int hex_digit_to_int(char c)
|
|
{
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
return -1;
|
|
}
|
|
|
|
static int parse_ipv6_comp(Scanner *s)
|
|
{
|
|
unsigned short buf;
|
|
|
|
if (s->cur == s->len || !is_hex_digit(s->src[s->cur]))
|
|
return -1;
|
|
buf = hex_digit_to_int(s->src[s->cur]);
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len || !is_hex_digit(s->src[s->cur]))
|
|
return buf;
|
|
buf <<= 4;
|
|
buf |= hex_digit_to_int(s->src[s->cur]);
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len || !is_hex_digit(s->src[s->cur]))
|
|
return buf;
|
|
buf <<= 4;
|
|
buf |= hex_digit_to_int(s->src[s->cur]);
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len || !is_hex_digit(s->src[s->cur]))
|
|
return buf;
|
|
buf <<= 4;
|
|
buf |= hex_digit_to_int(s->src[s->cur]);
|
|
s->cur++;
|
|
|
|
return (int) buf;
|
|
}
|
|
|
|
static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6)
|
|
{
|
|
unsigned short head[8];
|
|
unsigned short tail[8];
|
|
int head_len = 0;
|
|
int tail_len = 0;
|
|
|
|
if (s->len - s->cur > 1
|
|
&& s->src[s->cur+0] == ':'
|
|
&& s->src[s->cur+1] == ':')
|
|
s->cur += 2;
|
|
else {
|
|
|
|
for (;;) {
|
|
|
|
int ret = parse_ipv6_comp(s);
|
|
if (ret < 0) return ret;
|
|
|
|
head[head_len++] = (unsigned short) ret;
|
|
if (head_len == 8) break;
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
|
return -1;
|
|
s->cur++;
|
|
|
|
if (s->cur < s->len && s->src[s->cur] == ':') {
|
|
s->cur++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (head_len < 8) {
|
|
while (s->cur < s->len && is_hex_digit(s->src[s->cur])) {
|
|
|
|
int ret = parse_ipv6_comp(s);
|
|
if (ret < 0) return ret;
|
|
|
|
tail[tail_len++] = (unsigned short) ret;
|
|
if (head_len + tail_len == 8) break;
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
|
break;
|
|
s->cur++;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < head_len; i++)
|
|
ipv6->data[i] = head[i];
|
|
|
|
for (int i = 0; i < 8 - head_len - tail_len; i++)
|
|
ipv6->data[head_len + i] = 0;
|
|
|
|
for (int i = 0; i < tail_len; i++)
|
|
ipv6->data[8 - tail_len + i] = tail[i];
|
|
|
|
if (little_endian())
|
|
for (int i = 0; i < 8; i++)
|
|
invert_bytes(&ipv6->data[i], 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// From RFC 3986:
|
|
// reg-name = *( unreserved / pct-encoded / sub-delims )
|
|
static int is_regname(char c)
|
|
{
|
|
return is_unreserved(c) || is_sub_delim(c);
|
|
}
|
|
|
|
static int parse_regname(Scanner *s, HTTP_String *regname)
|
|
{
|
|
if (s->cur == s->len || !is_regname(s->src[s->cur]))
|
|
return -1;
|
|
int start = s->cur;
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_regname(s->src[s->cur]));
|
|
regname->ptr = s->src + start;
|
|
regname->len = s->cur - start;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_host(Scanner *s, HTTP_Host *host)
|
|
{
|
|
int ret;
|
|
if (s->cur < s->len && s->src[s->cur] == '[') {
|
|
|
|
s->cur++;
|
|
|
|
int start = s->cur;
|
|
HTTP_IPv6 ipv6;
|
|
ret = parse_ipv6(s, &ipv6);
|
|
if (ret < 0) return ret;
|
|
|
|
host->mode = HTTP_HOST_MODE_IPV6;
|
|
host->ipv6 = ipv6;
|
|
host->text = (HTTP_String) { s->src + start, s->cur - start };
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ']')
|
|
return -1;
|
|
s->cur++;
|
|
|
|
} else {
|
|
|
|
int start = s->cur;
|
|
HTTP_IPv4 ipv4;
|
|
ret = parse_ipv4(s, &ipv4);
|
|
if (ret >= 0) {
|
|
host->mode = HTTP_HOST_MODE_IPV4;
|
|
host->ipv4 = ipv4;
|
|
} else {
|
|
s->cur = start;
|
|
|
|
HTTP_String regname;
|
|
ret = parse_regname(s, ®name);
|
|
if (ret < 0) return ret;
|
|
|
|
host->mode = HTTP_HOST_MODE_NAME;
|
|
host->name = regname;
|
|
}
|
|
host->text = (HTTP_String) { s->src + start, s->cur - start };
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
static int is_scheme_head(char c)
|
|
{
|
|
return is_alpha(c);
|
|
}
|
|
|
|
static int is_scheme_body(char c)
|
|
{
|
|
return is_alpha(c)
|
|
|| is_digit(c)
|
|
|| c == '+'
|
|
|| c == '-'
|
|
|| c == '.';
|
|
}
|
|
|
|
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
|
static int is_userinfo(char c)
|
|
{
|
|
return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded
|
|
}
|
|
|
|
// authority = [ userinfo "@" ] host [ ":" port ]
|
|
static int parse_authority(Scanner *s, HTTP_Authority *authority)
|
|
{
|
|
HTTP_String userinfo;
|
|
{
|
|
int start = s->cur;
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo);
|
|
|
|
if (s->cur < s->len && s->src[s->cur] == '@') {
|
|
userinfo = (HTTP_String) {
|
|
s->src + start,
|
|
s->cur - start
|
|
};
|
|
s->cur++;
|
|
} else {
|
|
// Rollback
|
|
s->cur = start;
|
|
userinfo = (HTTP_String) {NULL, 0};
|
|
}
|
|
}
|
|
|
|
HTTP_Host host;
|
|
{
|
|
int ret = parse_host(s, &host);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
int port = 0;
|
|
if (s->cur < s->len && s->src[s->cur] == ':') {
|
|
s->cur++;
|
|
if (s->cur < s->len && is_digit(s->src[s->cur])) {
|
|
port = s->src[s->cur++] - '0';
|
|
while (s->cur < s->len && is_digit(s->src[s->cur])) {
|
|
int x = s->src[s->cur++] - '0';
|
|
if (port > (UINT16_MAX - x) / 10)
|
|
return -1; // ERROR: Port too big
|
|
port = port * 10 + x;
|
|
}
|
|
}
|
|
}
|
|
|
|
authority->userinfo = userinfo;
|
|
authority->host = host;
|
|
authority->port = port;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
|
|
{
|
|
HTTP_String scheme = {0};
|
|
{
|
|
int start = s->cur;
|
|
if (s->cur == s->len || !is_scheme_head(s->src[s->cur]))
|
|
return -1; // ERROR: Missing scheme
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_scheme_body(s->src[s->cur]));
|
|
scheme = (HTTP_String) {
|
|
s->src + start,
|
|
s->cur - start,
|
|
};
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
|
return -1; // ERROR: Missing ':' after scheme
|
|
s->cur++;
|
|
}
|
|
|
|
int abempty = 0;
|
|
HTTP_Authority authority = {0};
|
|
if (s->len - s->cur > 1
|
|
&& s->src[s->cur+0] == '/'
|
|
&& s->src[s->cur+1] == '/') {
|
|
|
|
s->cur += 2;
|
|
|
|
int ret = parse_authority(s, &authority);
|
|
if (ret < 0) return ret;
|
|
|
|
abempty = 1;
|
|
}
|
|
|
|
HTTP_String path;
|
|
int ret = parse_path(s, &path, abempty);
|
|
if (ret < 0) return ret;
|
|
|
|
HTTP_String query = {0};
|
|
if (s->cur < s->len && s->src[s->cur] == '?') {
|
|
int start = s->cur;
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_query(s->src[s->cur]));
|
|
query = (HTTP_String) {
|
|
s->src + start,
|
|
s->cur - start,
|
|
};
|
|
}
|
|
|
|
HTTP_String fragment = {0};
|
|
if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') {
|
|
int start = s->cur;
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_fragment(s->src[s->cur]));
|
|
fragment = (HTTP_String) {
|
|
s->src + start,
|
|
s->cur - start,
|
|
};
|
|
}
|
|
|
|
url->scheme = scheme;
|
|
url->authority = authority;
|
|
url->path = path;
|
|
url->query = query;
|
|
url->fragment = fragment;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// authority-form = host ":" port
|
|
// host = IP-literal / IPv4address / reg-name
|
|
// IP-literal = "[" ( IPv6address / IPvFuture ) "]"
|
|
// reg-name = *( unreserved / pct-encoded / sub-delims )
|
|
static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
|
|
{
|
|
int ret;
|
|
|
|
ret = parse_host(s, host);
|
|
if (ret < 0) return ret;
|
|
|
|
// Default port value
|
|
*port = 0;
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
|
return 0; // No port
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|
return 0; // No port
|
|
|
|
int buf = 0;
|
|
do {
|
|
int x = s->src[s->cur++] - '0';
|
|
if (buf > (UINT16_MAX - x) / 10)
|
|
return -1; // ERROR
|
|
buf = buf * 10 + x;
|
|
} while (s->cur < s->len && is_digit(s->src[s->cur]));
|
|
|
|
*port = buf;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query)
|
|
{
|
|
int ret, start;
|
|
|
|
start = s->cur;
|
|
ret = consume_absolute_path(s);
|
|
if (ret < 0) return ret;
|
|
*path = (HTTP_String) { s->src + start, s->cur - start };
|
|
|
|
if (s->cur < s->len && s->src[s->cur] == '?') {
|
|
start = s->cur;
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_query(s->src[s->cur]));
|
|
*query = (HTTP_String) { s->src + start, s->cur - start };
|
|
} else
|
|
*query = (HTTP_String) { NULL, 0 };
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_asterisk_form(Scanner *s)
|
|
{
|
|
if (s->len - s->cur < 2
|
|
|| s->src[s->cur+0] != '*'
|
|
|| s->src[s->cur+1] != ' ')
|
|
return -1;
|
|
s->cur++;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_request_target(Scanner *s, HTTP_URL *url)
|
|
{
|
|
int ret;
|
|
|
|
memset(url, 0, sizeof(HTTP_URL));
|
|
|
|
// asterisk-form
|
|
ret = parse_asterisk_form(s);
|
|
if (ret >= 0) return ret;
|
|
|
|
ret = parse_uri(s, url, 0);
|
|
if (ret >= 0) return ret;
|
|
|
|
ret = parse_authority_form(s, &url->authority.host, &url->authority.port);
|
|
if (ret >= 0) return ret;
|
|
|
|
ret = parse_origin_form(s, &url->path, &url->query);
|
|
if (ret >= 0) return ret;
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool consume_str(Scanner *scan, HTTP_String token)
|
|
{
|
|
assert(token.len > 0);
|
|
|
|
if (token.len > scan->len - scan->cur)
|
|
return false;
|
|
|
|
for (int i = 0; i < token.len; i++)
|
|
if (scan->src[scan->cur + i] != token.ptr[i])
|
|
return false;
|
|
|
|
scan->cur += token.len;
|
|
return true;
|
|
}
|
|
|
|
static int is_header_body(char c)
|
|
{
|
|
return is_vchar(c) || c == ' ' || c == '\t';
|
|
}
|
|
|
|
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
|
{
|
|
int num_headers = 0;
|
|
while (!consume_str(s, HTTP_STR("\r\n"))) {
|
|
|
|
// RFC 9112:
|
|
// field-line = field-name ":" OWS field-value OWS
|
|
//
|
|
// RFC 9110:
|
|
// field-value = *field-content
|
|
// field-content = field-vchar
|
|
// [ 1*( SP / HTAB / field-vchar ) field-vchar ]
|
|
// field-vchar = VCHAR / obs-text
|
|
// obs-text = %x80-FF
|
|
|
|
int start;
|
|
|
|
if (s->cur == s->len || !is_tchar(s->src[s->cur]))
|
|
return -1; // ERROR
|
|
start = s->cur;
|
|
do
|
|
s->cur++;
|
|
while (s->cur < s->len && is_tchar(s->src[s->cur]));
|
|
HTTP_String name = { s->src + start, s->cur - start };
|
|
|
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
|
return -1; // ERROR
|
|
s->cur++;
|
|
|
|
start = s->cur;
|
|
CONSUME_OPTIONAL_SEQUENCE(s, is_header_body);
|
|
HTTP_String body = { s->src + start, s->cur - start };
|
|
body = http_trim(body);
|
|
|
|
if (num_headers < max_headers)
|
|
headers[num_headers++] = (HTTP_Header) { name, body };
|
|
|
|
if (!consume_str(s, HTTP_STR("\r\n"))) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return num_headers;
|
|
}
|
|
|
|
typedef enum {
|
|
TRANSFER_ENCODING_OPTION_CHUNKED,
|
|
TRANSFER_ENCODING_OPTION_COMPRESS,
|
|
TRANSFER_ENCODING_OPTION_DEFLATE,
|
|
TRANSFER_ENCODING_OPTION_GZIP,
|
|
} TransferEncodingOption;
|
|
|
|
static bool is_space(char c)
|
|
{
|
|
return c == ' ' || c == '\t';
|
|
}
|
|
|
|
static int
|
|
parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
|
|
{
|
|
Scanner s = { src.ptr, src.len, 0 };
|
|
|
|
int num = 0;
|
|
for (;;) {
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
|
|
|
TransferEncodingOption opt;
|
|
if (0) {}
|
|
else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED;
|
|
else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS;
|
|
else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE;
|
|
else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP;
|
|
else return -1; // Invalid option
|
|
|
|
if (num == max)
|
|
return -1; // Too many options
|
|
dst[num++] = opt;
|
|
|
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
|
|
|
if (s.cur == s.len)
|
|
break;
|
|
|
|
if (s.src[s.cur] != ',')
|
|
return -1; // Missing comma separator
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static int
|
|
parse_content_length(const char *src, int len, uint64_t *out)
|
|
{
|
|
int cur = 0;
|
|
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
|
|
cur++;
|
|
|
|
if (cur == len || !is_digit(src[cur]))
|
|
return -1;
|
|
|
|
uint64_t buf = 0;
|
|
do {
|
|
int d = src[cur++] - '0';
|
|
if (buf > (UINT64_MAX - d) / 10)
|
|
return -1;
|
|
buf = buf * 10 + d;
|
|
} while (cur < len && is_digit(src[cur]));
|
|
|
|
*out = buf;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_body(Scanner *s,
|
|
HTTP_Header *headers, int num_headers,
|
|
HTTP_String *body, bool body_expected)
|
|
{
|
|
|
|
// RFC 9112 section 6:
|
|
// The presence of a message body in a request is signaled by a Content-Length or
|
|
// Transfer-Encoding header field. Request message framing is independent of method
|
|
// semantics.
|
|
|
|
int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding"));
|
|
if (header_index != -1) {
|
|
|
|
// RFC 9112 section 6.1:
|
|
// A server MAY reject a request that contains both Content-Length and Transfer-Encoding
|
|
// or process such a request in accordance with the Transfer-Encoding alone. Regardless,
|
|
// the server MUST close the connection after responding to such a request to avoid the
|
|
// potential attacks.
|
|
if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1)
|
|
return -1;
|
|
|
|
HTTP_String value = headers[header_index].value;
|
|
|
|
// RFC 9112 section 6.1:
|
|
// If any transfer coding other than chunked is applied to a request's content, the
|
|
// sender MUST apply chunked as the final transfer coding to ensure that the message
|
|
// is properly framed. If any transfer coding other than chunked is applied to a
|
|
// response's content, the sender MUST either apply chunked as the final transfer
|
|
// coding or terminate the message by closing the connection.
|
|
|
|
TransferEncodingOption opts[8];
|
|
int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts));
|
|
if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED)
|
|
return -1;
|
|
|
|
HTTP_String chunks_maybe[128];
|
|
HTTP_String *chunks = chunks_maybe;
|
|
int num_chunks = 0;
|
|
int max_chunks = HTTP_COUNT(chunks_maybe);
|
|
|
|
#define FREE_CHUNK_LIST \
|
|
if (chunks != chunks_maybe) \
|
|
free(chunks);
|
|
|
|
char *content_start = s->src + s->cur;
|
|
|
|
for (;;) {
|
|
|
|
// RFC 9112 section 7.1:
|
|
// The chunked transfer coding wraps content in order to transfer it as a series of chunks,
|
|
// each with its own size indicator, followed by an OPTIONAL trailer section containing
|
|
// trailer fields.
|
|
|
|
if (s->cur == s->len) {
|
|
FREE_CHUNK_LIST
|
|
return 0; // Incomplete request
|
|
}
|
|
|
|
if (!is_hex_digit(s->src[s->cur])) {
|
|
FREE_CHUNK_LIST
|
|
return -1;
|
|
}
|
|
|
|
int chunk_len = 0;
|
|
|
|
do {
|
|
char c = s->src[s->cur++];
|
|
int n = hex_digit_to_int(c);
|
|
if (chunk_len > (INT_MAX - n) / 16) {
|
|
FREE_CHUNK_LIST
|
|
return -1; // overflow
|
|
}
|
|
chunk_len = chunk_len * 16 + n;
|
|
} while (s->cur < s->len && is_hex_digit(s->src[s->cur]));
|
|
|
|
if (s->cur == s->len) {
|
|
FREE_CHUNK_LIST
|
|
return 0; // Incomplete request
|
|
}
|
|
if (s->src[s->cur] != '\r') {
|
|
FREE_CHUNK_LIST
|
|
return -1;
|
|
}
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len) {
|
|
FREE_CHUNK_LIST
|
|
return 0;
|
|
}
|
|
if (s->src[s->cur] != '\n') {
|
|
FREE_CHUNK_LIST
|
|
return -1;
|
|
}
|
|
s->cur++;
|
|
|
|
char *chunk_ptr = s->src + s->cur;
|
|
|
|
if (chunk_len > s->len - s->cur) {
|
|
FREE_CHUNK_LIST
|
|
return 0; // Incomplete request
|
|
}
|
|
s->cur += chunk_len;
|
|
|
|
if (s->cur == s->len)
|
|
return 0; // Incomplete request
|
|
if (s->src[s->cur] != '\r') {
|
|
FREE_CHUNK_LIST
|
|
return -1;
|
|
}
|
|
s->cur++;
|
|
|
|
if (s->cur == s->len) {
|
|
FREE_CHUNK_LIST
|
|
return 0; // Incomplete request
|
|
}
|
|
if (s->src[s->cur] != '\n') {
|
|
FREE_CHUNK_LIST
|
|
return -1;
|
|
}
|
|
s->cur++;
|
|
|
|
if (chunk_len == 0)
|
|
break;
|
|
|
|
if (num_chunks == max_chunks) {
|
|
|
|
max_chunks *= 2;
|
|
|
|
HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String));
|
|
if (new_chunks == NULL) {
|
|
if (chunks != chunks_maybe)
|
|
free(chunks);
|
|
return -1;
|
|
}
|
|
|
|
for (int i = 0; i < num_chunks; i++)
|
|
new_chunks[i] = chunks[i];
|
|
|
|
if (chunks != chunks_maybe)
|
|
free(chunks);
|
|
|
|
chunks = new_chunks;
|
|
}
|
|
chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len };
|
|
}
|
|
|
|
char *content_ptr = content_start;
|
|
for (int i = 0; i < num_chunks; i++) {
|
|
memmove(content_ptr, chunks[i].ptr, chunks[i].len);
|
|
content_ptr += chunks[i].len;
|
|
}
|
|
|
|
*body = (HTTP_String) {
|
|
content_start,
|
|
content_ptr - content_start
|
|
};
|
|
|
|
if (chunks != chunks_maybe)
|
|
free(chunks);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// RFC 9112 section 6.3:
|
|
// If a valid Content-Length header field is present without Transfer-Encoding,
|
|
// its decimal value defines the expected message body length in octets.
|
|
|
|
header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length"));
|
|
if (header_index != -1) {
|
|
|
|
// Have Content-Length
|
|
HTTP_String value = headers[header_index].value;
|
|
|
|
uint64_t tmp;
|
|
if (parse_content_length(value.ptr, value.len, &tmp) < 0)
|
|
return -1;
|
|
if (tmp > INT_MAX)
|
|
return -1;
|
|
int len = (int) tmp;
|
|
|
|
if (len > s->len - s->cur)
|
|
return 0; // Incomplete request
|
|
|
|
*body = (HTTP_String) { s->src + s->cur, len };
|
|
|
|
s->cur += len;
|
|
return 1;
|
|
}
|
|
|
|
// No Content-Length or Transfer-Encoding
|
|
if (body_expected) return -1;
|
|
|
|
*body = (HTTP_String) { NULL, 0 };
|
|
return 1;
|
|
}
|
|
|
|
static int contains_head(char *src, int len)
|
|
{
|
|
int cur = 0;
|
|
while (len - cur > 3) {
|
|
if (src[cur+0] == '\r' &&
|
|
src[cur+1] == '\n' &&
|
|
src[cur+2] == '\r' &&
|
|
src[cur+3] == '\n')
|
|
return 1;
|
|
cur++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_request(Scanner *s, HTTP_Request *req)
|
|
{
|
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
|
return 0;
|
|
|
|
req->secure = false;
|
|
|
|
if (0) {}
|
|
else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET;
|
|
else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST;
|
|
else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT;
|
|
else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD;
|
|
else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE;
|
|
else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT;
|
|
else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS;
|
|
else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE;
|
|
else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH;
|
|
else return -1;
|
|
|
|
{
|
|
Scanner s2 = *s;
|
|
int peek = s->cur;
|
|
while (peek < s->len && s->src[peek] != ' ')
|
|
peek++;
|
|
if (peek == s->len)
|
|
return -1;
|
|
s2.len = peek;
|
|
|
|
int ret = parse_request_target(&s2, &req->url);
|
|
if (ret < 0) return ret;
|
|
|
|
s->cur = s2.cur;
|
|
}
|
|
|
|
if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) {
|
|
req->minor = 1;
|
|
} else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) {
|
|
req->minor = 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
|
|
if (num_headers < 0)
|
|
return num_headers;
|
|
req->num_headers = num_headers;
|
|
|
|
bool body_expected = true;
|
|
if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods?
|
|
body_expected = false;
|
|
|
|
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
|
|
}
|
|
|
|
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
|
|
{
|
|
for (int i = 0; i < num_headers; i++)
|
|
if (http_streqcase(name, headers[i].name))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
static int parse_response(Scanner *s, HTTP_Response *res)
|
|
{
|
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
|
return 0;
|
|
|
|
if (consume_str(s, HTTP_STR("HTTP/1.1 "))) {
|
|
res->minor = 1;
|
|
} else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) {
|
|
res->minor = 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if (s->len - s->cur < 5
|
|
|| s->src[s->cur+0] != ' '
|
|
|| !is_digit(s->src[s->cur+1])
|
|
|| !is_digit(s->src[s->cur+2])
|
|
|| !is_digit(s->src[s->cur+3])
|
|
|| s->src[s->cur+4] != ' ')
|
|
return -1;
|
|
s->cur += 5;
|
|
|
|
res->status =
|
|
(s->src[s->cur-2] - '0') * 1 +
|
|
(s->src[s->cur-3] - '0') * 10 +
|
|
(s->src[s->cur-4] - '0') * 100;
|
|
|
|
while (s->cur < s->len && (
|
|
s->src[s->cur] == '\t' ||
|
|
s->src[s->cur] == ' ' ||
|
|
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
|
s->cur++;
|
|
|
|
if (s->len - s->cur < 2
|
|
|| s->src[s->cur+0] != '\r'
|
|
|| s->src[s->cur+1] != '\n')
|
|
return -1;
|
|
s->cur += 2;
|
|
|
|
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
|
|
if (num_headers < 0)
|
|
return num_headers;
|
|
res->num_headers = num_headers;
|
|
|
|
bool body_expected = true; // TODO
|
|
|
|
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
|
|
}
|
|
|
|
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
|
|
{
|
|
Scanner s = {src, len, 0};
|
|
int ret = parse_ipv4(&s, ipv4);
|
|
if (ret < 0) return ret;
|
|
return s.cur;
|
|
}
|
|
|
|
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
|
|
{
|
|
Scanner s = {src, len, 0};
|
|
int ret = parse_ipv6(&s, ipv6);
|
|
if (ret < 0) return ret;
|
|
return s.cur;
|
|
}
|
|
|
|
int http_parse_url(char *src, int len, HTTP_URL *url)
|
|
{
|
|
Scanner s = {src, len, 0};
|
|
int ret = parse_uri(&s, url, 1);
|
|
if (ret == 1)
|
|
return s.cur;
|
|
return ret;
|
|
}
|
|
|
|
int http_parse_request(char *src, int len, HTTP_Request *req)
|
|
{
|
|
Scanner s = {src, len, 0};
|
|
int ret = parse_request(&s, req);
|
|
if (ret == 1)
|
|
return s.cur;
|
|
return ret;
|
|
}
|
|
|
|
int http_parse_response(char *src, int len, HTTP_Response *res)
|
|
{
|
|
Scanner s = {src, len, 0};
|
|
int ret = parse_response(&s, res);
|
|
if (ret == 1)
|
|
return s.cur;
|
|
return ret;
|
|
}
|
|
|
|
HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
|
|
{
|
|
// TODO: best-effort implementation
|
|
|
|
for (int i = 0; i < req->num_headers; i++) {
|
|
|
|
if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie")))
|
|
continue;
|
|
|
|
char *src = req->headers[i].value.ptr;
|
|
int len = req->headers[i].value.len;
|
|
int cur = 0;
|
|
|
|
// Cookie: name1=value1; name2=value2; name3=value3
|
|
|
|
for (;;) {
|
|
|
|
while (cur < len && src[cur] == ' ')
|
|
cur++;
|
|
|
|
int off = cur;
|
|
while (cur < len && src[cur] != '=')
|
|
cur++;
|
|
|
|
HTTP_String cookie_name = { src + off, cur - off };
|
|
|
|
if (cur == len)
|
|
break;
|
|
cur++;
|
|
|
|
off = cur;
|
|
while (cur < len && src[cur] != ';')
|
|
cur++;
|
|
|
|
HTTP_String cookie_value = { src + off, cur - off };
|
|
|
|
if (http_streq(name, cookie_name))
|
|
return cookie_value;
|
|
|
|
if (cur == len)
|
|
break;
|
|
cur++;
|
|
}
|
|
}
|
|
|
|
return HTTP_STR("");
|
|
}
|
|
|
|
HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap)
|
|
{
|
|
// This is just a best-effort implementation
|
|
|
|
char *src = body.ptr;
|
|
int len = body.len;
|
|
int cur = 0;
|
|
|
|
if (cur < len && src[cur] == '?')
|
|
cur++;
|
|
|
|
while (cur < len) {
|
|
|
|
HTTP_String name;
|
|
{
|
|
int off = cur;
|
|
while (cur < len && src[cur] != '=' && src[cur] != '&')
|
|
cur++;
|
|
name = (HTTP_String) { src + off, cur - off };
|
|
}
|
|
|
|
HTTP_String body = HTTP_STR("");
|
|
if (cur < len) {
|
|
cur++;
|
|
if (src[cur-1] == '=') {
|
|
int off = cur;
|
|
while (cur < len && src[cur] != '&')
|
|
cur++;
|
|
body = (HTTP_String) { src + off, cur - off };
|
|
|
|
if (cur < len)
|
|
cur++;
|
|
}
|
|
}
|
|
|
|
if (http_streq(str, name)) {
|
|
|
|
bool percent_encoded = false;
|
|
for (int i = 0; i < body.len; i++)
|
|
if (body.ptr[i] == '+' || body.ptr[i] == '%') {
|
|
percent_encoded = true;
|
|
break;
|
|
}
|
|
|
|
if (!percent_encoded)
|
|
return body;
|
|
|
|
if (body.len > cap)
|
|
return (HTTP_String) { NULL, 0 };
|
|
|
|
HTTP_String decoded = { mem, 0 };
|
|
for (int i = 0; i < body.len; i++) {
|
|
|
|
char c = body.ptr[i];
|
|
if (c == '+')
|
|
c = ' ';
|
|
else {
|
|
if (body.ptr[i] == '%') {
|
|
if (body.len - i < 3
|
|
|| !is_hex_digit(body.ptr[i+1])
|
|
|| !is_hex_digit(body.ptr[i+2]))
|
|
return (HTTP_String) { NULL, 0 };
|
|
|
|
int h = hex_digit_to_int(body.ptr[i+1]);
|
|
int l = hex_digit_to_int(body.ptr[i+2]);
|
|
c = (h << 4) | l;
|
|
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
decoded.ptr[decoded.len++] = c;
|
|
}
|
|
|
|
return decoded;
|
|
}
|
|
}
|
|
|
|
return HTTP_STR("");
|
|
}
|
|
|
|
int http_get_param_i(HTTP_String body, HTTP_String str)
|
|
{
|
|
char buf[128];
|
|
HTTP_String out = http_get_param(body, str, buf, (int) sizeof(buf));
|
|
if (out.len == 0 || !is_digit(out.ptr[0]))
|
|
return -1;
|
|
|
|
int cur = 0;
|
|
int res = 0;
|
|
do {
|
|
int d = out.ptr[cur++] - '0';
|
|
if (res > (INT_MAX - d) / 10)
|
|
return -1;
|
|
res = res * 10 + d;
|
|
} while (cur < out.len && is_digit(out.ptr[cur]));
|
|
|
|
return res;
|
|
}
|
|
|
|
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
|
|
{
|
|
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
|
|
assert(idx != -1); // Requests without the host header are always rejected
|
|
|
|
char tmp[1<<8];
|
|
if (port > -1 && port != 80) {
|
|
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
|
|
assert(ret > 0);
|
|
domain = (HTTP_String) { tmp, ret };
|
|
}
|
|
|
|
HTTP_String host = req->headers[idx].value;
|
|
return http_streq(host, domain);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// src/server.c
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int http_server_init(HTTP_Server *server)
|
|
{
|
|
server->num_conns = 0;
|
|
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
|
|
server->conns[i].state = HTTP_SERVER_CONN_FREE;
|
|
|
|
server->num_ready = 0;
|
|
server->ready_head = 0;
|
|
|
|
if (socket_manager_init(&server->sockets,
|
|
&server->socket_pool, HTTP_SERVER_CAPACITY) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
void http_server_free(HTTP_Server *server)
|
|
{
|
|
socket_manager_free(&server->sockets);
|
|
|
|
for (int i = 0, j = 0; j < server->num_conns; i++) {
|
|
HTTP_ServerConn *conn = &server->conns[i];
|
|
if (conn->state != HTTP_SERVER_CONN_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
http_server_conn_free(conn);
|
|
}
|
|
}
|
|
|
|
int http_server_listen_tcp(HTTP_Server *server,
|
|
String addr, Port port)
|
|
{
|
|
if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int http_server_listen_tls(HTTP_Server *server,
|
|
String addr, Port port, String cert_file_name,
|
|
String key_file_name)
|
|
{
|
|
if (socket_manager_listen_tls(&server->sockets, addr,
|
|
port, cert_file_name, key_file_name) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int http_server_add_certificate(HTTP_Server *server,
|
|
String domain, String cert_file, String key_file)
|
|
{
|
|
if (socket_manager_add_certificate(&server->sockets,
|
|
domain, cert_file, key_file) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int http_server_wakeup(HTTP_Server *server)
|
|
{
|
|
if (socket_manager_wakeup(&server->sockets) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int http_server_register_events(HTTP_Server *server,
|
|
struct pollfd *polled, int max_polled)
|
|
{
|
|
return socket_manager_register_events(&server->sockets, polled, max_polled);
|
|
}
|
|
|
|
// Look at the head of the input buffer to see if
|
|
// a request was buffered. If it was, change the
|
|
// connection's status to WAIT_STATUS and push it
|
|
// to the ready queue. If the request is invalid,
|
|
// close the socket.
|
|
static void
|
|
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
|
|
{
|
|
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
|
|
|
|
ByteView src = byte_queue_read_buf(&conn->input);
|
|
int ret = http_parse_request(src.ptr, src.len, &conn->request);
|
|
if (ret < 0) {
|
|
|
|
// Invalid request
|
|
byte_queue_read_ack(&conn->input, 0);
|
|
socket_close(&server->sockets, conn->handle);
|
|
|
|
} else if (ret == 0) {
|
|
|
|
// Still waiting
|
|
byte_queue_read_ack(&conn->input, 0);
|
|
|
|
// If the queue reached its limit and we still didn't receive
|
|
// a complete request, abort the exchange.
|
|
if (byte_queue_full(&conn->input))
|
|
socket_close(&server->sockets, conn->handle);
|
|
|
|
} else {
|
|
|
|
// Ready
|
|
assert(ret == 1);
|
|
|
|
conn->state = HTTP_SERVER_CONN_STATUS;
|
|
conn->request_len = ret;
|
|
conn->response_offset = byte_queue_offset(&conn->output);
|
|
|
|
// Push to the ready queue
|
|
assert(server->num_ready < HTTP_SERVER_CAPACITY);
|
|
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
|
|
server->ready[tail] = conn - server->conns;
|
|
server->num_ready++;
|
|
}
|
|
}
|
|
|
|
bool http_server_next_request(HTTP_Server *server,
|
|
HTTP_Request **request, HTTP_ResponseBuilder *builder)
|
|
{
|
|
if (server->num_ready == 0)
|
|
return false;
|
|
|
|
HTTP_ServerConn *conn = &server->conns[server->ready_head];
|
|
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
|
|
server->num_ready--;
|
|
|
|
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
|
|
*request = &conn->request;
|
|
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
|
|
return true;
|
|
}
|
|
|
|
int http_server_process_events(HTTP_Server *server,
|
|
struct pollfd *polled, int num_polled)
|
|
{
|
|
SocketEvent events[HTTP_SERVER_CAPACITY];
|
|
int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled);
|
|
if (num_events < 0)
|
|
return -1;
|
|
|
|
for (int i = 0; i < num_events; i++) {
|
|
|
|
HTTP_ServerConn *conn = events[i].user;
|
|
|
|
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
|
|
|
http_server_conn_free(conn);
|
|
server->num_conns--;
|
|
|
|
} else if (events[i].type == SOCKET_EVENT_READY) {
|
|
|
|
if (events[i].user == NULL) {
|
|
|
|
if (server->num_conns == HTTP_SERVER_CAPACITY) {
|
|
socket_close(&server->sockets, events[i].handle);
|
|
continue;
|
|
}
|
|
|
|
int i = 0;
|
|
while (server->conns[i].state != HTTP_SERVER_CONN_FREE) {
|
|
i++;
|
|
assert(i < HTTP_SERVER_CAPACITY);
|
|
}
|
|
|
|
conn = &server->conns[i];
|
|
http_server_conn_init(conn, events[i].handle);
|
|
server->num_conns++;
|
|
|
|
socket_set_user(&server->sockets, events[i].handle, conn);
|
|
}
|
|
|
|
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
|
|
|
|
int min_recv = 1<<10;
|
|
byte_queue_write_setmincap(&conn->input, min_recv);
|
|
|
|
// Note that it's extra important that we don't
|
|
// buffer while the user is building the response.
|
|
// If we did that, a resize would invalidate all
|
|
// pointers on the parsed request structure.
|
|
int num = 0;
|
|
ByteView dst = byte_queue_write_buf(&conn->input);
|
|
if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
|
|
byte_queue_write_ack(&conn->input, num);
|
|
|
|
if (byte_queue_error(&conn->output))
|
|
socket_close(&server->sockets, conn->handle);
|
|
else
|
|
check_request_buffer(server, conn);
|
|
|
|
} else if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
|
|
|
|
int num = 0;
|
|
ByteView src = byte_queue_read_buf(&conn->output);
|
|
if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len);
|
|
byte_queue_read_ack(&conn->output, num);
|
|
|
|
if (byte_queue_error(&conn->output))
|
|
socket_close(&server->sockets, conn->handle);
|
|
else if (byte_queue_empty(&conn->output)) {
|
|
// We finished sending the response. Now we can
|
|
// either close the connection or process a new
|
|
// buffered request.
|
|
if (conn->closing) {
|
|
socket_close(&server->sockets, conn->handle);
|
|
} else {
|
|
check_request_buffer(server, conn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Get a connection pointer from a response builder.
|
|
// If the builder is invalid, returns NULL.
|
|
// Note that only connections in the responding states
|
|
// can be returned, as any builder is invalidated by
|
|
// incrementing the connection's generation counter
|
|
// when a response is completed.
|
|
static HTTP_ServerConn*
|
|
builder_to_conn(HTTP_ResponseBuilder builder)
|
|
{
|
|
HTTP_Server *server = builder.server;
|
|
if (server == NULL)
|
|
return NULL;
|
|
|
|
if (server->index > HTTP_SERVER_CAPACITY)
|
|
return NULL;
|
|
|
|
HTTP_ServerConn *conn = server->conns[server->index];
|
|
if (conn->gen != builder.gen)
|
|
return NULL;
|
|
|
|
return conn;
|
|
}
|
|
|
|
static void
|
|
write_status(HTTP_ServerConn *conn, int status)
|
|
{
|
|
byte_queue_write(&conn->output, xxx);
|
|
}
|
|
|
|
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
|
|
{
|
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
|
|
// Reset all response content and start from scrach.
|
|
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
|
|
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
|
|
}
|
|
|
|
write_status(conn, status);
|
|
|
|
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
|
}
|
|
|
|
void http_response_builder_header(HTTP_ResponseBuilder builder, String str)
|
|
{
|
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
|
|
return;
|
|
|
|
byte_queue_write(&conn->output, xxx);
|
|
}
|
|
|
|
static void append_special_headers(HTTP_ServerConn *conn)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
static void patch_special_headers(HTTP_ServerConn *conn)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void http_response_builder_body(HTTP_ResponseBuilder builder, String str)
|
|
{
|
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
|
|
append_special_headers(conn);
|
|
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
|
}
|
|
|
|
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
|
|
return;
|
|
|
|
byte_queue_write(&conn->output, str);
|
|
}
|
|
|
|
void http_response_builder_send(HTTP_ResponseBuilder builder, String str)
|
|
{
|
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
|
|
write_status(conn, 500);
|
|
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
|
}
|
|
|
|
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
|
|
append_special_headers(conn);
|
|
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
|
}
|
|
|
|
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
|
|
patch_special_headers(conn);
|
|
|
|
// Remove the buffered request
|
|
byte_queue_read_ack(&conn->input, conn->request_len);
|
|
|
|
conn->state = HTTP_SERVER_CONN_FLUSHING;
|
|
conn->gen++;
|
|
}
|