Enhance server ssl configuration

- Added SSL certificate and key paths to ServerConfig structure.
- Updated init_config function to initialize new SSL paths.
- Formated code for better readability.
This commit is contained in:
2025-10-05 17:35:00 +00:00
parent 46b653efe0
commit 72df6a73fc
13 changed files with 1420 additions and 1000 deletions

2
.gitignore vendored
View File

@@ -52,4 +52,4 @@ Mkfile.old
dkms.conf dkms.conf
log/* log/*
server server
certs/*.pem ssl/*

View File

@@ -118,24 +118,6 @@ make
# Run the server # Run the server
sudo ./server sudo ./server
``` ```
### Using SSL certificate or HTTPS, WebSocket, HTTP/2
```bash
# Generate SSL certificates (optional)
mkdir -p certs
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout certs/key.pem -out certs/cert.pem \
-subj "/C=US/ST=State/L=City/O=Carbon/CN=localhost"
# Test HTTP/2
curl --http2 -k https://localhost:443/
# to use WebSocket (edit server.conf)
# Set: use_https = true, enable_http2 = true, enable_websocket = true
# Test WebSocket
# Visit https://localhost:443/websocket-test.html in your browser
```
### Build Options ### Build Options
@@ -167,12 +149,12 @@ gcc src/server.c src/config_parser.c src/server_config.c src/websocket.c src/htt
```bash ```bash
# Create certificates directory # Create certificates directory
mkdir -p certs mkdir -p ssl ssl/certs ssl/key
# Generate self-signed certificate (for testing only) # Generate self-signed certificate (for testing only)
openssl req -x509 -newkey rsa:2048 \ openssl req -x509 -newkey rsa:2048 \
-keyout certs/key.pem \ -keyout ssl/key/key.key \
-out certs/cert.pem \ -out ssl/cert/cert.pem \
-days 365 -nodes \ -days 365 -nodes \
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
``` ```
@@ -223,6 +205,8 @@ enable_websocket = false
- `max_threads`: Number of worker threads - `max_threads`: Number of worker threads
- `server_name`: Your domain or IP address - `server_name`: Your domain or IP address
- `verbose`: Enable detailed logging - accepts: true/false, yes/no, on/off, 1/0 - `verbose`: Enable detailed logging - accepts: true/false, yes/no, on/off, 1/0
- `ssl_cert_path`: Path to ssl certificate
- `ssl_key_path`: Path to ssl key
**Note:** Boolean values are flexible and accept multiple formats: **Note:** Boolean values are flexible and accept multiple formats:
- True: `true`, `yes`, `on`, `1` - True: `true`, `yes`, `on`, `1`

View File

@@ -7,9 +7,9 @@ running = true
# Server listening port # Server listening port
port = 8080 port = 8080
# Enable HTTPS (requires valid certificates in certs/ directory) # Enable HTTPS (requires valid certificates in certs/ directory)
use_https = false use_https = false
# Enable HTTP/2 support (requires HTTPS) # Enable HTTP/2 support (requires HTTPS)
enable_http2 = false enable_http2 = false
# Enable WebSocket support # Enable WebSocket support
enable_websocket = false enable_websocket = false
# Server name or IP address (used for logging and response headers) # Server name or IP address (used for logging and response headers)
@@ -21,12 +21,14 @@ server_name = Your_domain/IP
max_threads = 4 max_threads = 4
max_connections = 1024 max_connections = 1024
# ---Path configuration--- # ---Path configuration---
# Log file location # Log file location
log_file = log/server.log log_file = log/server.log
# Enable verbose logging # Enable verbose logging
verbose = true verbose = true
# Path to www # Path to www
www_path = www www_path = www
# path to public ssl certification
ssl_cert_path = ssl/cert/cert.pem
# path to private ssl key
ssl_key_path = ssl/key/key.key

View File

@@ -5,7 +5,8 @@
#include <ctype.h> #include <ctype.h>
#include "server_config.h" #include "server_config.h"
typedef enum { typedef enum
{
CONFIG_PORT, CONFIG_PORT,
CONFIG_USE_HTTPS, CONFIG_USE_HTTPS,
CONFIG_LOG_FILE, CONFIG_LOG_FILE,
@@ -17,40 +18,49 @@ typedef enum {
CONFIG_ENABLE_WEBSOCKET, CONFIG_ENABLE_WEBSOCKET,
CONFIG_WWW_PATH, CONFIG_WWW_PATH,
CONFIG_MAX_CONNECTIONS, CONFIG_MAX_CONNECTIONS,
CONFIG_SSL_CERT_PATH,
CONFIG_SSL_KEY_PATH,
CONFIG_UNKNOWN CONFIG_UNKNOWN
} ConfigKey; } ConfigKey;
// Trim whitespace from both ends of a string // Trim whitespace from both ends of a string
static char* trim_whitespace(char *str) { static char *trim_whitespace(char *str)
{
char *end; char *end;
// Trim leading space // Trim leading space
while(isspace((unsigned char)*str)) str++; while (isspace((unsigned char)*str))
str++;
if(*str == 0) return str;
if (*str == 0)
return str;
// Trim trailing space // Trim trailing space
end = str + strlen(str) - 1; end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--; while (end > str && isspace((unsigned char)*end))
end--;
end[1] = '\0'; end[1] = '\0';
return str; return str;
} }
// Parse a boolean value (true/false, yes/no, on/off, 1/0) // Parse a boolean value (true/false, yes/no, on/off, 1/0)
static bool parse_bool(const char *value) { static bool parse_bool(const char *value)
if (strcasecmp(value, "true") == 0 || {
strcasecmp(value, "yes") == 0 || if (strcasecmp(value, "true") == 0 ||
strcasecmp(value, "yes") == 0 ||
strcasecmp(value, "on") == 0 || strcasecmp(value, "on") == 0 ||
strcmp(value, "1") == 0) { strcmp(value, "1") == 0)
{
return true; return true;
} }
return false; return false;
} }
// Map string to enum // Map string to enum
static ConfigKey get_config_key(const char *key) { static ConfigKey get_config_key(const char *key)
static const struct {
static const struct
{ {
const char *name; const char *name;
ConfigKey key; ConfigKey key;
@@ -63,23 +73,29 @@ static ConfigKey get_config_key(const char *key) {
{"server_name", CONFIG_SERVER_NAME}, {"server_name", CONFIG_SERVER_NAME},
{"verbose", CONFIG_VERBOSE}, {"verbose", CONFIG_VERBOSE},
{"enable_http2", CONFIG_ENABLE_HTTP2}, {"enable_http2", CONFIG_ENABLE_HTTP2},
{"enable_websocket",CONFIG_ENABLE_WEBSOCKET}, {"enable_websocket", CONFIG_ENABLE_WEBSOCKET},
{"www_path", CONFIG_WWW_PATH}, {"www_path", CONFIG_WWW_PATH},
{"max_connections", CONFIG_MAX_CONNECTIONS}, {"max_connections", CONFIG_MAX_CONNECTIONS},
{"ssl_cert_path", CONFIG_SSL_CERT_PATH},
{"ssl_key_path", CONFIG_SSL_KEY_PATH},
{NULL, CONFIG_UNKNOWN} {NULL, CONFIG_UNKNOWN}
}; };
for (int i = 0;key_map[i].name != NULL; i++) { for (int i = 0; key_map[i].name != NULL; i++)
if (strcasecmp(key, key_map[i].name) == 0) { {
if (strcasecmp(key, key_map[i].name) == 0)
{
return key_map[i].key; return key_map[i].key;
} }
} }
return CONFIG_UNKNOWN; return CONFIG_UNKNOWN;
} }
int load_config(const char *filename, ServerConfig *config) { int load_config(const char *filename, ServerConfig *config)
{
FILE *fp = fopen(filename, "r"); FILE *fp = fopen(filename, "r");
if (!fp) { if (!fp)
{
perror("Error opening config file"); perror("Error opening config file");
return 1; return 1;
} }
@@ -87,115 +103,146 @@ int load_config(const char *filename, ServerConfig *config) {
char line[512]; char line[512];
int line_number = 0; int line_number = 0;
while (fgets(line, sizeof(line), fp)) { while (fgets(line, sizeof(line), fp))
line_number++; {
line_number++;
// Remove newline
line[strcspn(line, "\r\n")] = 0; // Remove newline
line[strcspn(line, "\r\n")] = 0;
// Trim whitespace
char *trimmed = trim_whitespace(line); // Trim whitespace
char *trimmed = trim_whitespace(line);
// Skip empty lines and comments
if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') { // Skip empty lines and comments
continue; if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';')
} {
continue;
// Find the delimiter (= or space) }
char *delim = strchr(trimmed, '=');
if (!delim) { // Find the delimiter (= or space)
// Try space as delimiter char *delim = strchr(trimmed, '=');
delim = strchr(trimmed, ' '); if (!delim)
} {
// Try space as delimiter
if (!delim) { delim = strchr(trimmed, ' ');
fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed); }
continue;
} if (!delim)
{
// Split into key and value fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed);
*delim = '\0'; continue;
char *key = trim_whitespace(trimmed); }
char *value = trim_whitespace(delim + 1);
// Split into key and value
// Remove quotes from value if present *delim = '\0';
if ((value[0] == '"' || value[0] == '\'') && char *key = trim_whitespace(trimmed);
value[strlen(value) - 1] == value[0]) { char *value = trim_whitespace(delim + 1);
value[strlen(value) - 1] = '\0';
value++; // Remove quotes from value if present
} if ((value[0] == '"' || value[0] == '\'') &&
// Parse configuration options value[strlen(value) - 1] == value[0])
switch (get_config_key(key)) { {
case CONFIG_PORT: value[strlen(value) - 1] = '\0';
config->port = atoi(value); value++;
printf("load_config: port = %d\n", config->port); }
// Parse configuration options
switch (get_config_key(key))
{
case CONFIG_PORT:
config->port = atoi(value);
printf("load_config: port = %d\n", config->port);
break; break;
case CONFIG_USE_HTTPS: case CONFIG_USE_HTTPS:
config->use_https = parse_bool(value); config->use_https = parse_bool(value);
printf("load_config: use_https = %d\n", config->use_https); printf("load_config: use_https = %d\n", config->use_https);
break; break;
case CONFIG_LOG_FILE: case CONFIG_LOG_FILE:
strncpy(config->log_file, value, sizeof(config->log_file) - 1); strncpy(config->log_file, value, sizeof(config->log_file) - 1);
config->log_file[sizeof(config->log_file) - 1] = '\0'; config->log_file[sizeof(config->log_file) - 1] = '\0';
printf("load_config: log_file = %s\n", config->log_file); printf("load_config: log_file = %s\n", config->log_file);
break; break;
case CONFIG_MAX_THREADS: case CONFIG_MAX_THREADS:
config->max_threads = atoi(value); config->max_threads = atoi(value);
printf("load_config: max_threads = %d\n", config->max_threads); printf("load_config: max_threads = %d\n", config->max_threads);
break; break;
case CONFIG_RUNNING: case CONFIG_RUNNING:
config->running = parse_bool(value); config->running = parse_bool(value);
if (config->running == 0 || false) { if (!config->running)
fprintf(stderr, "ERROR: current run state is false cannot run the server!\n"); {
exit(EXIT_FAILURE); fprintf(stderr, "ERROR: current run state is false cannot run the server!\n");
} exit(EXIT_FAILURE);
printf("load_config: running = %d\n", config->running); }
printf("load_config: running = %d\n", config->running);
break; break;
case CONFIG_SERVER_NAME: case CONFIG_SERVER_NAME:
strncpy(config->server_name, value, sizeof(config->server_name) - 1); strncpy(config->server_name, value, sizeof(config->server_name) - 1);
config->server_name[sizeof(config->server_name) - 1] = '\0'; config->server_name[sizeof(config->server_name) - 1] = '\0';
printf("load_config: server_name = %s\n", config->server_name); printf("load_config: server_name = %s\n", config->server_name);
if (strcmp(config->server_name, "Your_domain/IP") == 0) { if (strcmp(config->server_name, "Your_domain/IP") == 0)
fprintf(stderr, "WARNING: server_name is set to default\n" {
"Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n"); fprintf(stderr, "WARNING: server_name is set to default\n"
} "Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n");
}
break;
case CONFIG_VERBOSE:
config->verbose = parse_bool(value);
printf("load_config: verbose = %d\n", config->verbose);
break; break;
case CONFIG_VERBOSE: case CONFIG_ENABLE_HTTP2:
config->verbose = parse_bool(value); config->enable_http2 = parse_bool(value);
printf("load_config: verbose = %d\n", config->verbose); if (!config->use_https && config->enable_http2)
break; {
printf("Error: Cannot load HTTP/2 while HTTPS is not enabled!\n");
case CONFIG_ENABLE_HTTP2: exit(EXIT_FAILURE);
config->enable_http2 = parse_bool(value); }
else
{
printf("load_config: enable_http2 = %d\n", config->enable_http2); printf("load_config: enable_http2 = %d\n", config->enable_http2);
}
break;
case CONFIG_ENABLE_WEBSOCKET:
config->enable_websocket = parse_bool(value);
printf("load_config: enable_websocket = %d\n", config->enable_websocket);
break; break;
case CONFIG_ENABLE_WEBSOCKET: case CONFIG_WWW_PATH:
config->enable_websocket = parse_bool(value); strncpy(config->www_path, value, sizeof(config->www_path) - 1);
printf("load_config: enable_websocket = %d\n", config->enable_websocket); config->www_path[sizeof(config->www_path) - 1] = '\0';
printf("load_config: www_path = %s\n", config->www_path);
break; break;
case CONFIG_WWW_PATH: case CONFIG_MAX_CONNECTIONS:
strncpy(config->www_path, value, sizeof(config->www_path) - 1); config->max_connections = atoi(value);
config->www_path[sizeof(config->www_path) - 1] = '\0'; printf("load_config: max_connections: = %d\n", config->max_connections);
printf("load_config: www_path = %s\n", config->www_path);
break;
case CONFIG_MAX_CONNECTIONS:
config->max_connections = atoi(value);
printf("load_config: max_connections: = %d\n", config->max_connections);
break; break;
case CONFIG_UNKNOWN: case CONFIG_SSL_CERT_PATH:
default: if (config->use_https)
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number); {
strncpy(config->ssl_cert_path, value, sizeof(config->ssl_cert_path) - 1);
config->ssl_cert_path[sizeof(config->ssl_cert_path) - 1] = '\0';
printf("load_config: ssl_cert_path = %s\n", config->ssl_cert_path);
}
break;
case CONFIG_SSL_KEY_PATH:
if (config->use_https)
{
strncpy(config->ssl_key_path, value, sizeof(config->ssl_key_path) - 1);
config->ssl_key_path[sizeof(config->ssl_key_path) - 1] = '\0';
printf("load_config: ssl_key_path = %s\n", config->ssl_key_path);
}
break;
case CONFIG_UNKNOWN:
default:
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number);
break; break;
} }
} }

View File

@@ -10,27 +10,31 @@
extern ServerConfig config; extern ServerConfig config;
extern void log_event(const char *message); extern void log_event(const char *message);
extern char* get_mime_type(const char *filepath); extern char *get_mime_type(const char *filepath);
extern char* sanitize_url(const char *url); extern char *sanitize_url(const char *url);
// ALPN callback - select HTTP/2 protocol // ALPN callback - select HTTP/2 protocol
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) { unsigned int inlen, void *arg)
{
(void)ssl; (void)ssl;
(void)arg; (void)arg;
int ret = nghttp2_select_next_protocol((unsigned char **)out, outlen, int ret = nghttp2_select_next_protocol((unsigned char **)out, outlen,
in, inlen); in, inlen);
if (ret == 1) { if (ret == 1)
{
// HTTP/2 selected // HTTP/2 selected
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} else if (ret == 0) { }
else if (ret == 0)
{
// HTTP/1.1 selected // HTTP/1.1 selected
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
// No match, use HTTP/1.1 as fallback // No match, use HTTP/1.1 as fallback
*out = (const unsigned char *)"http/1.1"; *out = (const unsigned char *)"http/1.1";
*outlen = 8; *outlen = 8;
@@ -39,252 +43,285 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
// Data read callback for nghttp2 // Data read callback for nghttp2
static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
uint8_t *buf, size_t length, uint8_t *buf, size_t length,
uint32_t *data_flags, uint32_t *data_flags,
nghttp2_data_source *source, nghttp2_data_source *source,
void *user_data) { void *user_data)
{
(void)session; (void)session;
(void)stream_id; (void)stream_id;
(void)user_data; (void)user_data;
int fd = source->fd; int fd = source->fd;
ssize_t nread; ssize_t nread;
while ((nread = read(fd, buf, length)) == -1 && errno == EINTR); while ((nread = read(fd, buf, length)) == -1 && errno == EINTR)
;
if (nread == -1) {
if (nread == -1)
{
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
if (nread == 0) { if (nread == 0)
{
*data_flags |= NGHTTP2_DATA_FLAG_EOF; *data_flags |= NGHTTP2_DATA_FLAG_EOF;
} }
return nread; return nread;
} }
// Send callback for nghttp2 // Send callback for nghttp2
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
size_t length, int flags, void *user_data) { size_t length, int flags, void *user_data)
{
(void)session; (void)session;
(void)flags; (void)flags;
http2_session_t *h2_session = (http2_session_t *)user_data; http2_session_t *h2_session = (http2_session_t *)user_data;
ssize_t rv; ssize_t rv;
if (h2_session->ssl) { if (h2_session->ssl)
{
rv = SSL_write(h2_session->ssl, data, (int)length); rv = SSL_write(h2_session->ssl, data, (int)length);
if (rv < 0) { if (rv < 0)
{
int ssl_error = SSL_get_error(h2_session->ssl, rv); int ssl_error = SSL_get_error(h2_session->ssl, rv);
if (ssl_error == SSL_ERROR_WANT_WRITE) { 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_WOULDBLOCK;
} }
return NGHTTP2_ERR_CALLBACK_FAILURE; 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; return rv;
} }
// Receive callback for nghttp2 // Receive callback for nghttp2
static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf,
size_t length, int flags, void *user_data) { size_t length, int flags, void *user_data)
{
(void)session; (void)session;
(void)flags; (void)flags;
http2_session_t *h2_session = (http2_session_t *)user_data; http2_session_t *h2_session = (http2_session_t *)user_data;
ssize_t rv; ssize_t rv;
if (h2_session->ssl) { if (h2_session->ssl)
{
rv = SSL_read(h2_session->ssl, buf, (int)length); rv = SSL_read(h2_session->ssl, buf, (int)length);
if (rv < 0) { if (rv < 0)
{
int ssl_error = SSL_get_error(h2_session->ssl, rv); int ssl_error = SSL_get_error(h2_session->ssl, rv);
if (ssl_error == SSL_ERROR_WANT_READ) { if (ssl_error == SSL_ERROR_WANT_READ)
{
return NGHTTP2_ERR_WOULDBLOCK; return NGHTTP2_ERR_WOULDBLOCK;
} }
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
if (rv == 0) { 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 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; return rv;
} }
// Frame receive callback // Frame receive callback
static int on_frame_recv_callback(nghttp2_session *session, static int on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
void *user_data) { void *user_data)
{
(void)user_data; (void)user_data;
switch (frame->hd.type) { switch (frame->hd.type)
case NGHTTP2_HEADERS: {
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { case NGHTTP2_HEADERS:
log_event("HTTP/2: Received HEADERS frame (request)"); if (frame->headers.cat == NGHTTP2_HCAT_REQUEST)
{
// Get stream data log_event("HTTP/2: Received HEADERS frame (request)");
http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); // Get stream data
http2_stream_data_t *stream_data =
if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
// Request is complete, send response
char *path = stream_data->request_path; if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM))
if (strlen(path) == 0) { {
strcpy(path, "/"); // Request is complete, send response
} char *path = stream_data->request_path;
if (strlen(path) == 0)
// Sanitize URL {
char *sanitized = sanitize_url(path); strcpy(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);
} }
// 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: break;
log_event("HTTP/2: Received DATA frame"); case NGHTTP2_DATA:
break; log_event("HTTP/2: Received DATA frame");
case NGHTTP2_SETTINGS: break;
log_event("HTTP/2: Received SETTINGS frame"); case NGHTTP2_SETTINGS:
break; log_event("HTTP/2: Received SETTINGS frame");
default: break;
break; default:
break;
} }
return 0; return 0;
} }
// Stream close callback // Stream close callback
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) { uint32_t error_code, void *user_data)
{
(void)error_code; (void)error_code;
(void)user_data; (void)user_data;
// Get stream data and clean up // Get stream data and clean up
http2_stream_data_t *stream_data = http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id); (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id);
if (stream_data) { if (stream_data)
if (stream_data->fd != -1) { {
if (stream_data->fd != -1)
{
close(stream_data->fd); close(stream_data->fd);
} }
if (stream_data->mime_type) { if (stream_data->mime_type)
{
free(stream_data->mime_type); free(stream_data->mime_type);
} }
if (stream_data->content_length) { if (stream_data->content_length)
{
free(stream_data->content_length); free(stream_data->content_length);
} }
free(stream_data); free(stream_data);
} }
log_event("HTTP/2: Stream closed"); log_event("HTTP/2: Stream closed");
return 0; return 0;
} }
@@ -293,89 +330,97 @@ static int on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
uint8_t flags, void *user_data) { uint8_t flags, void *user_data)
{
(void)flags; (void)flags;
(void)user_data; (void)user_data;
if (frame->hd.type != NGHTTP2_HEADERS || if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) { frame->headers.cat != NGHTTP2_HCAT_REQUEST)
{
return 0; return 0;
} }
// Get stream data // Get stream data
http2_stream_data_t *stream_data = http2_stream_data_t *stream_data =
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if (!stream_data) { if (!stream_data)
{
return 0; return 0;
} }
// Process request headers // Process request headers
if (namelen == 5 && memcmp(name, ":path", 5) == 0) { 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; 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); memcpy(stream_data->request_path, value, copy_len);
stream_data->request_path[copy_len] = '\0'; stream_data->request_path[copy_len] = '\0';
char log_msg[512]; char log_msg[512];
snprintf(log_msg, sizeof(log_msg), "HTTP/2: Request path: %s", stream_data->request_path); snprintf(log_msg, sizeof(log_msg), "HTTP/2: Request path: %s", stream_data->request_path);
log_event(log_msg); log_event(log_msg);
} }
return 0; return 0;
} }
// Begin headers callback // Begin headers callback
static int on_begin_headers_callback(nghttp2_session *session, static int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
void *user_data) { void *user_data)
{
(void)session; (void)session;
(void)user_data; (void)user_data;
if (frame->hd.type != NGHTTP2_HEADERS || if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) { frame->headers.cat != NGHTTP2_HCAT_REQUEST)
{
return 0; return 0;
} }
// Allocate stream data // Allocate stream data
http2_stream_data_t *stream_data = calloc(1, sizeof(http2_stream_data_t)); http2_stream_data_t *stream_data = calloc(1, sizeof(http2_stream_data_t));
if (!stream_data) { if (!stream_data)
{
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
stream_data->stream_id = frame->hd.stream_id; stream_data->stream_id = frame->hd.stream_id;
stream_data->fd = -1; stream_data->fd = -1;
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data);
return 0; return 0;
} }
// Data chunk receive callback // Data chunk receive callback
static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data, int32_t stream_id, const uint8_t *data,
size_t len, void *user_data) { size_t len, void *user_data)
{
(void)session; (void)session;
(void)flags; (void)flags;
(void)stream_id; (void)stream_id;
(void)data; (void)data;
(void)len; (void)len;
(void)user_data; (void)user_data;
// Handle POST data if needed // Handle POST data if needed
return 0; return 0;
} }
// Initialize HTTP/2 session // Initialize HTTP/2 session
int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) { int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl)
{
h2_session->client_socket = client_socket; h2_session->client_socket = client_socket;
h2_session->ssl = ssl; h2_session->ssl = ssl;
h2_session->handshake_complete = false; h2_session->handshake_complete = false;
// Setup callbacks // Setup callbacks
nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks); nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_recv_callback(callbacks, recv_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_frame_recv_callback(callbacks, on_frame_recv_callback);
@@ -383,39 +428,42 @@ int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl)
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_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_begin_headers_callback(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
// Create server session // Create server session
int rv = nghttp2_session_server_new(&h2_session->session, callbacks, h2_session); int rv = nghttp2_session_server_new(&h2_session->session, callbacks, h2_session);
nghttp2_session_callbacks_del(callbacks); nghttp2_session_callbacks_del(callbacks);
if (rv != 0) { if (rv != 0)
{
log_event("HTTP/2: Failed to create session"); log_event("HTTP/2: Failed to create session");
return -1; return -1;
} }
// Send initial SETTINGS frame // Send initial SETTINGS frame
nghttp2_settings_entry settings[] = { nghttp2_settings_entry settings[] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535} {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535}};
};
rv = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE, rv = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE,
settings, sizeof(settings) / sizeof(settings[0])); settings, sizeof(settings) / sizeof(settings[0]));
if (rv != 0) { if (rv != 0)
{
log_event("HTTP/2: Failed to submit settings"); log_event("HTTP/2: Failed to submit settings");
nghttp2_session_del(h2_session->session); nghttp2_session_del(h2_session->session);
return -1; return -1;
} }
h2_session->handshake_complete = true; h2_session->handshake_complete = true;
log_event("HTTP/2: Session initialized"); log_event("HTTP/2: Session initialized");
return 0; return 0;
} }
// Cleanup HTTP/2 session // Cleanup HTTP/2 session
void http2_session_cleanup(http2_session_t *h2_session) { void http2_session_cleanup(http2_session_t *h2_session)
if (h2_session->session) { {
if (h2_session->session)
{
nghttp2_session_del(h2_session->session); nghttp2_session_del(h2_session->session);
h2_session->session = NULL; h2_session->session = NULL;
} }
@@ -423,60 +471,66 @@ void http2_session_cleanup(http2_session_t *h2_session) {
// Send HTTP/2 response // Send HTTP/2 response
int http2_send_response(http2_session_t *h2_session, int32_t stream_id, int http2_send_response(http2_session_t *h2_session, int32_t stream_id,
const char *data, size_t len, bool end_stream) { const char *data, size_t len, bool end_stream)
(void)data; // Unused in current implementation {
(void)len; // Unused in current implementation (void)data; // Unused in current implementation
(void)end_stream; // Unused in current implementation (void)len; // Unused in current implementation
(void)end_stream; // Unused in current implementation
// Send response headers // Send response headers
nghttp2_nv hdrs[] = { nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE}, {(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 *)"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} {(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); int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 3, NULL);
if (rv != 0) { if (rv != 0)
{
return -1; return -1;
} }
return nghttp2_session_send(h2_session->session); return nghttp2_session_send(h2_session->session);
} }
// Send HTTP/2 error response // Send HTTP/2 error response
int http2_send_error(http2_session_t *h2_session, int32_t stream_id, int http2_send_error(http2_session_t *h2_session, int32_t stream_id,
int status_code, const char *message) { int status_code, const char *message)
{
char status_str[4]; char status_str[4];
snprintf(status_str, sizeof(status_str), "%d", status_code); snprintf(status_str, sizeof(status_str), "%d", status_code);
nghttp2_nv hdrs[] = { nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE}, {(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} {(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); int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 2, NULL);
if (rv != 0) { if (rv != 0)
{
return -1; return -1;
} }
if (message) { if (message)
{
nghttp2_data_provider prd; nghttp2_data_provider prd;
prd.source.ptr = (void *)message; prd.source.ptr = (void *)message;
prd.read_callback = NULL; prd.read_callback = NULL;
nghttp2_submit_data(h2_session->session, NGHTTP2_FLAG_END_STREAM, nghttp2_submit_data(h2_session->session, NGHTTP2_FLAG_END_STREAM,
stream_id, &prd); stream_id, &prd);
} }
return nghttp2_session_send(h2_session->session); return nghttp2_session_send(h2_session->session);
} }
// Handle HTTP/2 connection // Handle HTTP/2 connection
int http2_handle_connection(http2_session_t *h2_session) { int http2_handle_connection(http2_session_t *h2_session)
{
// Receive and process frames first // Receive and process frames first
int rv = nghttp2_session_recv(h2_session->session); int rv = nghttp2_session_recv(h2_session->session);
if (rv != 0) { if (rv != 0)
if (rv == NGHTTP2_ERR_EOF) { {
if (rv == NGHTTP2_ERR_EOF)
{
log_event("HTTP/2: Connection closed"); log_event("HTTP/2: Connection closed");
return 0; return 0;
} }
@@ -485,22 +539,24 @@ int http2_handle_connection(http2_session_t *h2_session) {
log_event(err_msg); log_event(err_msg);
return -1; return -1;
} }
// Send all pending data // Send all pending data
rv = nghttp2_session_send(h2_session->session); rv = nghttp2_session_send(h2_session->session);
if (rv != 0) { if (rv != 0)
{
char err_msg[128]; char err_msg[128];
snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session send failed: %s", nghttp2_strerror(rv)); snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session send failed: %s", nghttp2_strerror(rv));
log_event(err_msg); log_event(err_msg);
return -1; return -1;
} }
// Check if session wants to terminate // Check if session wants to terminate
if (nghttp2_session_want_read(h2_session->session) == 0 && if (nghttp2_session_want_read(h2_session->session) == 0 &&
nghttp2_session_want_write(h2_session->session) == 0) { nghttp2_session_want_write(h2_session->session) == 0)
{
log_event("HTTP/2: Session terminated normally"); log_event("HTTP/2: Session terminated normally");
return 0; return 0;
} }
return 1; return 1;
} }

View File

@@ -6,7 +6,8 @@
#include <stdbool.h> #include <stdbool.h>
// HTTP/2 session context // HTTP/2 session context
typedef struct { typedef struct
{
nghttp2_session *session; nghttp2_session *session;
SSL *ssl; SSL *ssl;
int client_socket; int client_socket;
@@ -14,11 +15,12 @@ typedef struct {
} http2_session_t; } http2_session_t;
// HTTP/2 stream data // HTTP/2 stream data
typedef struct { typedef struct
{
int32_t stream_id; int32_t stream_id;
char request_path[256]; char request_path[256];
char *request_method; char *request_method;
int fd; // File descriptor for response int fd; // File descriptor for response
size_t file_size; size_t file_size;
char *mime_type; char *mime_type;
char *content_length; char *content_length;
@@ -28,14 +30,14 @@ typedef struct {
int http2_session_init(http2_session_t *session, int client_socket, SSL *ssl); int http2_session_init(http2_session_t *session, int client_socket, SSL *ssl);
void http2_session_cleanup(http2_session_t *session); void http2_session_cleanup(http2_session_t *session);
int http2_handle_connection(http2_session_t *session); int http2_handle_connection(http2_session_t *session);
int http2_send_response(http2_session_t *session, int32_t stream_id, int http2_send_response(http2_session_t *session, int32_t stream_id,
const char *data, size_t len, bool end_stream); const char *data, size_t len, bool end_stream);
int http2_send_error(http2_session_t *session, int32_t stream_id, int http2_send_error(http2_session_t *session, int32_t stream_id,
int status_code, const char *message); int status_code, const char *message);
// ALPN callback for protocol selection // ALPN callback for protocol selection
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg); unsigned int inlen, void *arg);
#endif #endif

View File

@@ -7,7 +7,7 @@
#include <stdio.h> #include <stdio.h>
#define MAX_MMAP_CACHE_SIZE 50 #define MAX_MMAP_CACHE_SIZE 50
#define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB #define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB
#define BUFFER_POOL_SIZE 32 #define BUFFER_POOL_SIZE 32
#define DEFAULT_BUFFER_SIZE 16384 #define DEFAULT_BUFFER_SIZE 16384
@@ -27,7 +27,8 @@ const char *response_429_header = "HTTP/1.1 429 Too Many Requests\r\n\r\nRate li
const char *response_500_header = "HTTP/1.1 500 Internal Server Error\r\n\r\nInternal Server Error"; const char *response_500_header = "HTTP/1.1 500 Internal Server Error\r\n\r\nInternal Server Error";
// Task queue implementation // Task queue implementation
void init_task_queue(task_queue_t *queue) { void init_task_queue(task_queue_t *queue)
{
queue->head = NULL; queue->head = NULL;
queue->tail = NULL; queue->tail = NULL;
queue->count = 0; queue->count = 0;
@@ -35,182 +36,216 @@ void init_task_queue(task_queue_t *queue) {
pthread_cond_init(&queue->cond, NULL); pthread_cond_init(&queue->cond, NULL);
} }
void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https) { void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https)
{
connection_task_t *task = malloc(sizeof(connection_task_t)); connection_task_t *task = malloc(sizeof(connection_task_t));
if (!task) return; if (!task)
return;
task->socket_fd = socket_fd; task->socket_fd = socket_fd;
task->ssl = ssl; task->ssl = ssl;
task->is_https = is_https; task->is_https = is_https;
task->next = NULL; task->next = NULL;
pthread_mutex_lock(&queue->mutex); pthread_mutex_lock(&queue->mutex);
if (queue->tail) { if (queue->tail)
{
queue->tail->next = task; queue->tail->next = task;
} else { }
else
{
queue->head = task; queue->head = task;
} }
queue->tail = task; queue->tail = task;
queue->count++; queue->count++;
pthread_cond_signal(&queue->cond); pthread_cond_signal(&queue->cond);
pthread_mutex_unlock(&queue->mutex); pthread_mutex_unlock(&queue->mutex);
} }
connection_task_t* dequeue_task(task_queue_t *queue) { connection_task_t *dequeue_task(task_queue_t *queue)
{
pthread_mutex_lock(&queue->mutex); pthread_mutex_lock(&queue->mutex);
while (queue->head == NULL) { while (queue->head == NULL)
{
pthread_cond_wait(&queue->cond, &queue->mutex); pthread_cond_wait(&queue->cond, &queue->mutex);
} }
connection_task_t *task = queue->head; connection_task_t *task = queue->head;
queue->head = task->next; queue->head = task->next;
if (queue->head == NULL) { if (queue->head == NULL)
{
queue->tail = NULL; queue->tail = NULL;
} }
queue->count--; queue->count--;
pthread_mutex_unlock(&queue->mutex); pthread_mutex_unlock(&queue->mutex);
return task; return task;
} }
void destroy_task_queue(task_queue_t *queue) { void destroy_task_queue(task_queue_t *queue)
{
pthread_mutex_lock(&queue->mutex); pthread_mutex_lock(&queue->mutex);
connection_task_t *current = queue->head; connection_task_t *current = queue->head;
while (current) { while (current)
{
connection_task_t *next = current->next; connection_task_t *next = current->next;
free(current); free(current);
current = next; current = next;
} }
pthread_mutex_unlock(&queue->mutex); pthread_mutex_unlock(&queue->mutex);
pthread_mutex_destroy(&queue->mutex); pthread_mutex_destroy(&queue->mutex);
pthread_cond_destroy(&queue->cond); pthread_cond_destroy(&queue->cond);
} }
// Memory-mapped file cache implementation // Memory-mapped file cache implementation
void init_mmap_cache(void) { void init_mmap_cache(void)
{
mmap_cache = calloc(MAX_MMAP_CACHE_SIZE, sizeof(mmap_cache_entry_t)); mmap_cache = calloc(MAX_MMAP_CACHE_SIZE, sizeof(mmap_cache_entry_t));
} }
mmap_cache_entry_t* get_cached_file(const char *path) { mmap_cache_entry_t *get_cached_file(const char *path)
{
pthread_mutex_lock(&mmap_cache_mutex); pthread_mutex_lock(&mmap_cache_mutex);
for (int i = 0; i < mmap_cache_size; i++) { for (int i = 0; i < mmap_cache_size; i++)
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) { {
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
{
mmap_cache[i].last_access = time(NULL); mmap_cache[i].last_access = time(NULL);
mmap_cache[i].ref_count++; mmap_cache[i].ref_count++;
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return &mmap_cache[i]; return &mmap_cache[i];
} }
} }
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return NULL; return NULL;
} }
void cache_file_mmap(const char *path, size_t size, const char *mime_type) { void cache_file_mmap(const char *path, size_t size, const char *mime_type)
if (size > MAX_MMAP_FILE_SIZE) return; {
if (size > MAX_MMAP_FILE_SIZE)
return;
pthread_mutex_lock(&mmap_cache_mutex); pthread_mutex_lock(&mmap_cache_mutex);
// Check if already cached // Check if already cached
for (int i = 0; i < mmap_cache_size; i++) { for (int i = 0; i < mmap_cache_size; i++)
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) { {
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
{
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return; return;
} }
} }
// Find slot (evict LRU if full) // Find slot (evict LRU if full)
int slot = mmap_cache_size; int slot = mmap_cache_size;
if (mmap_cache_size >= MAX_MMAP_CACHE_SIZE) { if (mmap_cache_size >= MAX_MMAP_CACHE_SIZE)
{
time_t oldest = time(NULL); time_t oldest = time(NULL);
for (int i = 0; i < mmap_cache_size; i++) { for (int i = 0; i < mmap_cache_size; i++)
if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest) { {
if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest)
{
oldest = mmap_cache[i].last_access; oldest = mmap_cache[i].last_access;
slot = i; slot = i;
} }
} }
if (slot == mmap_cache_size) { if (slot == mmap_cache_size)
{
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return; // All entries in use return; // All entries in use
} }
// Evict old entry // Evict old entry
if (mmap_cache[slot].mmap_data) { if (mmap_cache[slot].mmap_data)
{
munmap(mmap_cache[slot].mmap_data, mmap_cache[slot].size); munmap(mmap_cache[slot].mmap_data, mmap_cache[slot].size);
} }
free(mmap_cache[slot].path); free(mmap_cache[slot].path);
free(mmap_cache[slot].mime_type); free(mmap_cache[slot].mime_type);
} else { }
else
{
mmap_cache_size++; mmap_cache_size++;
} }
// Map file // Map file
int fd = open(path, O_RDONLY); int fd = open(path, O_RDONLY);
if (fd < 0) { if (fd < 0)
{
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return; return;
} }
void *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); void *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd); close(fd);
if (mapped == MAP_FAILED) { if (mapped == MAP_FAILED)
{
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
return; return;
} }
// Advise kernel about access pattern // Advise kernel about access pattern
madvise(mapped, size, MADV_WILLNEED | MADV_SEQUENTIAL); madvise(mapped, size, MADV_WILLNEED | MADV_SEQUENTIAL);
mmap_cache[slot].path = strdup(path); mmap_cache[slot].path = strdup(path);
mmap_cache[slot].mmap_data = mapped; mmap_cache[slot].mmap_data = mapped;
mmap_cache[slot].size = size; mmap_cache[slot].size = size;
mmap_cache[slot].last_access = time(NULL); mmap_cache[slot].last_access = time(NULL);
mmap_cache[slot].mime_type = strdup(mime_type); mmap_cache[slot].mime_type = strdup(mime_type);
mmap_cache[slot].ref_count = 0; mmap_cache[slot].ref_count = 0;
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
} }
void release_cached_file(mmap_cache_entry_t *entry) { void release_cached_file(mmap_cache_entry_t *entry)
{
pthread_mutex_lock(&mmap_cache_mutex); pthread_mutex_lock(&mmap_cache_mutex);
entry->ref_count--; entry->ref_count--;
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
} }
void cleanup_mmap_cache(void) { void cleanup_mmap_cache(void)
{
pthread_mutex_lock(&mmap_cache_mutex); pthread_mutex_lock(&mmap_cache_mutex);
for (int i = 0; i < mmap_cache_size; i++) { for (int i = 0; i < mmap_cache_size; i++)
if (mmap_cache[i].mmap_data) { {
if (mmap_cache[i].mmap_data)
{
munmap(mmap_cache[i].mmap_data, mmap_cache[i].size); munmap(mmap_cache[i].mmap_data, mmap_cache[i].size);
} }
free(mmap_cache[i].path); free(mmap_cache[i].path);
free(mmap_cache[i].mime_type); free(mmap_cache[i].mime_type);
} }
free(mmap_cache); free(mmap_cache);
mmap_cache = NULL; mmap_cache = NULL;
mmap_cache_size = 0; mmap_cache_size = 0;
pthread_mutex_unlock(&mmap_cache_mutex); pthread_mutex_unlock(&mmap_cache_mutex);
} }
// Buffer pool implementation // Buffer pool implementation
void init_buffer_pool(void) { void init_buffer_pool(void)
{
pthread_mutex_lock(&buffer_pool_mutex); pthread_mutex_lock(&buffer_pool_mutex);
for (int i = 0; i < BUFFER_POOL_SIZE; i++) { for (int i = 0; i < BUFFER_POOL_SIZE; i++)
{
buffer_pool_t *buf = malloc(sizeof(buffer_pool_t)); buffer_pool_t *buf = malloc(sizeof(buffer_pool_t));
if (buf) { if (buf)
{
buf->buffer = malloc(DEFAULT_BUFFER_SIZE); buf->buffer = malloc(DEFAULT_BUFFER_SIZE);
buf->size = DEFAULT_BUFFER_SIZE; buf->size = DEFAULT_BUFFER_SIZE;
buf->in_use = false; buf->in_use = false;
@@ -218,58 +253,66 @@ void init_buffer_pool(void) {
buffer_pool = buf; buffer_pool = buf;
} }
} }
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
} }
char* get_buffer_from_pool(size_t min_size) { char *get_buffer_from_pool(size_t min_size)
{
pthread_mutex_lock(&buffer_pool_mutex); pthread_mutex_lock(&buffer_pool_mutex);
buffer_pool_t *current = buffer_pool; buffer_pool_t *current = buffer_pool;
while (current) { while (current)
if (!current->in_use && current->size >= min_size) { {
if (!current->in_use && current->size >= min_size)
{
current->in_use = true; current->in_use = true;
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
return current->buffer; return current->buffer;
} }
current = current->next; current = current->next;
} }
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
return malloc(min_size); return malloc(min_size);
} }
void return_buffer_to_pool(char *buffer) { void return_buffer_to_pool(char *buffer)
{
pthread_mutex_lock(&buffer_pool_mutex); pthread_mutex_lock(&buffer_pool_mutex);
buffer_pool_t *current = buffer_pool; buffer_pool_t *current = buffer_pool;
while (current) { while (current)
if (current->buffer == buffer) { {
if (current->buffer == buffer)
{
current->in_use = false; current->in_use = false;
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
return; return;
} }
current = current->next; current = current->next;
} }
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
// Not from pool, free it // Not from pool, free it
free(buffer); free(buffer);
} }
void cleanup_buffer_pool(void) { void cleanup_buffer_pool(void)
{
pthread_mutex_lock(&buffer_pool_mutex); pthread_mutex_lock(&buffer_pool_mutex);
buffer_pool_t *current = buffer_pool; buffer_pool_t *current = buffer_pool;
while (current) { while (current)
{
buffer_pool_t *next = current->next; buffer_pool_t *next = current->next;
free(current->buffer); free(current->buffer);
free(current); free(current);
current = next; current = next;
} }
buffer_pool = NULL; buffer_pool = NULL;
pthread_mutex_unlock(&buffer_pool_mutex); pthread_mutex_unlock(&buffer_pool_mutex);
} }

View File

@@ -8,14 +8,16 @@
#include <openssl/ssl.h> #include <openssl/ssl.h>
// Connection pool structures // Connection pool structures
typedef struct connection_task_t { typedef struct connection_task_t
{
int socket_fd; int socket_fd;
SSL *ssl; SSL *ssl;
bool is_https; bool is_https;
struct connection_task_t *next; struct connection_task_t *next;
} connection_task_t; } connection_task_t;
typedef struct { typedef struct
{
connection_task_t *head; connection_task_t *head;
connection_task_t *tail; connection_task_t *tail;
pthread_mutex_t mutex; pthread_mutex_t mutex;
@@ -24,7 +26,8 @@ typedef struct {
} task_queue_t; } task_queue_t;
// Memory-mapped file cache // Memory-mapped file cache
typedef struct { typedef struct
{
char *path; char *path;
void *mmap_data; void *mmap_data;
size_t size; size_t size;
@@ -34,7 +37,8 @@ typedef struct {
} mmap_cache_entry_t; } mmap_cache_entry_t;
// Response buffer pool // Response buffer pool
typedef struct buffer_pool_t { typedef struct buffer_pool_t
{
char *buffer; char *buffer;
size_t size; size_t size;
bool in_use; bool in_use;
@@ -44,17 +48,17 @@ typedef struct buffer_pool_t {
// Function declarations // Function declarations
void init_task_queue(task_queue_t *queue); void init_task_queue(task_queue_t *queue);
void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https); void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https);
connection_task_t* dequeue_task(task_queue_t *queue); connection_task_t *dequeue_task(task_queue_t *queue);
void destroy_task_queue(task_queue_t *queue); void destroy_task_queue(task_queue_t *queue);
void init_mmap_cache(void); void init_mmap_cache(void);
mmap_cache_entry_t* get_cached_file(const char *path); mmap_cache_entry_t *get_cached_file(const char *path);
void cache_file_mmap(const char *path, size_t size, const char *mime_type); void cache_file_mmap(const char *path, size_t size, const char *mime_type);
void release_cached_file(mmap_cache_entry_t *entry); void release_cached_file(mmap_cache_entry_t *entry);
void cleanup_mmap_cache(void); void cleanup_mmap_cache(void);
void init_buffer_pool(void); void init_buffer_pool(void);
char* get_buffer_from_pool(size_t min_size); char *get_buffer_from_pool(size_t min_size);
void return_buffer_to_pool(char *buffer); void return_buffer_to_pool(char *buffer);
void cleanup_buffer_pool(void); void cleanup_buffer_pool(void);

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,20 @@
#include <string.h> #include <string.h>
#include "server_config.h" #include "server_config.h"
void init_config(ServerConfig *config) { void init_config(ServerConfig *config)
{
config->port = 8080; config->port = 8080;
config->use_https = false; config->use_https = false;
strcpy(config->log_file, "server.log"); strcpy(config->log_file, "server.log");
config->max_threads = 4; config->max_threads = 4;
config->running = true; config->running = true;
config->automatic_startup = false; config->automatic_startup = false;
config->verbose = 0; config->verbose = 0;
strcpy(config->server_name, "127.0.0.1"); strcpy(config->server_name, "127.0.0.1");
config->enable_http2 = false; config->enable_http2 = false;
config->enable_websocket = false; config->enable_websocket = false;
strcpy(config->www_path, "www"); strcpy(config->www_path, "www");
config->max_connections = 1024; config->max_connections = 1024;
strcpy(config->ssl_cert_path, "ssl/cert/");
strcpy(config->ssl_key_path, "ssl");
} }

View File

@@ -3,24 +3,26 @@
#include <stdbool.h> #include <stdbool.h>
typedef struct { typedef struct
{
int port; int port;
bool use_https; bool use_https;
char log_file[256]; char log_file[256];
int max_threads; int max_threads;
bool running; bool running;
bool automatic_startup; bool automatic_startup;
char server_name[256]; char server_name[256];
int verbose; int verbose;
bool enable_http2; bool enable_http2;
bool enable_websocket; bool enable_websocket;
char www_path[256]; char www_path[256];
int max_connections; int max_connections;
char ssl_cert_path[256];
char ssl_key_path[256];
} ServerConfig; } ServerConfig;
int load_config(const char *filename, ServerConfig *config); int load_config(const char *filename, ServerConfig *config);
void init_config(ServerConfig *config); void init_config(ServerConfig *config);
void log_event(const char *message); void log_event(const char *message);
#endif #endif

View File

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

View File

@@ -7,14 +7,15 @@
// WebSocket opcodes // WebSocket opcodes
#define WS_OPCODE_CONTINUATION 0x0 #define WS_OPCODE_CONTINUATION 0x0
#define WS_OPCODE_TEXT 0x1 #define WS_OPCODE_TEXT 0x1
#define WS_OPCODE_BINARY 0x2 #define WS_OPCODE_BINARY 0x2
#define WS_OPCODE_CLOSE 0x8 #define WS_OPCODE_CLOSE 0x8
#define WS_OPCODE_PING 0x9 #define WS_OPCODE_PING 0x9
#define WS_OPCODE_PONG 0xA #define WS_OPCODE_PONG 0xA
// WebSocket frame header structure // WebSocket frame header structure
typedef struct { typedef struct
{
uint8_t fin; uint8_t fin;
uint8_t opcode; uint8_t opcode;
uint8_t mask; uint8_t mask;
@@ -23,7 +24,8 @@ typedef struct {
} ws_frame_header_t; } ws_frame_header_t;
// WebSocket connection context // WebSocket connection context
typedef struct { typedef struct
{
int socket_fd; int socket_fd;
SSL *ssl; SSL *ssl;
bool is_ssl; bool is_ssl;
@@ -41,7 +43,7 @@ int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_l
void ws_close_connection(ws_connection_t *conn, uint16_t status_code); void ws_close_connection(ws_connection_t *conn, uint16_t status_code);
// Helper functions // Helper functions
char* ws_generate_accept_key(const char *client_key); char *ws_generate_accept_key(const char *client_key);
bool ws_is_valid_utf8(const uint8_t *data, size_t len); bool ws_is_valid_utf8(const uint8_t *data, size_t len);
#endif #endif