Implement HTTP/2 and WebSocket support; remove legacy JavaScript and CSS files

- Added HTTP/2 support in src/http2.c and src/http2.h, including session management, frame handling, and response sending.
- Introduced WebSocket functionality in src/websocket.c and src/websocket.h, covering handshake, frame parsing, and message sending.
- Created a WebSocket test page (www/websocket-test.html) for client-side interaction.
- Removed outdated JavaScript (www/script.js) and CSS (www/style.css) files.
This commit is contained in:
2025-10-02 21:14:23 +00:00
parent 7028a0cb11
commit a2c4617493
15 changed files with 2062 additions and 174 deletions

View File

@@ -115,6 +115,14 @@ int load_config(const char *filename, ServerConfig *config) {
config->verbose = parse_bool(value);
printf("load_config: verbose = %d\n", config->verbose);
}
else if (strcasecmp(key, "enable_http2") == 0) {
config->enable_http2 = parse_bool(value);
printf("load_config: enable_http2 = %d\n", config->enable_http2);
}
else if (strcasecmp(key, "enable_websocket") == 0) {
config->enable_websocket = parse_bool(value);
printf("load_config: enable_websocket = %d\n", config->enable_websocket);
}
else {
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number);
}

506
src/http2.c Normal file
View File

@@ -0,0 +1,506 @@
#include "http2.h"
#include "server_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
extern ServerConfig config;
extern void log_event(const char *message);
extern char* get_mime_type(const char *filepath);
extern char* sanitize_url(const char *url);
// ALPN callback - select HTTP/2 protocol
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
(void)ssl;
(void)arg;
int ret = nghttp2_select_next_protocol((unsigned char **)out, outlen,
in, inlen);
if (ret == 1) {
// HTTP/2 selected
return SSL_TLSEXT_ERR_OK;
} else if (ret == 0) {
// HTTP/1.1 selected
return SSL_TLSEXT_ERR_OK;
}
// No match, use HTTP/1.1 as fallback
*out = (const unsigned char *)"http/1.1";
*outlen = 8;
return SSL_TLSEXT_ERR_OK;
}
// Data read callback for nghttp2
static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
uint8_t *buf, size_t length,
uint32_t *data_flags,
nghttp2_data_source *source,
void *user_data) {
(void)session;
(void)stream_id;
(void)user_data;
int fd = source->fd;
ssize_t nread;
while ((nread = read(fd, buf, length)) == -1 && errno == EINTR);
if (nread == -1) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (nread == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
return nread;
}
// Send callback for nghttp2
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
size_t length, int flags, void *user_data) {
(void)session;
(void)flags;
http2_session_t *h2_session = (http2_session_t *)user_data;
ssize_t rv;
if (h2_session->ssl) {
rv = SSL_write(h2_session->ssl, data, (int)length);
if (rv < 0) {
int ssl_error = SSL_get_error(h2_session->ssl, rv);
if (ssl_error == SSL_ERROR_WANT_WRITE) {
return NGHTTP2_ERR_WOULDBLOCK;
}
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else {
rv = write(h2_session->client_socket, data, length);
if (rv < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return NGHTTP2_ERR_WOULDBLOCK;
}
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return rv;
}
// Receive callback for nghttp2
static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf,
size_t length, int flags, void *user_data) {
(void)session;
(void)flags;
http2_session_t *h2_session = (http2_session_t *)user_data;
ssize_t rv;
if (h2_session->ssl) {
rv = SSL_read(h2_session->ssl, buf, (int)length);
if (rv < 0) {
int ssl_error = SSL_get_error(h2_session->ssl, rv);
if (ssl_error == SSL_ERROR_WANT_READ) {
return NGHTTP2_ERR_WOULDBLOCK;
}
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if (rv == 0) {
return NGHTTP2_ERR_EOF;
}
} else {
rv = read(h2_session->client_socket, buf, length);
if (rv < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return NGHTTP2_ERR_WOULDBLOCK;
}
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if (rv == 0) {
return NGHTTP2_ERR_EOF;
}
}
return rv;
}
// Frame receive callback
static int on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data) {
(void)user_data;
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
log_event("HTTP/2: Received HEADERS frame (request)");
// Get stream data
http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
// Request is complete, send response
char *path = stream_data->request_path;
if (strlen(path) == 0) {
strcpy(path, "/");
}
// Sanitize URL
char *sanitized = sanitize_url(path);
if (!sanitized) {
log_event("HTTP/2: Blocked malicious URL");
// Send 403 error
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"403", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
};
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
break;
}
// Build file path
char filepath[512];
snprintf(filepath, sizeof(filepath), "www%s",
(strcmp(sanitized, "/") == 0) ? "/index.html" : sanitized);
free(sanitized);
// Open file
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
log_event("HTTP/2: File not found");
// Send 404 error
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"404", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
};
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
break;
}
// Get file size
struct stat st;
if (fstat(fd, &st) == -1) {
close(fd);
log_event("HTTP/2: Error getting file size");
// Send 500 error
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"500", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
};
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
break;
}
// Get MIME type
char *mime_type = get_mime_type(filepath);
if (!mime_type) {
mime_type = strdup("application/octet-stream");
}
// Store file info in stream data
stream_data->fd = fd;
stream_data->file_size = st.st_size;
stream_data->mime_type = mime_type;
// Build response headers - allocate content length string
char *content_length = malloc(32);
if (!content_length) {
close(fd);
free(mime_type);
log_event("HTTP/2: Memory allocation failed");
break;
}
snprintf(content_length, 32, "%ld", (long)st.st_size);
// Store content_length in stream_data for cleanup
stream_data->content_length = content_length;
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)mime_type, 12, strlen(mime_type), NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-length", (uint8_t *)content_length, 14, strlen(content_length), NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}
};
// Submit response with file data provider
nghttp2_data_provider data_prd;
data_prd.source.fd = fd;
data_prd.read_callback = file_read_callback;
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 4, &data_prd);
char log_msg[1024];
snprintf(log_msg, sizeof(log_msg), "HTTP/2: Response submitted for %s (%ld bytes)",
filepath, (long)st.st_size);
log_event(log_msg);
}
}
break;
case NGHTTP2_DATA:
log_event("HTTP/2: Received DATA frame");
break;
case NGHTTP2_SETTINGS:
log_event("HTTP/2: Received SETTINGS frame");
break;
default:
break;
}
return 0;
}
// Stream close callback
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
(void)error_code;
(void)user_data;
// Get stream data and clean up
http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id);
if (stream_data) {
if (stream_data->fd != -1) {
close(stream_data->fd);
}
if (stream_data->mime_type) {
free(stream_data->mime_type);
}
if (stream_data->content_length) {
free(stream_data->content_length);
}
free(stream_data);
}
log_event("HTTP/2: Stream closed");
return 0;
}
// Header callback
static int on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
uint8_t flags, void *user_data) {
(void)flags;
(void)user_data;
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
// Get stream data
http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if (!stream_data) {
return 0;
}
// Process request headers
if (namelen == 5 && memcmp(name, ":path", 5) == 0) {
size_t copy_len = valuelen < sizeof(stream_data->request_path) - 1 ?
valuelen : sizeof(stream_data->request_path) - 1;
memcpy(stream_data->request_path, value, copy_len);
stream_data->request_path[copy_len] = '\0';
char log_msg[512];
snprintf(log_msg, sizeof(log_msg), "HTTP/2: Request path: %s", stream_data->request_path);
log_event(log_msg);
}
return 0;
}
// Begin headers callback
static int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data) {
(void)session;
(void)user_data;
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
// Allocate stream data
http2_stream_data_t *stream_data = calloc(1, sizeof(http2_stream_data_t));
if (!stream_data) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
stream_data->stream_id = frame->hd.stream_id;
stream_data->fd = -1;
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data);
return 0;
}
// Data chunk receive callback
static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data) {
(void)session;
(void)flags;
(void)stream_id;
(void)data;
(void)len;
(void)user_data;
// Handle POST data if needed
return 0;
}
// Initialize HTTP/2 session
int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) {
h2_session->client_socket = client_socket;
h2_session->ssl = ssl;
h2_session->handshake_complete = false;
// Setup callbacks
nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
// Create server session
int rv = nghttp2_session_server_new(&h2_session->session, callbacks, h2_session);
nghttp2_session_callbacks_del(callbacks);
if (rv != 0) {
log_event("HTTP/2: Failed to create session");
return -1;
}
// Send initial SETTINGS frame
nghttp2_settings_entry settings[] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535}
};
rv = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE,
settings, sizeof(settings) / sizeof(settings[0]));
if (rv != 0) {
log_event("HTTP/2: Failed to submit settings");
nghttp2_session_del(h2_session->session);
return -1;
}
h2_session->handshake_complete = true;
log_event("HTTP/2: Session initialized");
return 0;
}
// Cleanup HTTP/2 session
void http2_session_cleanup(http2_session_t *h2_session) {
if (h2_session->session) {
nghttp2_session_del(h2_session->session);
h2_session->session = NULL;
}
}
// Send HTTP/2 response
int http2_send_response(http2_session_t *h2_session, int32_t stream_id,
const char *data, size_t len, bool end_stream) {
(void)data; // Unused in current implementation
(void)len; // Unused in current implementation
(void)end_stream; // Unused in current implementation
// Send response headers
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/html", 12, 9, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}
};
int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 3, NULL);
if (rv != 0) {
return -1;
}
return nghttp2_session_send(h2_session->session);
}
// Send HTTP/2 error response
int http2_send_error(http2_session_t *h2_session, int32_t stream_id,
int status_code, const char *message) {
char status_str[4];
snprintf(status_str, sizeof(status_str), "%d", status_code);
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
};
int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 2, NULL);
if (rv != 0) {
return -1;
}
if (message) {
nghttp2_data_provider prd;
prd.source.ptr = (void *)message;
prd.read_callback = NULL;
nghttp2_submit_data(h2_session->session, NGHTTP2_FLAG_END_STREAM,
stream_id, &prd);
}
return nghttp2_session_send(h2_session->session);
}
// Handle HTTP/2 connection
int http2_handle_connection(http2_session_t *h2_session) {
// Receive and process frames first
int rv = nghttp2_session_recv(h2_session->session);
if (rv != 0) {
if (rv == NGHTTP2_ERR_EOF) {
log_event("HTTP/2: Connection closed");
return 0;
}
char err_msg[128];
snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session receive failed: %s", nghttp2_strerror(rv));
log_event(err_msg);
return -1;
}
// Send all pending data
rv = nghttp2_session_send(h2_session->session);
if (rv != 0) {
char err_msg[128];
snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session send failed: %s", nghttp2_strerror(rv));
log_event(err_msg);
return -1;
}
// Check if session wants to terminate
if (nghttp2_session_want_read(h2_session->session) == 0 &&
nghttp2_session_want_write(h2_session->session) == 0) {
log_event("HTTP/2: Session terminated normally");
return 0;
}
return 1;
}

41
src/http2.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef HTTP2_H
#define HTTP2_H
#include <nghttp2/nghttp2.h>
#include <openssl/ssl.h>
#include <stdbool.h>
// HTTP/2 session context
typedef struct {
nghttp2_session *session;
SSL *ssl;
int client_socket;
bool handshake_complete;
} http2_session_t;
// HTTP/2 stream data
typedef struct {
int32_t stream_id;
char request_path[256];
char *request_method;
int fd; // File descriptor for response
size_t file_size;
char *mime_type;
char *content_length;
} http2_stream_data_t;
// Function prototypes
int http2_session_init(http2_session_t *session, int client_socket, SSL *ssl);
void http2_session_cleanup(http2_session_t *session);
int http2_handle_connection(http2_session_t *session);
int http2_send_response(http2_session_t *session, int32_t stream_id,
const char *data, size_t len, bool end_stream);
int http2_send_error(http2_session_t *session, int32_t stream_id,
int status_code, const char *message);
// ALPN callback for protocol selection
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg);
#endif

View File

@@ -21,6 +21,8 @@
#include <sys/sendfile.h>
#include "server_config.h"
#include "websocket.h"
#include "http2.h"
#define MAX_REQUEST_SIZE 8192
#define MAX_LOG_SIZE 2048
@@ -168,6 +170,12 @@ void configure_ssl_context(SSL_CTX *ctx) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// Enable HTTP/2 ALPN if configured
if (config.enable_http2) {
SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL);
log_event("HTTP/2 ALPN enabled");
}
}
void set_socket_options(int socket_fd) {
@@ -359,30 +367,148 @@ void *start_https_server(void *arg) {
pthread_exit(NULL);
}
// Check if request is a WebSocket upgrade request
static int is_websocket_upgrade(const char *request) {
// Make a lowercase copy for case-insensitive comparison
char *request_lower = strdup(request);
if (!request_lower) return 0;
for (char *p = request_lower; *p; p++) {
*p = tolower((unsigned char)*p);
}
// Check for "upgrade: websocket" and "connection:" containing "upgrade"
int has_upgrade = strstr(request_lower, "upgrade: websocket") != NULL;
int has_connection = strstr(request_lower, "connection:") != NULL &&
strstr(request_lower, "upgrade") != NULL;
free(request_lower);
return has_upgrade && has_connection;
}
// Handle WebSocket connection
static void *handle_websocket(void *arg) {
ws_connection_t *conn = (ws_connection_t *)arg;
log_event("WebSocket connection established");
uint8_t buffer[65536];
while (server_running && config.running) {
ssize_t bytes_received;
if (conn->is_ssl) {
bytes_received = SSL_read(conn->ssl, buffer, sizeof(buffer));
} else {
bytes_received = recv(conn->socket_fd, buffer, sizeof(buffer), 0);
}
if (bytes_received <= 0) {
break;
}
ws_frame_header_t header;
uint8_t *payload = NULL;
int parsed = ws_parse_frame(buffer, bytes_received, &header, &payload);
if (parsed < 0) {
log_event("Failed to parse WebSocket frame");
free(payload);
break;
}
switch (header.opcode) {
case WS_OPCODE_TEXT:
if (ws_is_valid_utf8(payload, header.payload_length)) {
// Echo back the text message
ws_send_text(conn, (const char *)payload);
log_event("WebSocket text frame received and echoed");
} else {
log_event("Invalid UTF-8 in text frame");
}
break;
case WS_OPCODE_BINARY:
// Echo back binary data
ws_send_frame(conn, WS_OPCODE_BINARY, payload, header.payload_length);
log_event("WebSocket binary frame received and echoed");
break;
case WS_OPCODE_PING:
ws_send_pong(conn, payload, header.payload_length);
log_event("WebSocket ping received, pong sent");
break;
case WS_OPCODE_CLOSE:
log_event("WebSocket close frame received");
free(payload);
ws_close_connection(conn, 1000);
free(conn);
pthread_exit(NULL);
default:
break;
}
free(payload);
}
ws_close_connection(conn, 1000);
free(conn);
pthread_exit(NULL);
}
void *handle_http_client(void *arg) {
int client_socket = *((int *)arg);
free(arg);
char request_buffer[MAX_REQUEST_SIZE];
ssize_t bytes_received = recv(client_socket, request_buffer, MAX_REQUEST_SIZE - 1, 0);
if (!server_running) {
close(client_socket); // Close socket before exiting
close(client_socket);
pthread_exit(NULL);
}
char request_buffer[MAX_REQUEST_SIZE];
memset(request_buffer, 0, MAX_REQUEST_SIZE);
ssize_t bytes_received = recv(client_socket, request_buffer, MAX_REQUEST_SIZE - 1, 0);
if (bytes_received > 0) {
request_buffer[bytes_received] = '\0';
log_event("Received HTTP request");
// Check for WebSocket upgrade request
if (config.enable_websocket && is_websocket_upgrade(request_buffer)) {
log_event("WebSocket upgrade request detected");
char response[512];
if (ws_handle_handshake(client_socket, request_buffer, response, sizeof(response)) == 0) {
send(client_socket, response, strlen(response), 0);
// Create WebSocket connection context
ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t));
if (ws_conn) {
ws_conn->socket_fd = client_socket;
ws_conn->ssl = NULL;
ws_conn->is_ssl = false;
ws_conn->handshake_complete = true;
// Handle WebSocket connection in this thread
handle_websocket(ws_conn);
} else {
close(client_socket);
}
} else {
log_event("WebSocket handshake failed");
close(client_socket);
}
pthread_exit(NULL);
}
char method[8], url[256], protocol[16];
if (parse_request_line(request_buffer, method, url, protocol) != 0) {
log_event("Invalid request line.");
const char *bad_request_response = "HTTP/1.1 400 Bad Request\r\n\r\nInvalid Request";
send(client_socket, bad_request_response, strlen(bad_request_response), 0);
close(client_socket);
return NULL;
pthread_exit(NULL);
}
if (config.use_https) { // Check if HTTPS is enabled
@@ -411,7 +537,8 @@ void *handle_http_client(void *arg) {
log_event("Blocked malicious URL");
const char *forbidden_response = "HTTP/1.1 403 Forbidden\r\n\r\nAccess Denied";
send(client_socket, forbidden_response, strlen(forbidden_response), 0);
return NULL;
close(client_socket);
pthread_exit(NULL);
}
char client_ip[INET_ADDRSTRLEN];
@@ -511,15 +638,6 @@ void *handle_https_client(void *arg) {
pthread_exit(NULL);
}
if (SSL_accept(ssl) <= 0) {
perror("SSL_accept error");
ERR_print_errors_fp(stderr);
log_event("SSL handshake failed.");
SSL_free(ssl); // Free SSL context on failure
close(client_socket);
pthread_exit(NULL);
}
if (SSL_accept(ssl) <= 0) {
int ssl_error = SSL_get_error(ssl, -1);
char error_msg[256];
@@ -534,7 +652,48 @@ void *handle_https_client(void *arg) {
log_event("SSL handshake successful!");
// Check if HTTP/2 was negotiated via ALPN
if (config.enable_http2) {
const unsigned char *alpn_data = NULL;
unsigned int alpn_len = 0;
SSL_get0_alpn_selected(ssl, &alpn_data, &alpn_len);
if (alpn_data && alpn_len == 2 && memcmp(alpn_data, "h2", 2) == 0) {
log_event("HTTP/2 protocol negotiated via ALPN");
// Set socket to non-blocking mode for HTTP/2
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
// Initialize HTTP/2 session
http2_session_t h2_session;
if (http2_session_init(&h2_session, client_socket, ssl) == 0) {
// Handle HTTP/2 connection
while (server_running) {
int result = http2_handle_connection(&h2_session);
if (result <= 0) {
break; // Connection closed or error
}
// Small delay to avoid busy loop
usleep(1000); // 1ms
}
http2_session_cleanup(&h2_session);
} else {
log_event("Failed to initialize HTTP/2 session");
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(client_socket);
pthread_exit(NULL);
}
}
char buffer[MAX_REQUEST_SIZE];
memset(buffer, 0, MAX_REQUEST_SIZE);
ssize_t bytes_received = SSL_read(ssl, buffer, MAX_REQUEST_SIZE - 1);
if (bytes_received < 0) {
@@ -551,6 +710,37 @@ void *handle_https_client(void *arg) {
log_event(buffer);
}
// Check for WebSocket upgrade request on HTTPS
if (config.enable_websocket && is_websocket_upgrade(buffer)) {
log_event("Secure WebSocket upgrade request detected");
char response[512];
if (ws_handle_handshake_ssl(ssl, buffer, response, sizeof(response)) == 0) {
SSL_write(ssl, response, strlen(response));
// Create WebSocket connection context
ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t));
if (ws_conn) {
ws_conn->socket_fd = client_socket;
ws_conn->ssl = ssl;
ws_conn->is_ssl = true;
ws_conn->handshake_complete = true;
// Handle WebSocket connection in this thread
handle_websocket(ws_conn);
pthread_exit(NULL);
} else {
SSL_shutdown(ssl);
SSL_free(ssl);
close(client_socket);
pthread_exit(NULL);
}
} else {
log_event("Secure WebSocket handshake failed");
goto cleanup;
}
}
char method[8], url[256], protocol[16];
if (parse_request_line(buffer, method, url, protocol) != 0) {
log_event("Invalid request line.");
@@ -1054,7 +1244,13 @@ int check_rate_limit(const char *ip) {
}
// Add new entry
rate_limits = realloc(rate_limits, (rate_limit_count + 1) * sizeof(RateLimit));
RateLimit *new_limits = realloc(rate_limits, (rate_limit_count + 1) * sizeof(RateLimit));
if (!new_limits) {
pthread_mutex_unlock(&rate_limit_mutex);
return 0; // Memory allocation failed, deny request
}
rate_limits = new_limits;
size_t ip_len = strlen(ip);
if (ip_len >= INET_ADDRSTRLEN) {
ip_len = INET_ADDRSTRLEN - 1;

View File

@@ -11,4 +11,6 @@ void init_config(ServerConfig *config) {
config->automatic_startup = false;
config->verbose = 0;
strcpy(config->server_name, "127.0.0.1");
config->enable_http2 = false;
config->enable_websocket = false;
}

View File

@@ -12,6 +12,8 @@ typedef struct {
bool automatic_startup;
char server_name[256];
int verbose;
bool enable_http2;
bool enable_websocket;
} ServerConfig;
int load_config(const char *filename, ServerConfig *config);

259
src/websocket.c Normal file
View File

@@ -0,0 +1,259 @@
#include "websocket.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <arpa/inet.h>
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
// Base64 encode function
static char* base64_encode(const unsigned char *input, int length) {
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, input, length);
BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
char *buff = (char *)malloc(bptr->length + 1);
memcpy(buff, bptr->data, bptr->length);
buff[bptr->length] = '\0';
BIO_free_all(b64);
return buff;
}
// Generate WebSocket accept key from client key
char* ws_generate_accept_key(const char *client_key) {
char combined[256];
snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID);
unsigned char hash[SHA_DIGEST_LENGTH];
SHA1((unsigned char*)combined, strlen(combined), hash);
return base64_encode(hash, SHA_DIGEST_LENGTH);
}
// Handle WebSocket handshake
int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size) {
(void)client_socket; // Unused in this implementation
// Extract Sec-WebSocket-Key from request
const char *key_header = "Sec-WebSocket-Key: ";
char *key_start = strstr(request, key_header);
if (!key_start) {
return -1;
}
key_start += strlen(key_header);
char *key_end = strstr(key_start, "\r\n");
if (!key_end) {
return -1;
}
char client_key[256];
size_t key_len = key_end - key_start;
if (key_len >= sizeof(client_key)) {
return -1;
}
memcpy(client_key, key_start, key_len);
client_key[key_len] = '\0';
// Generate accept key
char *accept_key = ws_generate_accept_key(client_key);
if (!accept_key) {
return -1;
}
// Create handshake response
snprintf(response, response_size,
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"\r\n",
accept_key);
free(accept_key);
return 0;
}
// Handle WebSocket handshake for SSL connections
int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size) {
(void)ssl; // Use the same logic, just different transport
return ws_handle_handshake(0, request, response, response_size);
}
// Parse WebSocket frame
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload) {
if (len < 2) {
return -1;
}
header->fin = (data[0] & 0x80) >> 7;
header->opcode = data[0] & 0x0F;
header->mask = (data[1] & 0x80) >> 7;
size_t offset = 2;
uint8_t payload_len = data[1] & 0x7F;
if (payload_len == 126) {
if (len < 4) return -1;
header->payload_length = (data[2] << 8) | data[3];
offset = 4;
} else if (payload_len == 127) {
if (len < 10) return -1;
header->payload_length = 0;
for (int i = 0; i < 8; i++) {
header->payload_length = (header->payload_length << 8) | data[2 + i];
}
offset = 10;
} else {
header->payload_length = payload_len;
}
if (header->mask) {
if (len < offset + 4) return -1;
memcpy(header->masking_key, data + offset, 4);
offset += 4;
}
if (len < offset + header->payload_length) {
return -1;
}
// Unmask payload if masked
*payload = (uint8_t*)malloc(header->payload_length);
if (!*payload) {
return -1;
}
if (header->mask) {
for (uint64_t i = 0; i < header->payload_length; i++) {
(*payload)[i] = data[offset + i] ^ header->masking_key[i % 4];
}
} else {
memcpy(*payload, data + offset, header->payload_length);
}
return offset + header->payload_length;
}
// Create WebSocket frame
int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len) {
size_t offset = 0;
// First byte: FIN + opcode
buffer[offset++] = 0x80 | (opcode & 0x0F);
// Second byte: MASK + payload length
if (payload_len < 126) {
if (buffer_size < offset + 1 + payload_len) return -1;
buffer[offset++] = payload_len;
} else if (payload_len < 65536) {
if (buffer_size < offset + 3 + payload_len) return -1;
buffer[offset++] = 126;
buffer[offset++] = (payload_len >> 8) & 0xFF;
buffer[offset++] = payload_len & 0xFF;
} else {
if (buffer_size < offset + 9 + payload_len) return -1;
buffer[offset++] = 127;
for (int i = 7; i >= 0; i--) {
buffer[offset++] = (payload_len >> (i * 8)) & 0xFF;
}
}
// Copy payload
if (payload && payload_len > 0) {
memcpy(buffer + offset, payload, payload_len);
offset += payload_len;
}
return offset;
}
// Send WebSocket frame
int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len) {
// Allocate buffer with enough space for header (max 10 bytes) + payload
size_t max_frame_size = 10 + payload_len;
if (max_frame_size > 65536) {
max_frame_size = 65536;
}
uint8_t buffer[65536];
// Limit payload to avoid overflow (65536 - 10 bytes for max header)
size_t safe_payload_len = payload_len;
if (safe_payload_len > 65526) {
safe_payload_len = 65526;
}
int frame_len = ws_create_frame(buffer, sizeof(buffer), opcode, payload, safe_payload_len);
if (frame_len < 0) {
return -1;
}
if (conn->is_ssl && conn->ssl) {
return SSL_write(conn->ssl, buffer, frame_len);
} else {
return write(conn->socket_fd, buffer, frame_len);
}
}
// Send text message
int ws_send_text(ws_connection_t *conn, const char *text) {
return ws_send_frame(conn, WS_OPCODE_TEXT, (const uint8_t*)text, strlen(text));
}
// Send pong response
int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len) {
return ws_send_frame(conn, WS_OPCODE_PONG, payload, payload_len);
}
// Close WebSocket connection
void ws_close_connection(ws_connection_t *conn, uint16_t status_code) {
uint8_t close_payload[2];
close_payload[0] = (status_code >> 8) & 0xFF;
close_payload[1] = status_code & 0xFF;
ws_send_frame(conn, WS_OPCODE_CLOSE, close_payload, 2);
if (conn->is_ssl && conn->ssl) {
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
}
close(conn->socket_fd);
}
// Validate UTF-8 encoding
bool ws_is_valid_utf8(const uint8_t *data, size_t len) {
size_t i = 0;
while (i < len) {
if (data[i] < 0x80) {
i++;
} else if ((data[i] & 0xE0) == 0xC0) {
if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) return false;
i += 2;
} else if ((data[i] & 0xF0) == 0xE0) {
if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false;
i += 3;
} else if ((data[i] & 0xF8) == 0xF0) {
if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false;
i += 4;
} else {
return false;
}
}
return true;
}

47
src/websocket.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef WEBSOCKET_H
#define WEBSOCKET_H
#include <stdint.h>
#include <stdbool.h>
#include <openssl/ssl.h>
// WebSocket opcodes
#define WS_OPCODE_CONTINUATION 0x0
#define WS_OPCODE_TEXT 0x1
#define WS_OPCODE_BINARY 0x2
#define WS_OPCODE_CLOSE 0x8
#define WS_OPCODE_PING 0x9
#define WS_OPCODE_PONG 0xA
// WebSocket frame header structure
typedef struct {
uint8_t fin;
uint8_t opcode;
uint8_t mask;
uint64_t payload_length;
uint8_t masking_key[4];
} ws_frame_header_t;
// WebSocket connection context
typedef struct {
int socket_fd;
SSL *ssl;
bool is_ssl;
bool handshake_complete;
} ws_connection_t;
// Function prototypes
int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size);
int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size);
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload);
int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len);
int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len);
int ws_send_text(ws_connection_t *conn, const char *text);
int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len);
void ws_close_connection(ws_connection_t *conn, uint16_t status_code);
// Helper functions
char* ws_generate_accept_key(const char *client_key);
bool ws_is_valid_utf8(const uint8_t *data, size_t len);
#endif