// 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 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 = // absolute-URI = // relative-part = // authority = // uri-host = // port = // path-abempty = // segment = // query = // // absolute-path = 1*( "/" segment ) // partial-URI = relative-part [ "?" query ] // // From RFC 3986: // segment = *pchar // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" // pct-encoded = "%" HEXDIG HEXDIG // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" // / "*" / "+" / "," / ";" / "=" // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" // query = *( pchar / "/" / "?" ) // absolute-URI = scheme ":" hier-part [ "?" query ] // hier-part = "//" authority path-abempty // / path-absolute // / path-rootless // / path-empty // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) typedef struct { char *src; int len; int cur; } Scanner; static int is_digit(char c) { return c >= '0' && c <= '9'; } static int is_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } static int is_hex_digit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } // From RFC 3986: // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" // / "*" / "+" / "," / ";" / "=" static int is_sub_delim(char c) { return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || c == '='; } // From RFC 3986: // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" static int is_unreserved(char c) { return is_alpha(c) || is_digit(c) || c == '-' || c == '.' || c == '_' || c == '~'; } // From RFC 3986: // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" static int is_pchar(char c) { return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'; } static int is_tchar(char c) { return is_digit(c) || is_alpha(c) || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '~'; } static int is_vchar(char c) { return c >= ' ' && c <= '~'; } #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 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++; }