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:
M | Makefile | | | 15 | ++------------- |
M | README.md | | | 32 | ++++++++++++++++++++++++++++++++ |
D | libhttp.c | | | 638 | ------------------------------------------------------------------------------- |
D | libhttp.h | | | 141 | ------------------------------------------------------------------------------- |
A | src/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 */