QUOTE: Never too old to chase dreams.

feat: Switched to single header file & added error handling - libhttp - A basic HTTP Framework

libhttp

A basic HTTP Framework
git clone git://192.168.2.2/libhttp
Log | Files | Refs | README

commit 2e7bac568e85445338d346402027e5ec7edd3a94
parent 4f5681279a82ad33373e70bf3de55e12e08a4c81
Author: Sophie <sophie@aest.me>
Date:   Wed, 25 Sep 2024 10:41:40 +0200

feat: Switched to single header file & added error handling

Diffstat:
MMakefile | 15++-------------
MREADME.md | 32++++++++++++++++++++++++++++++++
Dlibhttp.c | 638-------------------------------------------------------------------------------
Dlibhttp.h | 141-------------------------------------------------------------------------------
Asrc/libhttp.h | 761+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 795 insertions(+), 792 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,18 +1,7 @@ -default: build - -build: - cc -o libhttp.so -shared -fPIC -lpthread -lssl -Wall -Wextra libhttp.c +default: install install: - cc -o libhttp.so -shared -fPIC libhttp.c - sudo cp libhttp.h /usr/local/include/ - sudo cp libhttp.so /usr/local/lib/ - sudo ldconfig - rm libhttp.so + sudo cp src/libhttp.h /usr/local/include/ uninstall: sudo rm /usr/local/include/libhttp.h - sudo rm /usr/local/lib/libhttp.so - -clean: - rm libhttp.so diff --git a/README.md b/README.md @@ -7,3 +7,35 @@ A basic HTTP Framework ``` sudo make install ``` + +## Usage + +```c +#define LIB_HTTP_IMPL +#include <libhttp.h> + +http_response_t do_handle(http_request_t *request) { + http_response_t response = http_response_create(HTTP_STATUS_OK); + http_header_set(&response, "Content-Type", "text/html"); + http_response_body(&response, "<h1>Hello, World!</h1>", 22); + return response; +} + +int main(void) { + http_t *http = http_init(); + http_on_request(http, do_handle); + if (http_bind(http, "0.0.0.0", 80, NULL, 0) != HTTP_ERR_OK) { + printf("failed to bind port :80\n"); + http_cleanup(http); + return 1; + } + if (http_listen(http) == HTTP_ERR_OK) { + printf("server is running on port :80\n"); + pause(); + } + http_close(http); + printf("server stopped\n"); + http_cleanup(http); + return 0; +} +``` diff --git a/libhttp.c b/libhttp.c @@ -1,638 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <stdbool.h> -#include <string.h> -#include <time.h> -#include <unistd.h> -#include <pthread.h> -#include <netdb.h> -#include <errno.h> -#include <poll.h> -#include <signal.h> -#include <openssl/ssl.h> - -#include "libhttp.h" - -#define HTTP_MAX_REQUEST_HEAD_SIZE 1000000 - -http_bind_t *http_binds = NULL; -int http_binds_len = 0; -pthread_t http_thread; -bool http_quit = false; -bool http_error = false; -SSL_CTX *http_ssl_ctx = NULL; - -http_response_t (*on_request_fn)(http_request_t *) = NULL; - -void http_on_request(http_response_t fn(http_request_t *)) { - on_request_fn = fn; -} - -char *http_read_file(char *filename) { - FILE *fp = fopen(filename, "r"); - fseek(fp, 0, SEEK_END); - long len = ftell(fp); - fseek(fp, 0, SEEK_SET); - char *content = malloc(sizeof(char) * (len + 1)); - fread(content, len, sizeof(char), fp); - content[len] = '\0'; - fclose(fp); - return content; -} - -char *http_status_str(http_status_t status) { - switch (status) { - case HTTP_STATUS_CONTINUE: - return "Continue"; - case HTTP_STATUS_SWITCHING_PROTOCOLS: - return "Switching Protocols"; - case HTTP_STATUS_PROCESSING: - return "Processing"; - case HTTP_STATUS_EARLY_HINTS: - return "Early Hints"; - case HTTP_STATUS_OK: - return "OK"; - case HTTP_STATUS_CREATED: - return "Created"; - case HTTP_STATUS_ACCEPTED: - return "Accepted"; - case HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: - return "Non-Authoritative Information"; - case HTTP_STATUS_NO_CONTENT: - return "No Content"; - case HTTP_STATUS_RESET_CONTENT: - return "Reset Content"; - case HTTP_STATUS_PARTIAL_CONTENT: - return "Partial Content"; - case HTTP_STATUS_MULTI_STATUS: - return "Multi-Status"; - case HTTP_STATUS_ALREADY_REPORTED: - return "Already Reported"; - case HTTP_STATUS_IM_USED: - return "IM Used"; - case HTTP_STATUS_MULTIPLE_CHOICES: - return "Multiple Choices"; - case HTTP_STATUS_MOVED_PERMANENTLY: - return "Moved Permanently"; - case HTTP_STATUS_FOUND: - return "Found"; - case HTTP_STATUS_SEE_OTHER: - return "See Other"; - case HTTP_STATUS_NOT_MODIFIED: - return "Not Modified"; - case HTTP_STATUS_TEMPORARY_REDIRECT: - return "Temporary Redirect"; - case HTTP_STATUS_PERMANENT_REDIRECT: - return "Permanent Redirect"; - case HTTP_STATUS_BAD_REQUEST: - return "Bad Request"; - case HTTP_STATUS_UNAUTHORIZED: - return "Unauthorized"; - case HTTP_STATUS_PAYMENT_REQUIRED: - return "Payment Required"; - case HTTP_STATUS_FORBIDDEN: - return "Forbidden"; - case HTTP_STATUS_NOT_FOUND: - return "Not Found"; - case HTTP_STATUS_METHOD_NOT_ALLOWED: - return "Method Not Allowed"; - case HTTP_STATUS_NOT_ACCEPTABLE: - return "Not Acceptable"; - case HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: - return "Proxy Authentication Required"; - case HTTP_STATUS_REQUEST_TIMEOUT: - return "Request Timeout"; - case HTTP_STATUS_CONFLICT: - return "Conflict"; - case HTTP_STATUS_GONE: - return "Gone"; - case HTTP_STATUS_LENGTH_REQUIRED: - return "Length Required"; - case HTTP_STATUS_PRECONDITION_FAILED: - return "Precondition Failed"; - case HTTP_STATUS_PAYLOAD_TOO_LARGE: - return "Payload Too Large"; - case HTTP_STATUS_URI_TOO_LONG: - return "URI Too Long"; - case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: - return "Unsupported Media Type"; - case HTTP_STATUS_RANGE_NOT_SATISFIABLE: - return "Range Not Satisfiable"; - case HTTP_STATUS_EXPECTATION_FAILED: - return "Expectation Failed"; - case HTTP_STATUS_I_AM_A_TEAPOT: - return "I'm a teapot"; - case HTTP_STATUS_MISDIRECTED_REQUEST: - return "Misdirected Request"; - case HTTP_STATUS_UNPROCESSABLE_CONTENT: - return "Unprocessable Content"; - case HTTP_STATUS_LOCKED: - return "Locked"; - case HTTP_STATUS_FAILED_DEPENDENCY: - return "Failed Dependency"; - case HTTP_STATUS_TOO_EARLY: - return "Too Early"; - case HTTP_STATUS_UPGRADE_REQUIRED: - return "Upgrade Required"; - case HTTP_STATUS_PRECONDITION_REQUIRED: - return "Precondition Required"; - case HTTP_STATUS_TOO_MANY_REQUESTS: - return "Too Many Requests"; - case HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: - return "Request Header Fields Too Large"; - case HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: - return "Unavailable For Legal Reasons"; - case HTTP_STATUS_INTERNAL_SERVER_ERROR: - return "Internal Server Error"; - case HTTP_STATUS_NOT_IMPLEMENTED: - return "Not Implemented"; - case HTTP_STATUS_BAD_GATEWAY: - return "Bad Gateway"; - case HTTP_STATUS_SERVICE_UNAVAILABLE: - return "Service Unavailable"; - case HTTP_STATUS_GATEWAY_TIMEOUT: - return "Gateway Timeout"; - case HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: - return "HTTP Version Not Supported"; - case HTTP_STATUS_VARIANT_ALSO_NEGOTITATES: - return "Variant Also Negotitates"; - case HTTP_STATUS_INSUFFICIENT_STORAGE: - return "Insufficient Storage"; - case HTTP_STATUS_LOOP_DETECTED: - return "Loop Detected"; - case HTTP_STATUS_NOT_EXTENDED: - return "Not Extended"; - case HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: - return "Network Authentication Required"; - default: - return NULL; - } -} - -ssize_t http_read(http_conn_t *conn, void *buffer, size_t len) { - if (conn->ssl != NULL) { - return SSL_read(conn->ssl, buffer, len); - } - return read(conn->sockfd, buffer, len); -} - -ssize_t http_write(http_conn_t *conn, void *buffer, size_t len) { - if (conn->ssl != NULL) { - return SSL_write(conn->ssl, buffer, len); - } - return write(conn->sockfd, buffer, len); -} - -char *http_header_get(http_request_t *request, char *key) { - if (request->headers_len > 0) { - for (int i = 0; i < request->headers_len; i++) { - http_header_t *header = &request->headers[i]; - if (strcmp(header->key, key) == 0) { - return header->value; - } - } - } - return NULL; -} - -void http_header_set(http_response_t *response, char *key, char *value) { - http_header_t *new_header = NULL; - if (response->headers_len > 0) { - for (int i = 0; i < response->headers_len; i++) { - http_header_t *header = &response->headers[i]; - if (strcmp(header->key, key) == 0) { - new_header = header; - break; - } - } - } - if (new_header == NULL) { - response->headers = realloc(response->headers, sizeof(http_header_t) * (response->headers_len + 1)); - new_header = &response->headers[response->headers_len]; - response->headers_len++; - } - int key_len = strlen(key); - new_header->key = malloc(sizeof(char) * (key_len + 1)); - memcpy(new_header->key, key, key_len); - new_header->key[key_len] = '\0'; - int value_len = strlen(value); - new_header->value = malloc(sizeof(char) * (value_len + 1)); - memcpy(new_header->value, value, value_len); - new_header->value[value_len] = '\0'; -} - -http_response_t http_response_create(http_status_t status) { - char *version = "HTTP/1.1"; - int version_len = strlen(version); - http_response_t response = { NULL, status, NULL, 0, NULL, 0 }; - response.version = malloc(sizeof(char) * (version_len + 1)); - memcpy(response.version, version, version_len); - response.version[version_len] = '\0'; - return response; -} - -void http_response_body(http_response_t *response, char *body, long len) { - if (response != NULL && body != NULL) { - response->body = malloc(sizeof(char) * len); - memcpy(response->body, body, len); - response->body_len = len; - char content_length[50]; - snprintf(content_length, 50, "%ld", len); - http_header_set(response, "Content-Length", content_length); - } -} - -void http_response_file(http_response_t *response, char *filename) { - char *content = http_read_file(filename); - if (content != NULL) { - http_response_body(response, content, strlen(content)); - free(content); - } -} - -void http_request_free(http_request_t *request) { - if (request->method != NULL) { - free(request->method); - } - if (request->url != NULL) { - free(request->url); - } - if (request->version != NULL) { - free(request->version); - } - if (request->headers_len > 0) { - for (int i = 0; i < request->headers_len; i++) { - http_header_t *header = &request->headers[i]; - if (header->key != NULL) { - free(header->key); - } - if (header->value != NULL) { - free(header->value); - } - } - } - if (request->body != NULL) { - free(request->body); - } - if (request->ext_data != NULL) { - free(request->ext_data); - } -} - -void http_response_free(http_response_t *response) { - if (response->version != NULL) { - free(response->version); - } - if (response->headers_len > 0) { - for (int i = 0; i < response->headers_len; i++) { - http_header_t *header = &response->headers[i]; - if (header->key != NULL) { - free(header->key); - } - if (header->value != NULL) { - free(header->value); - } - } - } - if (response->body != NULL) { - free(response->body); - } -} - -http_request_t http_read_request(http_conn_t *conn) { - int len = 0; - int ln = 0; - int start = 0; - char buffer[HTTP_MAX_REQUEST_HEAD_SIZE]; - http_request_t request = { - .conn = conn, - .method = NULL, - .url = NULL, - .version = NULL, - .headers = NULL, - .headers_len = 0, - .body = NULL, - .body_len = 0, - .ext_data = NULL, - }; - while (true) { - char c; - int read = http_read(conn, &c, 1); - if (read == 0) { - break; - } - // break if too long - if (len == HTTP_MAX_REQUEST_HEAD_SIZE) { - perror("request is too long"); - break; - } - buffer[len] = c; - len++; - // read head - if (len >= 2) { - char *slice = buffer + (len - 2); - if (strncmp(slice, "\r\n", 2) == 0) { - int line_len = (len - 2) - start; - if (line_len > 0) { - // read line - char line[line_len + 1]; - memcpy(line, buffer + start, line_len); - line[line_len] = '\0'; - // read method, url, and version - if (ln == 0) { - int arg = 0; - int start = 0; - for (int i = 0; i <= line_len; i++) { - if (line[i] == ' ' || i == line_len) { - // read method - if (arg == 0) { - int method_length = i - start; - request.method = malloc(sizeof(char) * (method_length + 1)); - memcpy(request.method, line + start, method_length); - request.method[method_length] = '\0'; - } - // read url - if (arg == 1) { - int url_length = i - start; - request.url = malloc(sizeof(char) * (url_length + 1)); - memcpy(request.url, line + start, url_length); - request.url[url_length] = '\0'; - } - // read version - if (arg == 2) { - int version_length = i - start; - request.version = malloc(sizeof(char) * (version_length + 1)); - memcpy(request.version, line + start, version_length); - request.version[version_length] = '\0'; - } - start = i + 1; - arg++; - } - } - } - // read headers - else { - int sep = 0; - request.headers = realloc(request.headers, sizeof(http_header_t) * (request.headers_len + 1)); - http_header_t *header = &request.headers[request.headers_len]; - request.headers_len++; - for (int i = 0; i < line_len; i++) { - if (line[i] == ':') { - int key_len = i; - header->key = malloc(sizeof(char) * (key_len + 1)); - memcpy(header->key, line, key_len); - header->key[key_len] = '\0'; - sep = i + 2; - break; - } - } - int value_len = line_len - sep; - header->value = malloc(sizeof(char) * (value_len + 1)); - memcpy(header->value, line + sep, value_len); - header->value[value_len] = '\0'; - } - } - start = len; - ln++; - } - } - // read body - if (len >= 4) { - char *slice = buffer + (len - 4); - if (strncmp(slice, "\r\n\r\n", 4) == 0) { - char *content_length = http_header_get(&request, "Content-Length"); - if (content_length != NULL) { - request.body_len = strtol(content_length, NULL, 10); - if (request.body_len > 0) { - request.body = malloc(sizeof(char) * (request.body_len + 1)); - http_read(conn, request.body, request.body_len); - request.body[request.body_len] = '\0'; - } - } - break; - } - } - } - return request; -} - -void http_write_response(http_conn_t *conn, http_response_t response) { - http_write(conn, response.version, strlen(response.version)); - http_write(conn, " ", 1); - char status[4]; - snprintf(status, 4, "%d", response.status); - http_write(conn, status, strlen(status)); - http_write(conn, " ", 1); - char *status_message = http_status_str(response.status); - http_write(conn, status_message, strlen(status_message)); - http_write(conn, "\r\n", 2); - if (response.headers_len > 0) { - for (int i = 0; i < response.headers_len; i++) { - http_header_t *header = &response.headers[i]; - http_write(conn, header->key, strlen(header->key)); - http_write(conn, ": ", 2); - http_write(conn, header->value, strlen(header->value)); - http_write(conn, "\r\n", 2); - } - } - http_write(conn, "\r\n", 2); - if (response.body != NULL) { - http_write(conn, response.body, response.body_len); - } -} - -void *http_handle_client(void *ptr) { - if (!ptr) { - pthread_exit(0); - } - http_conn_t *conn = (http_conn_t *) ptr; - if (conn->bind->hosts_len > 0) { - SSL_CTX_set_tlsext_servername_arg(http_ssl_ctx, (void *) conn); - conn->ssl = SSL_new(http_ssl_ctx); - SSL_set_fd(conn->ssl, conn->sockfd); - if (SSL_accept(conn->ssl) <= 0) { - pthread_exit(0); - } - } - http_request_t request = http_read_request(conn); - if (request.method != NULL && request.url != NULL) { - if (on_request_fn != NULL) { - http_response_t response = on_request_fn(&request); - http_write_response(conn, response); - http_response_free(&response); - } - } - http_request_free(&request); - if (conn->ssl != NULL) { - SSL_shutdown(conn->ssl); - SSL_free(conn->ssl); - } - close(conn->sockfd); - free(conn); - pthread_exit(0); -} - -void *http_handle_server(void *ptr) { - (void)(ptr); - struct pollfd fds[http_binds_len]; - for (int i = 0; i < http_binds_len; i++) { - fds[i].fd = http_binds[i].sockfd; - fds[i].events = POLLIN | POLLPRI; - } - while (!http_quit) { - struct timespec sleep_req, sleep_rem; - sleep_req.tv_sec = 0; - sleep_req.tv_nsec = 100000000; - nanosleep(&sleep_req, &sleep_rem); - if (poll(fds, http_binds_len, 100)) { - for (int i = 0; i < http_binds_len; i++) { - if (fds[i].revents & POLLIN) { - http_conn_t *conn = malloc(sizeof(http_conn_t)); - conn->addr_len = sizeof(conn->addr); - conn->sockfd = accept(http_binds[i].sockfd, &conn->addr, (socklen_t *) &conn->addr_len); - conn->bind = &http_binds[i]; - conn->ssl = NULL; - if (conn->sockfd <= 0) { - free(conn); - } - else { - pthread_t thread; - pthread_attr_t thread_attr; - pthread_attr_init(&thread_attr); - pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thread, &thread_attr, http_handle_client, (void *) conn); - pthread_attr_destroy(&thread_attr); - } - } - } - } - } - pthread_exit(0); -} - -int http_tls_sni_callback(SSL *ssl, int *al, void *arg) { - (void)(al); - http_conn_t *conn = (http_conn_t *) arg; - const char *hostname = SSL_get_servername(ssl, 0); - if (hostname != NULL) { - for (int i = 0; i < http_binds_len; i++) { - for (int j = 0; j < http_binds[i].hosts_len; j++) { - http_host_t *http_host = &http_binds[i].hosts[j]; - if (strcmp(http_host->hostname, hostname) == 0) { - conn->curr_host = http_host; - SSL_set_SSL_CTX(ssl, http_host->ssl_ctx); - return SSL_TLSEXT_ERR_OK; - } - } - } - } - return SSL_TLSEXT_ERR_NOACK; -} - -void http_bind(char *hostname, int port, http_host_t *hosts, int hosts_len) { - http_binds = realloc(http_binds, sizeof(http_bind_t) * (http_binds_len + 1)); - http_bind_t *http_bind = &http_binds[http_binds_len]; - http_bind->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - http_bind->port = port; - if (http_bind->sockfd <= 0) { - perror("cannot create socket"); - errno = HTTP_ERR_NO_SOCKET; - http_error = true; - return; - } - struct hostent *host = gethostbyname(hostname); - if (!host) { - perror("unknown host"); - errno = HTTP_ERR_UNKNOWN_HOST; - http_error = true; - return; - } - struct sockaddr_in addr; - addr.sin_family = AF_INET; - memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length); - addr.sin_port = htons(port); - int enable = 1; - if (setsockopt(http_bind->sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { - perror("cannot reuse address"); - errno = HTTP_ERR_NO_ADDR_REUSE; - http_error = true; - return; - } - if (bind(http_bind->sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0) { - perror("cannot bind socket to port"); - errno = HTTP_ERR_ADDR_IN_USE; - http_error = true; - return; - } - if (listen(http_bind->sockfd, 5) < 0) { - perror("cannot listen on port"); - errno = HTTP_ERR_NO_LISTEN; - http_error = true; - return; - } - for (int i = 0; i < hosts_len; i++) { - http_host_t *http_host = &hosts[i]; - if (http_host->certificate != NULL && http_host->private_key != NULL) { - const SSL_METHOD *method = TLS_server_method(); - http_host->ssl_ctx = SSL_CTX_new(method); - if (!http_host->ssl_ctx) { - perror("cannot create SSL context"); - errno = HTTP_ERR_NO_SSL_CONTEXT; - http_error = true; - return; - } - if (SSL_CTX_use_certificate_chain_file(http_host->ssl_ctx, http_host->certificate) <= 0) { - perror("cannot use certificate file"); - errno = HTTP_ERR_INVALID_CERTIFICATE; - http_error = true; - } - if (SSL_CTX_use_PrivateKey_file(http_host->ssl_ctx, http_host->private_key, SSL_FILETYPE_PEM) <= 0) { - perror("cannot use private key file"); - errno = HTTP_ERR_INVALID_PRIVATE_KEY; - http_error = true; - } - } - else { - http_host->ssl_ctx = NULL; - } - } - http_bind->hosts = hosts; - http_bind->hosts_len = hosts_len; - http_binds_len++; -} - -void http_listen(void) { - signal(SIGPIPE, SIG_IGN); - if (http_error) { - return; - } - errno = 0; - const SSL_METHOD *method = TLS_server_method(); - http_ssl_ctx = SSL_CTX_new(method); - if (!http_ssl_ctx) { - perror("cannot create SSL context"); - errno = HTTP_ERR_NO_SSL_CONTEXT; - http_error = true; - return; - } - SSL_CTX_set_tlsext_servername_callback(http_ssl_ctx, http_tls_sni_callback); - pthread_create(&http_thread, 0, http_handle_server, NULL); -} - -void http_close(void) { - http_quit = true; - if (!http_error) { - pthread_join(http_thread, NULL); - } - for (int i = 0; i < http_binds_len; i++) { - close(http_binds[i].sockfd); - for (int j = 0; j < http_binds[i].hosts_len; j++) { - if (http_binds[i].hosts[j].ssl_ctx != NULL) { - SSL_CTX_free(http_binds[i].hosts[j].ssl_ctx); - } - } - } - if (http_ssl_ctx != NULL) { - free(http_ssl_ctx); - } -} diff --git a/libhttp.h b/libhttp.h @@ -1,141 +0,0 @@ -#pragma once - -#include "sys/socket.h" -#include "openssl/ssl.h" - -typedef enum { - HTTP_ERR_NO_SOCKET = 1, - HTTP_ERR_UNKNOWN_HOST = 2, - HTTP_ERR_NO_ADDR_REUSE = 3, - HTTP_ERR_ADDR_IN_USE = 4, - HTTP_ERR_NO_LISTEN = 5, - HTTP_ERR_NO_SSL_CONTEXT = 6, - HTTP_ERR_INVALID_CERTIFICATE = 7, - HTTP_ERR_INVALID_PRIVATE_KEY = 8, -} http_error_t; - -typedef enum { - HTTP_STATUS_CONTINUE = 100, - HTTP_STATUS_SWITCHING_PROTOCOLS = 101, - HTTP_STATUS_PROCESSING = 102, - HTTP_STATUS_EARLY_HINTS = 103, - HTTP_STATUS_OK = 200, - HTTP_STATUS_CREATED = 201, - HTTP_STATUS_ACCEPTED = 202, - HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, - HTTP_STATUS_NO_CONTENT = 204, - HTTP_STATUS_RESET_CONTENT = 205, - HTTP_STATUS_PARTIAL_CONTENT = 206, - HTTP_STATUS_MULTI_STATUS = 207, - HTTP_STATUS_ALREADY_REPORTED = 208, - HTTP_STATUS_IM_USED = 226, - HTTP_STATUS_MULTIPLE_CHOICES = 300, - HTTP_STATUS_MOVED_PERMANENTLY = 301, - HTTP_STATUS_FOUND = 302, - HTTP_STATUS_SEE_OTHER = 303, - HTTP_STATUS_NOT_MODIFIED = 304, - HTTP_STATUS_TEMPORARY_REDIRECT = 307, - HTTP_STATUS_PERMANENT_REDIRECT = 308, - HTTP_STATUS_BAD_REQUEST = 400, - HTTP_STATUS_UNAUTHORIZED = 401, - HTTP_STATUS_PAYMENT_REQUIRED = 402, - HTTP_STATUS_FORBIDDEN = 403, - HTTP_STATUS_NOT_FOUND = 404, - HTTP_STATUS_METHOD_NOT_ALLOWED = 405, - HTTP_STATUS_NOT_ACCEPTABLE = 406, - HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, - HTTP_STATUS_REQUEST_TIMEOUT = 408, - HTTP_STATUS_CONFLICT = 409, - HTTP_STATUS_GONE = 410, - HTTP_STATUS_LENGTH_REQUIRED = 411, - HTTP_STATUS_PRECONDITION_FAILED = 412, - HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, - HTTP_STATUS_URI_TOO_LONG = 414, - HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, - HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, - HTTP_STATUS_EXPECTATION_FAILED = 417, - HTTP_STATUS_I_AM_A_TEAPOT = 418, - HTTP_STATUS_MISDIRECTED_REQUEST = 421, - HTTP_STATUS_UNPROCESSABLE_CONTENT = 422, - HTTP_STATUS_LOCKED = 423, - HTTP_STATUS_FAILED_DEPENDENCY = 424, - HTTP_STATUS_TOO_EARLY = 425, - HTTP_STATUS_UPGRADE_REQUIRED = 426, - HTTP_STATUS_PRECONDITION_REQUIRED = 428, - HTTP_STATUS_TOO_MANY_REQUESTS = 429, - HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, - HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, - HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, - HTTP_STATUS_NOT_IMPLEMENTED = 501, - HTTP_STATUS_BAD_GATEWAY = 502, - HTTP_STATUS_SERVICE_UNAVAILABLE = 503, - HTTP_STATUS_GATEWAY_TIMEOUT = 504, - HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, - HTTP_STATUS_VARIANT_ALSO_NEGOTITATES = 506, - HTTP_STATUS_INSUFFICIENT_STORAGE = 507, - HTTP_STATUS_LOOP_DETECTED = 508, - HTTP_STATUS_NOT_EXTENDED = 510, - HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, -} http_status_t; - -typedef struct { - char *hostname; - char *certificate; - char *private_key; - SSL_CTX *ssl_ctx; -} http_host_t; - -typedef struct { - int sockfd; - http_host_t *hosts; - int hosts_len; - int port; -} http_bind_t; - -typedef struct { - int sockfd; - struct sockaddr addr; - int addr_len; - SSL *ssl; - http_bind_t *bind; - http_host_t *curr_host; -} http_conn_t; - -typedef struct { - char *key; - char *value; -} http_header_t; - -typedef struct { - http_conn_t *conn; - char *method; - char *url; - char *version; - http_header_t *headers; - int headers_len; - char *body; - long body_len; - void *ext_data; -} http_request_t; - -typedef struct { - char *version; - http_status_t status; - http_header_t *headers; - int headers_len; - char *body; - long body_len; -} http_response_t; - -void http_on_request(http_response_t fn(http_request_t *)); - -char *http_header_get(http_request_t *request, char *key); -void http_header_set(http_response_t *response, char *key, char *value); - -http_response_t http_response_create(http_status_t status); -void http_response_body(http_response_t *response, char *body, long len); -void http_response_file(http_response_t *response, char *filename); - -void http_bind(char *hostname, int port, http_host_t *hosts, int hosts_len); -void http_listen(void); -void http_close(void); diff --git a/src/libhttp.h b/src/libhttp.h @@ -0,0 +1,761 @@ +#ifndef LIB_HTTP_H +#define LIB_HTTP_H + +#include <stdbool.h> +#include <openssl/ssl.h> +#include <sys/socket.h> + +typedef enum { + HTTP_ERR_OK = 0, + HTTP_ERR_NO_SOCKET = 1, + HTTP_ERR_UNKNOWN_HOST = 2, + HTTP_ERR_NO_ADDR_REUSE = 3, + HTTP_ERR_ADDR_IN_USE = 4, + HTTP_ERR_NO_LISTEN = 5, + HTTP_ERR_NO_SSL_CONTEXT = 6, + HTTP_ERR_INVALID_CERTIFICATE = 7, + HTTP_ERR_INVALID_PRIVATE_KEY = 8, +} http_error_t; + +typedef enum { + HTTP_STATUS_CONTINUE = 100, + HTTP_STATUS_SWITCHING_PROTOCOLS = 101, + HTTP_STATUS_PROCESSING = 102, + HTTP_STATUS_EARLY_HINTS = 103, + HTTP_STATUS_OK = 200, + HTTP_STATUS_CREATED = 201, + HTTP_STATUS_ACCEPTED = 202, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_RESET_CONTENT = 205, + HTTP_STATUS_PARTIAL_CONTENT = 206, + HTTP_STATUS_MULTI_STATUS = 207, + HTTP_STATUS_ALREADY_REPORTED = 208, + HTTP_STATUS_IM_USED = 226, + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_PAYMENT_REQUIRED = 402, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_STATUS_REQUEST_TIMEOUT = 408, + HTTP_STATUS_CONFLICT = 409, + HTTP_STATUS_GONE = 410, + HTTP_STATUS_LENGTH_REQUIRED = 411, + HTTP_STATUS_PRECONDITION_FAILED = 412, + HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, + HTTP_STATUS_URI_TOO_LONG = 414, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, + HTTP_STATUS_EXPECTATION_FAILED = 417, + HTTP_STATUS_I_AM_A_TEAPOT = 418, + HTTP_STATUS_MISDIRECTED_REQUEST = 421, + HTTP_STATUS_UNPROCESSABLE_CONTENT = 422, + HTTP_STATUS_LOCKED = 423, + HTTP_STATUS_FAILED_DEPENDENCY = 424, + HTTP_STATUS_TOO_EARLY = 425, + HTTP_STATUS_UPGRADE_REQUIRED = 426, + HTTP_STATUS_PRECONDITION_REQUIRED = 428, + HTTP_STATUS_TOO_MANY_REQUESTS = 429, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED = 501, + HTTP_STATUS_BAD_GATEWAY = 502, + HTTP_STATUS_SERVICE_UNAVAILABLE = 503, + HTTP_STATUS_GATEWAY_TIMEOUT = 504, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_STATUS_VARIANT_ALSO_NEGOTITATES = 506, + HTTP_STATUS_INSUFFICIENT_STORAGE = 507, + HTTP_STATUS_LOOP_DETECTED = 508, + HTTP_STATUS_NOT_EXTENDED = 510, + HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, +} http_status_t; + +typedef struct Http http_t; + +typedef struct { + char *hostname; + char *certificate; + char *private_key; + SSL_CTX *ssl_ctx; +} http_host_t; + +typedef struct { + int sockfd; + http_host_t *hosts; + int hosts_len; + int port; +} http_bind_t; + +typedef struct { + int sockfd; + struct sockaddr addr; + int addr_len; + SSL *ssl; + http_bind_t *curr_bind; + http_host_t *curr_host; + http_t *http; +} http_conn_t; + +typedef struct { + char *key; + char *value; +} http_header_t; + +typedef struct { + http_conn_t *conn; + char *method; + char *url; + char *version; + http_header_t *headers; + int headers_len; + char *body; + long body_len; + void *ext_data; +} http_request_t; + +typedef struct { + char *version; + http_status_t status; + http_header_t *headers; + int headers_len; + char *body; + long body_len; +} http_response_t; + +struct Http { + http_bind_t *binds; + int binds_len; + pthread_t thread; + bool quit; + SSL_CTX *ssl_ctx; + http_response_t (*on_request_fn)(http_request_t *); +}; + +http_t *http_init(void); +void http_cleanup(http_t *http); +void http_on_request(http_t *http, http_response_t fn(http_request_t *)); +char *http_header_get(http_request_t *request, char *key); +void http_header_set(http_response_t *response, char *key, char *value); +http_response_t http_response_create(http_status_t status); +void http_response_body(http_response_t *response, char *body, long len); +void http_response_file(http_response_t *response, char *filename); +http_error_t http_bind(http_t *http, char *hostname, int port, http_host_t *hosts, int hosts_len); +http_error_t http_listen(http_t *http); +void http_close(http_t *http); + +#ifdef LIB_HTTP_IMPL + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <pthread.h> +#include <netdb.h> +#include <poll.h> +#include <signal.h> +#include <openssl/ssl.h> +#include <sys/socket.h> + +#define HTTP_MAX_REQUEST_HEAD_SIZE 1000000 + +http_t *http_init(void) { + http_t *http = (http_t *) malloc(sizeof(http_t)); + *http = (http_t) { + .binds = NULL, + .binds_len = 0, + .quit = false, + .ssl_ctx = NULL, + .on_request_fn = NULL, + }; + return http; +} + +void http_cleanup(http_t *http) { + free(http); +} + +void http_on_request(http_t *http, http_response_t fn(http_request_t *)) { + http->on_request_fn = fn; +} + +char *http_read_file(char *filename) { + FILE *fp = fopen(filename, "r"); + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + fseek(fp, 0, SEEK_SET); + char *content = (char *) malloc(sizeof(char) * (len + 1)); + fread(content, len, sizeof(char), fp); + content[len] = '\0'; + fclose(fp); + return content; +} + +const char *http_status_str(http_status_t status) { + switch (status) { + case HTTP_STATUS_CONTINUE: + return "Continue"; + case HTTP_STATUS_SWITCHING_PROTOCOLS: + return "Switching Protocols"; + case HTTP_STATUS_PROCESSING: + return "Processing"; + case HTTP_STATUS_EARLY_HINTS: + return "Early Hints"; + case HTTP_STATUS_OK: + return "OK"; + case HTTP_STATUS_CREATED: + return "Created"; + case HTTP_STATUS_ACCEPTED: + return "Accepted"; + case HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: + return "Non-Authoritative Information"; + case HTTP_STATUS_NO_CONTENT: + return "No Content"; + case HTTP_STATUS_RESET_CONTENT: + return "Reset Content"; + case HTTP_STATUS_PARTIAL_CONTENT: + return "Partial Content"; + case HTTP_STATUS_MULTI_STATUS: + return "Multi-Status"; + case HTTP_STATUS_ALREADY_REPORTED: + return "Already Reported"; + case HTTP_STATUS_IM_USED: + return "IM Used"; + case HTTP_STATUS_MULTIPLE_CHOICES: + return "Multiple Choices"; + case HTTP_STATUS_MOVED_PERMANENTLY: + return "Moved Permanently"; + case HTTP_STATUS_FOUND: + return "Found"; + case HTTP_STATUS_SEE_OTHER: + return "See Other"; + case HTTP_STATUS_NOT_MODIFIED: + return "Not Modified"; + case HTTP_STATUS_TEMPORARY_REDIRECT: + return "Temporary Redirect"; + case HTTP_STATUS_PERMANENT_REDIRECT: + return "Permanent Redirect"; + case HTTP_STATUS_BAD_REQUEST: + return "Bad Request"; + case HTTP_STATUS_UNAUTHORIZED: + return "Unauthorized"; + case HTTP_STATUS_PAYMENT_REQUIRED: + return "Payment Required"; + case HTTP_STATUS_FORBIDDEN: + return "Forbidden"; + case HTTP_STATUS_NOT_FOUND: + return "Not Found"; + case HTTP_STATUS_METHOD_NOT_ALLOWED: + return "Method Not Allowed"; + case HTTP_STATUS_NOT_ACCEPTABLE: + return "Not Acceptable"; + case HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: + return "Proxy Authentication Required"; + case HTTP_STATUS_REQUEST_TIMEOUT: + return "Request Timeout"; + case HTTP_STATUS_CONFLICT: + return "Conflict"; + case HTTP_STATUS_GONE: + return "Gone"; + case HTTP_STATUS_LENGTH_REQUIRED: + return "Length Required"; + case HTTP_STATUS_PRECONDITION_FAILED: + return "Precondition Failed"; + case HTTP_STATUS_PAYLOAD_TOO_LARGE: + return "Payload Too Large"; + case HTTP_STATUS_URI_TOO_LONG: + return "URI Too Long"; + case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: + return "Unsupported Media Type"; + case HTTP_STATUS_RANGE_NOT_SATISFIABLE: + return "Range Not Satisfiable"; + case HTTP_STATUS_EXPECTATION_FAILED: + return "Expectation Failed"; + case HTTP_STATUS_I_AM_A_TEAPOT: + return "I'm a teapot"; + case HTTP_STATUS_MISDIRECTED_REQUEST: + return "Misdirected Request"; + case HTTP_STATUS_UNPROCESSABLE_CONTENT: + return "Unprocessable Content"; + case HTTP_STATUS_LOCKED: + return "Locked"; + case HTTP_STATUS_FAILED_DEPENDENCY: + return "Failed Dependency"; + case HTTP_STATUS_TOO_EARLY: + return "Too Early"; + case HTTP_STATUS_UPGRADE_REQUIRED: + return "Upgrade Required"; + case HTTP_STATUS_PRECONDITION_REQUIRED: + return "Precondition Required"; + case HTTP_STATUS_TOO_MANY_REQUESTS: + return "Too Many Requests"; + case HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: + return "Request Header Fields Too Large"; + case HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: + return "Unavailable For Legal Reasons"; + case HTTP_STATUS_INTERNAL_SERVER_ERROR: + return "Internal Server Error"; + case HTTP_STATUS_NOT_IMPLEMENTED: + return "Not Implemented"; + case HTTP_STATUS_BAD_GATEWAY: + return "Bad Gateway"; + case HTTP_STATUS_SERVICE_UNAVAILABLE: + return "Service Unavailable"; + case HTTP_STATUS_GATEWAY_TIMEOUT: + return "Gateway Timeout"; + case HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: + return "HTTP Version Not Supported"; + case HTTP_STATUS_VARIANT_ALSO_NEGOTITATES: + return "Variant Also Negotitates"; + case HTTP_STATUS_INSUFFICIENT_STORAGE: + return "Insufficient Storage"; + case HTTP_STATUS_LOOP_DETECTED: + return "Loop Detected"; + case HTTP_STATUS_NOT_EXTENDED: + return "Not Extended"; + case HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: + return "Network Authentication Required"; + default: + return NULL; + } +} + +ssize_t http_read(http_conn_t *conn, char *buffer, size_t len) { + if (conn->ssl != NULL) { + return SSL_read(conn->ssl, buffer, len); + } + return read(conn->sockfd, buffer, len); +} + +ssize_t http_write(http_conn_t *conn, char *buffer, size_t len) { + if (conn->ssl != NULL) { + return SSL_write(conn->ssl, buffer, len); + } + return write(conn->sockfd, buffer, len); +} + +char *http_header_get(http_request_t *request, char *key) { + if (request->headers_len > 0) { + for (int i = 0; i < request->headers_len; i++) { + http_header_t *header = &request->headers[i]; + if (strcmp(header->key, key) == 0) { + return header->value; + } + } + } + return NULL; +} + +void http_header_set(http_response_t *response, char *key, char *value) { + response->headers = (http_header_t *) realloc(response->headers, sizeof(http_header_t) * (response->headers_len + 1)); + http_header_t *new_header = &response->headers[response->headers_len]; + response->headers_len++; + int key_len = strlen(key); + new_header->key = (char *) malloc(sizeof(char) * (key_len + 1)); + memcpy(new_header->key, key, key_len); + new_header->key[key_len] = '\0'; + int value_len = strlen(value); + new_header->value = (char *) malloc(sizeof(char) * (value_len + 1)); + memcpy(new_header->value, value, value_len); + new_header->value[value_len] = '\0'; +} + +http_response_t http_response_create(http_status_t status) { + const char *version = "HTTP/1.1"; + int version_len = strlen(version); + http_response_t response = { NULL, status, NULL, 0, NULL, 0 }; + response.version = (char *) malloc(sizeof(char) * (version_len + 1)); + memcpy(response.version, version, version_len); + response.version[version_len] = '\0'; + return response; +} + +void http_response_body(http_response_t *response, char *body, long len) { + if (response != NULL && body != NULL) { + response->body = (char *) malloc(sizeof(char) * len); + memcpy(response->body, body, len); + response->body_len = len; + char content_length[50]; + snprintf(content_length, 50, "%ld", len); + http_header_set(response, (char *) "Content-Length", content_length); + } +} + +void http_response_file(http_response_t *response, char *filename) { + char *content = http_read_file(filename); + if (content != NULL) { + http_response_body(response, content, strlen(content)); + free(content); + } +} + +void http_request_free(http_request_t *request) { + if (request->method != NULL) { + free(request->method); + } + if (request->url != NULL) { + free(request->url); + } + if (request->version != NULL) { + free(request->version); + } + if (request->headers_len > 0) { + for (int i = 0; i < request->headers_len; i++) { + http_header_t *header = &request->headers[i]; + if (header->key != NULL) { + free(header->key); + } + if (header->value != NULL) { + free(header->value); + } + } + } + if (request->body != NULL) { + free(request->body); + } + if (request->ext_data != NULL) { + free(request->ext_data); + } +} + +void http_response_free(http_response_t *response) { + if (response->version != NULL) { + free(response->version); + } + if (response->headers_len > 0) { + for (int i = 0; i < response->headers_len; i++) { + http_header_t *header = &response->headers[i]; + if (header->key != NULL) { + free(header->key); + } + if (header->value != NULL) { + free(header->value); + } + } + } + if (response->body != NULL) { + free(response->body); + } +} + +http_request_t http_read_request(http_conn_t *conn) { + int len = 0; + int ln = 0; + int start = 0; + char buffer[HTTP_MAX_REQUEST_HEAD_SIZE]; + http_request_t request = { + .conn = conn, + .method = NULL, + .url = NULL, + .version = NULL, + .headers = NULL, + .headers_len = 0, + .body = NULL, + .body_len = 0, + .ext_data = NULL, + }; + while (true) { + char c; + int read = http_read(conn, &c, 1); + if (read == 0) { + break; + } + // break if too long + if (len == HTTP_MAX_REQUEST_HEAD_SIZE) { + perror("request is too long"); + break; + } + buffer[len] = c; + len++; + // read head + if (len >= 2) { + char *slice = buffer + (len - 2); + if (strncmp(slice, "\r\n", 2) == 0) { + int line_len = (len - 2) - start; + if (line_len > 0) { + // read line + char line[line_len + 1]; + memcpy(line, buffer + start, line_len); + line[line_len] = '\0'; + // read method, url, and version + if (ln == 0) { + int arg = 0; + int start = 0; + for (int i = 0; i <= line_len; i++) { + if (line[i] == ' ' || i == line_len) { + // read method + if (arg == 0) { + int method_length = i - start; + request.method = (char *) malloc(sizeof(char) * (method_length + 1)); + memcpy(request.method, line + start, method_length); + request.method[method_length] = '\0'; + } + // read url + if (arg == 1) { + int url_length = i - start; + request.url = (char *) malloc(sizeof(char) * (url_length + 1)); + memcpy(request.url, line + start, url_length); + request.url[url_length] = '\0'; + } + // read version + if (arg == 2) { + int version_length = i - start; + request.version = (char *) malloc(sizeof(char) * (version_length + 1)); + memcpy(request.version, line + start, version_length); + request.version[version_length] = '\0'; + } + start = i + 1; + arg++; + } + } + } + // read headers + else { + int sep = 0; + request.headers = (http_header_t *) realloc(request.headers, sizeof(http_header_t) * (request.headers_len + 1)); + http_header_t *header = &request.headers[request.headers_len]; + request.headers_len++; + for (int i = 0; i < line_len; i++) { + if (line[i] == ':') { + int key_len = i; + header->key = (char *) malloc(sizeof(char) * (key_len + 1)); + memcpy(header->key, line, key_len); + header->key[key_len] = '\0'; + sep = i + 2; + break; + } + } + int value_len = line_len - sep; + header->value = (char *) malloc(sizeof(char) * (value_len + 1)); + memcpy(header->value, line + sep, value_len); + header->value[value_len] = '\0'; + } + } + start = len; + ln++; + } + } + // read body + if (len >= 4) { + char *slice = buffer + (len - 4); + if (strncmp(slice, "\r\n\r\n", 4) == 0) { + char *content_length = http_header_get(&request, (char *) "Content-Length"); + if (content_length != NULL) { + request.body_len = strtol(content_length, NULL, 10); + if (request.body_len > 0) { + request.body = (char *) malloc(sizeof(char) * (request.body_len + 1)); + http_read(conn, request.body, request.body_len); + request.body[request.body_len] = '\0'; + } + } + break; + } + } + } + return request; +} + +void http_write_response(http_conn_t *conn, http_response_t response) { + http_write(conn, response.version, strlen(response.version)); + http_write(conn, (char *) " ", 1); + char status[4]; + snprintf(status, 4, "%d", response.status); + http_write(conn, status, strlen(status)); + http_write(conn, (char *) " ", 1); + const char *status_message = http_status_str(response.status); + http_write(conn, (char *) status_message, strlen(status_message)); + http_write(conn, (char *) "\r\n", 2); + if (response.headers_len > 0) { + for (int i = 0; i < response.headers_len; i++) { + http_header_t *header = &response.headers[i]; + http_write(conn, header->key, strlen(header->key)); + http_write(conn, (char *) ": ", 2); + http_write(conn, header->value, strlen(header->value)); + http_write(conn, (char *) "\r\n", 2); + } + } + http_write(conn, (char *) "\r\n", 2); + if (response.body != NULL) { + http_write(conn, response.body, response.body_len); + } +} + +void *http_handle_client(void *ptr) { + if (!ptr) { + pthread_exit(0); + } + http_conn_t *conn = (http_conn_t *) ptr; + if (conn->curr_bind->hosts_len > 0) { + SSL_CTX_set_tlsext_servername_arg(conn->http->ssl_ctx, (void *) conn); + conn->ssl = SSL_new(conn->http->ssl_ctx); + SSL_set_fd(conn->ssl, conn->sockfd); + if (SSL_accept(conn->ssl) <= 0) { + pthread_exit(0); + } + } + http_request_t request = http_read_request(conn); + if (request.method != NULL && request.url != NULL) { + if (conn->http->on_request_fn != NULL) { + http_response_t response = conn->http->on_request_fn(&request); + http_write_response(conn, response); + http_response_free(&response); + } + } + http_request_free(&request); + if (conn->ssl != NULL) { + SSL_shutdown(conn->ssl); + SSL_free(conn->ssl); + } + close(conn->sockfd); + free(conn); + pthread_exit(0); +} + +void *http_handle_server(void *ptr) { + http_t *http = (http_t *) ptr; + struct pollfd fds[http->binds_len]; + for (int i = 0; i < http->binds_len; i++) { + fds[i].fd = http->binds[i].sockfd; + fds[i].events = POLLIN | POLLPRI; + } + while (!http->quit) { + struct timespec sleep_req, sleep_rem; + sleep_req.tv_sec = 0; + sleep_req.tv_nsec = 100000000; + nanosleep(&sleep_req, &sleep_rem); + if (poll(fds, http->binds_len, 100)) { + for (int i = 0; i < http->binds_len; i++) { + if (fds[i].revents & POLLIN) { + http_conn_t *conn = (http_conn_t *) malloc(sizeof(http_conn_t)); + conn->addr_len = sizeof(conn->addr); + conn->sockfd = accept(http->binds[i].sockfd, &conn->addr, (socklen_t *) &conn->addr_len); + conn->curr_bind = &http->binds[i]; + conn->ssl = NULL; + conn->http = http; + if (conn->sockfd <= 0) { + free(conn); + } + else { + pthread_t thread; + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); + pthread_create(&thread, &thread_attr, http_handle_client, (void *) conn); + pthread_attr_destroy(&thread_attr); + } + } + } + } + } + pthread_exit(0); +} + +int http_tls_sni_callback(SSL *ssl, int *al, void *arg) { + (void)(al); + http_conn_t *conn = (http_conn_t *) arg; + const char *hostname = SSL_get_servername(ssl, 0); + if (hostname != NULL) { + for (int i = 0; i < conn->http->binds_len; i++) { + for (int j = 0; j < conn->http->binds[i].hosts_len; j++) { + http_host_t *http_host = &conn->http->binds[i].hosts[j]; + if (strcmp(http_host->hostname, hostname) == 0) { + conn->curr_host = http_host; + SSL_set_SSL_CTX(ssl, http_host->ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } + } + } + } + return SSL_TLSEXT_ERR_NOACK; +} + +http_error_t http_bind(http_t *http, char *hostname, int port, http_host_t *hosts, int hosts_len) { + http->binds = (http_bind_t *) realloc(http->binds, sizeof(http_bind_t) * (http->binds_len + 1)); + http_bind_t *http_bind = &http->binds[http->binds_len]; + http_bind->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + http_bind->port = port; + if (http_bind->sockfd <= 0) { + return HTTP_ERR_NO_SOCKET; + } + struct hostent *host = gethostbyname(hostname); + if (!host) { + return HTTP_ERR_UNKNOWN_HOST; + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length); + addr.sin_port = htons(port); + int enable = 1; + if (setsockopt(http_bind->sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + return HTTP_ERR_NO_ADDR_REUSE; + } + if (bind(http_bind->sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0) { + return HTTP_ERR_ADDR_IN_USE; + } + if (listen(http_bind->sockfd, 5) < 0) { + return HTTP_ERR_NO_LISTEN; + } + for (int i = 0; i < hosts_len; i++) { + http_host_t *http_host = &hosts[i]; + http_host->ssl_ctx = NULL; + if (http_host->certificate != NULL && http_host->private_key != NULL) { + const SSL_METHOD *method = TLS_server_method(); + http_host->ssl_ctx = SSL_CTX_new(method); + if (!http_host->ssl_ctx) { + return HTTP_ERR_NO_SSL_CONTEXT; + } + if (SSL_CTX_use_certificate_chain_file(http_host->ssl_ctx, http_host->certificate) <= 0) { + return HTTP_ERR_INVALID_CERTIFICATE; + } + if (SSL_CTX_use_PrivateKey_file(http_host->ssl_ctx, http_host->private_key, SSL_FILETYPE_PEM) <= 0) { + return HTTP_ERR_INVALID_PRIVATE_KEY; + } + } + } + http_bind->hosts = hosts; + http_bind->hosts_len = hosts_len; + http->binds_len++; + return HTTP_ERR_OK; +} + +http_error_t http_listen(http_t *http) { + signal(SIGPIPE, SIG_IGN); + const SSL_METHOD *method = TLS_server_method(); + http->ssl_ctx = SSL_CTX_new(method); + if (!http->ssl_ctx) { + return HTTP_ERR_NO_SSL_CONTEXT; + } + SSL_CTX_set_tlsext_servername_callback(http->ssl_ctx, http_tls_sni_callback); + pthread_create(&http->thread, 0, http_handle_server, (void *) http); + return HTTP_ERR_OK; +} + +void http_close(http_t *http) { + http->quit = true; + pthread_join(http->thread, NULL); + for (int i = 0; i < http->binds_len; i++) { + close(http->binds[i].sockfd); + for (int j = 0; j < http->binds[i].hosts_len; j++) { + if (http->binds[i].hosts[j].ssl_ctx != NULL) { + SSL_CTX_free(http->binds[i].hosts[j].ssl_ctx); + } + } + } + if (http->ssl_ctx != NULL) { + free(http->ssl_ctx); + } +} + +#endif /* LIB_HTTP_IMPL */ +#endif /* LIB_HTTP_H */