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:
@@ -5,7 +5,8 @@
|
||||
#include <ctype.h>
|
||||
#include "server_config.h"
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
CONFIG_PORT,
|
||||
CONFIG_USE_HTTPS,
|
||||
CONFIG_LOG_FILE,
|
||||
@@ -17,40 +18,49 @@ typedef enum {
|
||||
CONFIG_ENABLE_WEBSOCKET,
|
||||
CONFIG_WWW_PATH,
|
||||
CONFIG_MAX_CONNECTIONS,
|
||||
CONFIG_SSL_CERT_PATH,
|
||||
CONFIG_SSL_KEY_PATH,
|
||||
CONFIG_UNKNOWN
|
||||
|
||||
} ConfigKey;
|
||||
|
||||
// Trim whitespace from both ends of a string
|
||||
static char* trim_whitespace(char *str) {
|
||||
static char *trim_whitespace(char *str)
|
||||
{
|
||||
char *end;
|
||||
|
||||
|
||||
// Trim leading space
|
||||
while(isspace((unsigned char)*str)) str++;
|
||||
|
||||
if(*str == 0) return str;
|
||||
|
||||
while (isspace((unsigned char)*str))
|
||||
str++;
|
||||
|
||||
if (*str == 0)
|
||||
return str;
|
||||
|
||||
// Trim trailing space
|
||||
end = str + strlen(str) - 1;
|
||||
while(end > str && isspace((unsigned char)*end)) end--;
|
||||
|
||||
while (end > str && isspace((unsigned char)*end))
|
||||
end--;
|
||||
|
||||
end[1] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
// Parse a boolean value (true/false, yes/no, on/off, 1/0)
|
||||
static bool parse_bool(const char *value) {
|
||||
if (strcasecmp(value, "true") == 0 ||
|
||||
strcasecmp(value, "yes") == 0 ||
|
||||
static bool parse_bool(const char *value)
|
||||
{
|
||||
if (strcasecmp(value, "true") == 0 ||
|
||||
strcasecmp(value, "yes") == 0 ||
|
||||
strcasecmp(value, "on") == 0 ||
|
||||
strcmp(value, "1") == 0) {
|
||||
strcmp(value, "1") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Map string to enum
|
||||
static ConfigKey get_config_key(const char *key) {
|
||||
static const struct
|
||||
static ConfigKey get_config_key(const char *key)
|
||||
{
|
||||
static const struct
|
||||
{
|
||||
const char *name;
|
||||
ConfigKey key;
|
||||
@@ -63,23 +73,29 @@ static ConfigKey get_config_key(const char *key) {
|
||||
{"server_name", CONFIG_SERVER_NAME},
|
||||
{"verbose", CONFIG_VERBOSE},
|
||||
{"enable_http2", CONFIG_ENABLE_HTTP2},
|
||||
{"enable_websocket",CONFIG_ENABLE_WEBSOCKET},
|
||||
{"enable_websocket", CONFIG_ENABLE_WEBSOCKET},
|
||||
{"www_path", CONFIG_WWW_PATH},
|
||||
{"max_connections", CONFIG_MAX_CONNECTIONS},
|
||||
{"ssl_cert_path", CONFIG_SSL_CERT_PATH},
|
||||
{"ssl_key_path", CONFIG_SSL_KEY_PATH},
|
||||
{NULL, CONFIG_UNKNOWN}
|
||||
|
||||
|
||||
};
|
||||
for (int i = 0;key_map[i].name != NULL; i++) {
|
||||
if (strcasecmp(key, key_map[i].name) == 0) {
|
||||
for (int i = 0; key_map[i].name != NULL; i++)
|
||||
{
|
||||
if (strcasecmp(key, key_map[i].name) == 0)
|
||||
{
|
||||
return key_map[i].key;
|
||||
}
|
||||
}
|
||||
return CONFIG_UNKNOWN;
|
||||
}
|
||||
|
||||
int load_config(const char *filename, ServerConfig *config) {
|
||||
int load_config(const char *filename, ServerConfig *config)
|
||||
{
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if (!fp) {
|
||||
if (!fp)
|
||||
{
|
||||
perror("Error opening config file");
|
||||
return 1;
|
||||
}
|
||||
@@ -87,115 +103,146 @@ int load_config(const char *filename, ServerConfig *config) {
|
||||
char line[512];
|
||||
int line_number = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line_number++;
|
||||
|
||||
// Remove newline
|
||||
line[strcspn(line, "\r\n")] = 0;
|
||||
|
||||
// Trim whitespace
|
||||
char *trimmed = trim_whitespace(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the delimiter (= or space)
|
||||
char *delim = strchr(trimmed, '=');
|
||||
if (!delim) {
|
||||
// Try space as delimiter
|
||||
delim = strchr(trimmed, ' ');
|
||||
}
|
||||
|
||||
if (!delim) {
|
||||
fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split into key and value
|
||||
*delim = '\0';
|
||||
char *key = trim_whitespace(trimmed);
|
||||
char *value = trim_whitespace(delim + 1);
|
||||
|
||||
// Remove quotes from value if present
|
||||
if ((value[0] == '"' || value[0] == '\'') &&
|
||||
value[strlen(value) - 1] == value[0]) {
|
||||
value[strlen(value) - 1] = '\0';
|
||||
value++;
|
||||
}
|
||||
// Parse configuration options
|
||||
switch (get_config_key(key)) {
|
||||
case CONFIG_PORT:
|
||||
config->port = atoi(value);
|
||||
printf("load_config: port = %d\n", config->port);
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
line_number++;
|
||||
|
||||
// Remove newline
|
||||
line[strcspn(line, "\r\n")] = 0;
|
||||
|
||||
// Trim whitespace
|
||||
char *trimmed = trim_whitespace(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the delimiter (= or space)
|
||||
char *delim = strchr(trimmed, '=');
|
||||
if (!delim)
|
||||
{
|
||||
// Try space as delimiter
|
||||
delim = strchr(trimmed, ' ');
|
||||
}
|
||||
|
||||
if (!delim)
|
||||
{
|
||||
fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split into key and value
|
||||
*delim = '\0';
|
||||
char *key = trim_whitespace(trimmed);
|
||||
char *value = trim_whitespace(delim + 1);
|
||||
|
||||
// Remove quotes from value if present
|
||||
if ((value[0] == '"' || value[0] == '\'') &&
|
||||
value[strlen(value) - 1] == value[0])
|
||||
{
|
||||
value[strlen(value) - 1] = '\0';
|
||||
value++;
|
||||
}
|
||||
// Parse configuration options
|
||||
switch (get_config_key(key))
|
||||
{
|
||||
case CONFIG_PORT:
|
||||
config->port = atoi(value);
|
||||
printf("load_config: port = %d\n", config->port);
|
||||
break;
|
||||
|
||||
case CONFIG_USE_HTTPS:
|
||||
config->use_https = parse_bool(value);
|
||||
printf("load_config: use_https = %d\n", config->use_https);
|
||||
case CONFIG_USE_HTTPS:
|
||||
config->use_https = parse_bool(value);
|
||||
printf("load_config: use_https = %d\n", config->use_https);
|
||||
break;
|
||||
|
||||
case CONFIG_LOG_FILE:
|
||||
case CONFIG_LOG_FILE:
|
||||
strncpy(config->log_file, value, sizeof(config->log_file) - 1);
|
||||
config->log_file[sizeof(config->log_file) - 1] = '\0';
|
||||
printf("load_config: log_file = %s\n", config->log_file);
|
||||
config->log_file[sizeof(config->log_file) - 1] = '\0';
|
||||
printf("load_config: log_file = %s\n", config->log_file);
|
||||
break;
|
||||
|
||||
case CONFIG_MAX_THREADS:
|
||||
config->max_threads = atoi(value);
|
||||
printf("load_config: max_threads = %d\n", config->max_threads);
|
||||
case CONFIG_MAX_THREADS:
|
||||
config->max_threads = atoi(value);
|
||||
printf("load_config: max_threads = %d\n", config->max_threads);
|
||||
break;
|
||||
|
||||
case CONFIG_RUNNING:
|
||||
config->running = parse_bool(value);
|
||||
if (config->running == 0 || false) {
|
||||
fprintf(stderr, "ERROR: current run state is false cannot run the server!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("load_config: running = %d\n", config->running);
|
||||
case CONFIG_RUNNING:
|
||||
config->running = parse_bool(value);
|
||||
if (!config->running)
|
||||
{
|
||||
fprintf(stderr, "ERROR: current run state is false cannot run the server!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("load_config: running = %d\n", config->running);
|
||||
break;
|
||||
|
||||
case CONFIG_SERVER_NAME:
|
||||
strncpy(config->server_name, value, sizeof(config->server_name) - 1);
|
||||
config->server_name[sizeof(config->server_name) - 1] = '\0';
|
||||
printf("load_config: server_name = %s\n", config->server_name);
|
||||
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");
|
||||
}
|
||||
case CONFIG_SERVER_NAME:
|
||||
strncpy(config->server_name, value, sizeof(config->server_name) - 1);
|
||||
config->server_name[sizeof(config->server_name) - 1] = '\0';
|
||||
printf("load_config: server_name = %s\n", config->server_name);
|
||||
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");
|
||||
}
|
||||
break;
|
||||
case CONFIG_VERBOSE:
|
||||
config->verbose = parse_bool(value);
|
||||
printf("load_config: verbose = %d\n", config->verbose);
|
||||
break;
|
||||
|
||||
case CONFIG_VERBOSE:
|
||||
config->verbose = parse_bool(value);
|
||||
printf("load_config: verbose = %d\n", config->verbose);
|
||||
break;
|
||||
|
||||
case CONFIG_ENABLE_HTTP2:
|
||||
config->enable_http2 = parse_bool(value);
|
||||
case CONFIG_ENABLE_HTTP2:
|
||||
config->enable_http2 = parse_bool(value);
|
||||
if (!config->use_https && config->enable_http2)
|
||||
{
|
||||
printf("Error: Cannot load HTTP/2 while HTTPS is not enabled!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
|
||||
case CONFIG_ENABLE_WEBSOCKET:
|
||||
config->enable_websocket = parse_bool(value);
|
||||
printf("load_config: enable_websocket = %d\n", config->enable_websocket);
|
||||
case CONFIG_WWW_PATH:
|
||||
strncpy(config->www_path, value, sizeof(config->www_path) - 1);
|
||||
config->www_path[sizeof(config->www_path) - 1] = '\0';
|
||||
printf("load_config: www_path = %s\n", config->www_path);
|
||||
break;
|
||||
|
||||
case CONFIG_WWW_PATH:
|
||||
strncpy(config->www_path, value, sizeof(config->www_path) - 1);
|
||||
config->www_path[sizeof(config->www_path) - 1] = '\0';
|
||||
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);
|
||||
case CONFIG_MAX_CONNECTIONS:
|
||||
config->max_connections = atoi(value);
|
||||
printf("load_config: max_connections: = %d\n", config->max_connections);
|
||||
|
||||
break;
|
||||
|
||||
case CONFIG_UNKNOWN:
|
||||
default:
|
||||
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number);
|
||||
case CONFIG_SSL_CERT_PATH:
|
||||
if (config->use_https)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
560
src/http2.c
560
src/http2.c
@@ -10,27 +10,31 @@
|
||||
|
||||
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);
|
||||
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) {
|
||||
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) {
|
||||
|
||||
if (ret == 1)
|
||||
{
|
||||
// HTTP/2 selected
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
} else if (ret == 0) {
|
||||
}
|
||||
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;
|
||||
@@ -39,252 +43,285 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
|
||||
while ((nread = read(fd, buf, length)) == -1 && errno == EINTR)
|
||||
;
|
||||
|
||||
if (nread == -1)
|
||||
{
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
if (nread == 0) {
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
||||
if (h2_session->ssl)
|
||||
{
|
||||
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);
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
if (h2_session->ssl)
|
||||
{
|
||||
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);
|
||||
if (ssl_error == SSL_ERROR_WANT_READ) {
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
(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);
|
||||
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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 *stream_data =
|
||||
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id);
|
||||
|
||||
if (stream_data) {
|
||||
if (stream_data->fd != -1) {
|
||||
|
||||
if (stream_data)
|
||||
{
|
||||
if (stream_data->fd != -1)
|
||||
{
|
||||
close(stream_data->fd);
|
||||
}
|
||||
if (stream_data->mime_type) {
|
||||
if (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);
|
||||
}
|
||||
|
||||
|
||||
log_event("HTTP/2: Stream closed");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -293,89 +330,97 @@ 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) {
|
||||
uint8_t flags, void *user_data)
|
||||
{
|
||||
(void)flags;
|
||||
(void)user_data;
|
||||
|
||||
|
||||
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||
frame->headers.cat != NGHTTP2_HCAT_REQUEST)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
if (!stream_data) {
|
||||
|
||||
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;
|
||||
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 *user_data)
|
||||
{
|
||||
(void)session;
|
||||
(void)user_data;
|
||||
|
||||
|
||||
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
@@ -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_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) {
|
||||
|
||||
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}
|
||||
};
|
||||
|
||||
{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) {
|
||||
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) {
|
||||
void http2_session_cleanup(http2_session_t *h2_session)
|
||||
{
|
||||
if (h2_session->session)
|
||||
{
|
||||
nghttp2_session_del(h2_session->session);
|
||||
h2_session->session = NULL;
|
||||
}
|
||||
@@ -423,60 +471,66 @@ void http2_session_cleanup(http2_session_t *h2_session) {
|
||||
|
||||
// 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
|
||||
|
||||
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}
|
||||
};
|
||||
|
||||
{(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) {
|
||||
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) {
|
||||
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}
|
||||
};
|
||||
|
||||
{(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) {
|
||||
if (rv != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
|
||||
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);
|
||||
stream_id, &prd);
|
||||
}
|
||||
|
||||
|
||||
return nghttp2_session_send(h2_session->session);
|
||||
}
|
||||
|
||||
// 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
|
||||
int rv = nghttp2_session_recv(h2_session->session);
|
||||
if (rv != 0) {
|
||||
if (rv == NGHTTP2_ERR_EOF) {
|
||||
if (rv != 0)
|
||||
{
|
||||
if (rv == NGHTTP2_ERR_EOF)
|
||||
{
|
||||
log_event("HTTP/2: Connection closed");
|
||||
return 0;
|
||||
}
|
||||
@@ -485,22 +539,24 @@ int http2_handle_connection(http2_session_t *h2_session) {
|
||||
log_event(err_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Send all pending data
|
||||
rv = nghttp2_session_send(h2_session->session);
|
||||
if (rv != 0) {
|
||||
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) {
|
||||
nghttp2_session_want_write(h2_session->session) == 0)
|
||||
{
|
||||
log_event("HTTP/2: Session terminated normally");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
16
src/http2.h
16
src/http2.h
@@ -6,7 +6,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
// HTTP/2 session context
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
nghttp2_session *session;
|
||||
SSL *ssl;
|
||||
int client_socket;
|
||||
@@ -14,11 +15,12 @@ typedef struct {
|
||||
} http2_session_t;
|
||||
|
||||
// HTTP/2 stream data
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
int32_t stream_id;
|
||||
char request_path[256];
|
||||
char *request_method;
|
||||
int fd; // File descriptor for response
|
||||
int fd; // File descriptor for response
|
||||
size_t file_size;
|
||||
char *mime_type;
|
||||
char *content_length;
|
||||
@@ -28,14 +30,14 @@ typedef struct {
|
||||
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,
|
||||
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 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);
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#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 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";
|
||||
|
||||
// Task queue implementation
|
||||
void init_task_queue(task_queue_t *queue) {
|
||||
void init_task_queue(task_queue_t *queue)
|
||||
{
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
queue->count = 0;
|
||||
@@ -35,182 +36,216 @@ void init_task_queue(task_queue_t *queue) {
|
||||
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));
|
||||
if (!task) return;
|
||||
|
||||
if (!task)
|
||||
return;
|
||||
|
||||
task->socket_fd = socket_fd;
|
||||
task->ssl = ssl;
|
||||
task->is_https = is_https;
|
||||
task->next = NULL;
|
||||
|
||||
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
if (queue->tail) {
|
||||
|
||||
if (queue->tail)
|
||||
{
|
||||
queue->tail->next = task;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
queue->head = task;
|
||||
}
|
||||
queue->tail = task;
|
||||
queue->count++;
|
||||
|
||||
|
||||
pthread_cond_signal(&queue->cond);
|
||||
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);
|
||||
|
||||
while (queue->head == NULL) {
|
||||
|
||||
while (queue->head == NULL)
|
||||
{
|
||||
pthread_cond_wait(&queue->cond, &queue->mutex);
|
||||
}
|
||||
|
||||
|
||||
connection_task_t *task = queue->head;
|
||||
queue->head = task->next;
|
||||
|
||||
if (queue->head == NULL) {
|
||||
|
||||
if (queue->head == NULL)
|
||||
{
|
||||
queue->tail = NULL;
|
||||
}
|
||||
queue->count--;
|
||||
|
||||
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
return task;
|
||||
}
|
||||
|
||||
void destroy_task_queue(task_queue_t *queue) {
|
||||
void destroy_task_queue(task_queue_t *queue)
|
||||
{
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
|
||||
connection_task_t *current = queue->head;
|
||||
while (current) {
|
||||
while (current)
|
||||
{
|
||||
connection_task_t *next = current->next;
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
|
||||
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
pthread_mutex_destroy(&queue->mutex);
|
||||
pthread_cond_destroy(&queue->cond);
|
||||
}
|
||||
|
||||
// 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_entry_t* get_cached_file(const char *path) {
|
||||
mmap_cache_entry_t *get_cached_file(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&mmap_cache_mutex);
|
||||
|
||||
for (int i = 0; i < mmap_cache_size; i++) {
|
||||
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) {
|
||||
|
||||
for (int i = 0; i < mmap_cache_size; i++)
|
||||
{
|
||||
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
|
||||
{
|
||||
mmap_cache[i].last_access = time(NULL);
|
||||
mmap_cache[i].ref_count++;
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return &mmap_cache[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cache_file_mmap(const char *path, size_t size, const char *mime_type) {
|
||||
if (size > MAX_MMAP_FILE_SIZE) return;
|
||||
|
||||
void cache_file_mmap(const char *path, size_t size, const char *mime_type)
|
||||
{
|
||||
if (size > MAX_MMAP_FILE_SIZE)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&mmap_cache_mutex);
|
||||
|
||||
|
||||
// Check if already cached
|
||||
for (int i = 0; i < mmap_cache_size; i++) {
|
||||
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) {
|
||||
for (int i = 0; i < mmap_cache_size; i++)
|
||||
{
|
||||
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
|
||||
{
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Find slot (evict LRU if full)
|
||||
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);
|
||||
for (int i = 0; i < mmap_cache_size; i++) {
|
||||
if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest) {
|
||||
for (int i = 0; i < mmap_cache_size; i++)
|
||||
{
|
||||
if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest)
|
||||
{
|
||||
oldest = mmap_cache[i].last_access;
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == mmap_cache_size) {
|
||||
|
||||
if (slot == mmap_cache_size)
|
||||
{
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return; // All entries in use
|
||||
return; // All entries in use
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
free(mmap_cache[slot].path);
|
||||
free(mmap_cache[slot].mime_type);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
mmap_cache_size++;
|
||||
}
|
||||
|
||||
|
||||
// Map file
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
if (fd < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (mapped == MAP_FAILED) {
|
||||
|
||||
if (mapped == MAP_FAILED)
|
||||
{
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Advise kernel about access pattern
|
||||
madvise(mapped, size, MADV_WILLNEED | MADV_SEQUENTIAL);
|
||||
|
||||
|
||||
mmap_cache[slot].path = strdup(path);
|
||||
mmap_cache[slot].mmap_data = mapped;
|
||||
mmap_cache[slot].size = size;
|
||||
mmap_cache[slot].last_access = time(NULL);
|
||||
mmap_cache[slot].mime_type = strdup(mime_type);
|
||||
mmap_cache[slot].ref_count = 0;
|
||||
|
||||
|
||||
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);
|
||||
entry->ref_count--;
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
}
|
||||
|
||||
void cleanup_mmap_cache(void) {
|
||||
void cleanup_mmap_cache(void)
|
||||
{
|
||||
pthread_mutex_lock(&mmap_cache_mutex);
|
||||
|
||||
for (int i = 0; i < mmap_cache_size; i++) {
|
||||
if (mmap_cache[i].mmap_data) {
|
||||
|
||||
for (int i = 0; i < mmap_cache_size; i++)
|
||||
{
|
||||
if (mmap_cache[i].mmap_data)
|
||||
{
|
||||
munmap(mmap_cache[i].mmap_data, mmap_cache[i].size);
|
||||
}
|
||||
free(mmap_cache[i].path);
|
||||
free(mmap_cache[i].mime_type);
|
||||
}
|
||||
|
||||
|
||||
free(mmap_cache);
|
||||
mmap_cache = NULL;
|
||||
mmap_cache_size = 0;
|
||||
|
||||
|
||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||
}
|
||||
|
||||
// Buffer pool implementation
|
||||
void init_buffer_pool(void) {
|
||||
void init_buffer_pool(void)
|
||||
{
|
||||
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));
|
||||
if (buf) {
|
||||
if (buf)
|
||||
{
|
||||
buf->buffer = malloc(DEFAULT_BUFFER_SIZE);
|
||||
buf->size = DEFAULT_BUFFER_SIZE;
|
||||
buf->in_use = false;
|
||||
@@ -218,58 +253,66 @@ void init_buffer_pool(void) {
|
||||
buffer_pool = buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
buffer_pool_t *current = buffer_pool;
|
||||
while (current) {
|
||||
if (!current->in_use && current->size >= min_size) {
|
||||
while (current)
|
||||
{
|
||||
if (!current->in_use && current->size >= min_size)
|
||||
{
|
||||
current->in_use = true;
|
||||
pthread_mutex_unlock(&buffer_pool_mutex);
|
||||
return current->buffer;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
|
||||
pthread_mutex_unlock(&buffer_pool_mutex);
|
||||
|
||||
|
||||
return malloc(min_size);
|
||||
}
|
||||
|
||||
void return_buffer_to_pool(char *buffer) {
|
||||
void return_buffer_to_pool(char *buffer)
|
||||
{
|
||||
pthread_mutex_lock(&buffer_pool_mutex);
|
||||
|
||||
|
||||
buffer_pool_t *current = buffer_pool;
|
||||
while (current) {
|
||||
if (current->buffer == buffer) {
|
||||
while (current)
|
||||
{
|
||||
if (current->buffer == buffer)
|
||||
{
|
||||
current->in_use = false;
|
||||
pthread_mutex_unlock(&buffer_pool_mutex);
|
||||
return;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
|
||||
pthread_mutex_unlock(&buffer_pool_mutex);
|
||||
|
||||
|
||||
// Not from pool, free it
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void cleanup_buffer_pool(void) {
|
||||
void cleanup_buffer_pool(void)
|
||||
{
|
||||
pthread_mutex_lock(&buffer_pool_mutex);
|
||||
|
||||
|
||||
buffer_pool_t *current = buffer_pool;
|
||||
while (current) {
|
||||
while (current)
|
||||
{
|
||||
buffer_pool_t *next = current->next;
|
||||
free(current->buffer);
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
|
||||
|
||||
buffer_pool = NULL;
|
||||
pthread_mutex_unlock(&buffer_pool_mutex);
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
// Connection pool structures
|
||||
typedef struct connection_task_t {
|
||||
typedef struct connection_task_t
|
||||
{
|
||||
int socket_fd;
|
||||
SSL *ssl;
|
||||
bool is_https;
|
||||
struct connection_task_t *next;
|
||||
} connection_task_t;
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
connection_task_t *head;
|
||||
connection_task_t *tail;
|
||||
pthread_mutex_t mutex;
|
||||
@@ -24,7 +26,8 @@ typedef struct {
|
||||
} task_queue_t;
|
||||
|
||||
// Memory-mapped file cache
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
char *path;
|
||||
void *mmap_data;
|
||||
size_t size;
|
||||
@@ -34,7 +37,8 @@ typedef struct {
|
||||
} mmap_cache_entry_t;
|
||||
|
||||
// Response buffer pool
|
||||
typedef struct buffer_pool_t {
|
||||
typedef struct buffer_pool_t
|
||||
{
|
||||
char *buffer;
|
||||
size_t size;
|
||||
bool in_use;
|
||||
@@ -44,17 +48,17 @@ typedef struct buffer_pool_t {
|
||||
// Function declarations
|
||||
void init_task_queue(task_queue_t *queue);
|
||||
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 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 release_cached_file(mmap_cache_entry_t *entry);
|
||||
void cleanup_mmap_cache(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 cleanup_buffer_pool(void);
|
||||
|
||||
|
||||
1065
src/server.c
1065
src/server.c
File diff suppressed because it is too large
Load Diff
@@ -2,17 +2,20 @@
|
||||
#include <string.h>
|
||||
#include "server_config.h"
|
||||
|
||||
void init_config(ServerConfig *config) {
|
||||
void init_config(ServerConfig *config)
|
||||
{
|
||||
config->port = 8080;
|
||||
config->use_https = false;
|
||||
strcpy(config->log_file, "server.log");
|
||||
config->max_threads = 4;
|
||||
config->running = true;
|
||||
config->automatic_startup = false;
|
||||
config->automatic_startup = false;
|
||||
config->verbose = 0;
|
||||
strcpy(config->server_name, "127.0.0.1");
|
||||
config->enable_http2 = false;
|
||||
config->enable_websocket = false;
|
||||
strcpy(config->www_path, "www");
|
||||
config->max_connections = 1024;
|
||||
strcpy(config->ssl_cert_path, "ssl/cert/");
|
||||
strcpy(config->ssl_key_path, "ssl");
|
||||
}
|
||||
|
||||
@@ -3,24 +3,26 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
int port;
|
||||
bool use_https;
|
||||
char log_file[256];
|
||||
int max_threads;
|
||||
bool running;
|
||||
bool automatic_startup;
|
||||
char server_name[256];
|
||||
char server_name[256];
|
||||
int verbose;
|
||||
bool enable_http2;
|
||||
bool enable_websocket;
|
||||
char www_path[256];
|
||||
int max_connections;
|
||||
char ssl_cert_path[256];
|
||||
char ssl_key_path[256];
|
||||
} ServerConfig;
|
||||
|
||||
int load_config(const char *filename, ServerConfig *config);
|
||||
void init_config(ServerConfig *config);
|
||||
void log_event(const char *message);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
222
src/websocket.c
222
src/websocket.c
@@ -12,7 +12,8 @@
|
||||
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
// 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;
|
||||
BUF_MEM *bptr;
|
||||
|
||||
@@ -34,47 +35,53 @@ static char* base64_encode(const unsigned char *input, int length) {
|
||||
}
|
||||
|
||||
// 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];
|
||||
snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
// Extract Sec-WebSocket-Key from request
|
||||
const char *key_header = "Sec-WebSocket-Key: ";
|
||||
char *key_start = strstr(request, key_header);
|
||||
if (!key_start) {
|
||||
if (!key_start)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
key_start += strlen(key_header);
|
||||
|
||||
|
||||
char *key_end = strstr(key_start, "\r\n");
|
||||
if (!key_end) {
|
||||
if (!key_end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
char client_key[256];
|
||||
size_t key_len = key_end - key_start;
|
||||
if (key_len >= sizeof(client_key)) {
|
||||
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) {
|
||||
if (!accept_key)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Create handshake response
|
||||
snprintf(response, response_size,
|
||||
"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"
|
||||
"\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) {
|
||||
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) {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
else if (payload_len == 127)
|
||||
{
|
||||
if (len < 10)
|
||||
return -1;
|
||||
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];
|
||||
}
|
||||
offset = 10;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
header->payload_length = payload_len;
|
||||
}
|
||||
|
||||
if (header->mask) {
|
||||
if (len < offset + 4) return -1;
|
||||
|
||||
if (header->mask)
|
||||
{
|
||||
if (len < offset + 4)
|
||||
return -1;
|
||||
memcpy(header->masking_key, data + offset, 4);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
if (len < offset + header->payload_length) {
|
||||
|
||||
if (len < offset + header->payload_length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Unmask payload if masked
|
||||
*payload = (uint8_t*)malloc(header->payload_length);
|
||||
if (!*payload) {
|
||||
*payload = (uint8_t *)malloc(header->payload_length);
|
||||
if (!*payload)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (header->mask) {
|
||||
for (uint64_t i = 0; i < header->payload_length; i++) {
|
||||
|
||||
if (header->mask)
|
||||
{
|
||||
for (uint64_t i = 0; i < header->payload_length; i++)
|
||||
{
|
||||
(*payload)[i] = data[offset + i] ^ header->masking_key[i % 4];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer_size < offset + 9 + payload_len)
|
||||
return -1;
|
||||
buffer[offset++] = 127;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
buffer[offset++] = (payload_len >> (i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copy payload
|
||||
if (payload && payload_len > 0) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
if (frame_len < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (conn->is_ssl && conn->ssl) {
|
||||
|
||||
if (conn->is_ssl && conn->ssl)
|
||||
{
|
||||
return SSL_write(conn->ssl, buffer, frame_len);
|
||||
} else {
|
||||
}
|
||||
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));
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
if (conn->is_ssl && conn->ssl)
|
||||
{
|
||||
SSL_shutdown(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
|
||||
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;
|
||||
while (i < len) {
|
||||
if (data[i] < 0x80) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 {
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
|
||||
// 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
|
||||
#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 {
|
||||
typedef struct
|
||||
{
|
||||
uint8_t fin;
|
||||
uint8_t opcode;
|
||||
uint8_t mask;
|
||||
@@ -23,7 +24,8 @@ typedef struct {
|
||||
} ws_frame_header_t;
|
||||
|
||||
// WebSocket connection context
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
int socket_fd;
|
||||
SSL *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);
|
||||
|
||||
// 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);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user