commit 677b9aec035576187d978c5ca3e9610f94b456c7 Author: TolaMironcenko Date: Mon Apr 14 15:21:10 2025 +0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c02344 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.vscode +server.key +server.crt \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1533ef9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.6) +project(smol) +add_executable( + smol + src/smol.c +) +target_link_libraries(smol ssl crypto) + +add_executable( + smol-pthreads + src/smol-pthreads.c +) +target_link_libraries(smol-pthreads ssl crypto) + +add_executable( + smol-http + src/smol-http.c +) + +add_executable( + smol-http-pthreads + src/smol-http-pthreads.c +) + +install(TARGETS smol smol-pthreads smol-http smol-http-pthreads DESTINATION bin) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dddc4ce --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +CC=gcc +STRIP=strip +BIN=build +SRC=src +PREFIX=/usr + +default: all + +all: build + +build: smol smol-pthreads smol-http smol-http-pthreads + +smol: + $(CC) -o $(BIN)/smol $(SRC)/smol.c -lcrypto -lssl + +smol-pthreads: + $(CC) -o $(BIN)/smol-pthreads $(SRC)/smol-pthreads.c -lcrypto -lssl + +smol-http: + $(CC) -o $(BIN)/smol-http $(SRC)/smol-http.c + +smol-http-pthreads: + $(CC) -o $(BIN)/smol-http-pthreads $(SRC)/smol-http-pthreads.c + +clean: + if [ -d $(BIN) ]; then rm -rv $(BIN);mkdir -pv $(BIN);fi + +install: + cp -v $(BIN)/* $(PREFIX)/bin/ + $(STRIP) $(PREFIX)/bin/smol + $(STRIP) $(PREFIX)/bin/smol-pthreads + $(STRIP) $(PREFIX)/bin/smol-http + $(STRIP) $(PREFIX)/bin/smol-http-pthreads + +uninstall: remove + +remove: + if [ -f $(PREFIX)/bin/smol ];then rm -v $(PREFIX)/bin/smol;fi + if [ -f $(PREFIX)/bin/smol-pthreads ];then rm -v $(PREFIX)/bin/smol-pthreads;fi + if [ -f $(PREFIX)/bin/smol-http ];then rm -v $(PREFIX)/bin/smol-http;fi + if [ -f $(PREFIX)/bin/smol-http-pthreads ];then rm -v $(PREFIX)/bin/smol-http-pthreads;fi \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5eac70f --- /dev/null +++ b/meson.build @@ -0,0 +1,27 @@ +project('smol', 'c') +deps = dependency('openssl') +executable( + 'smol', + 'src/smol.c', + dependencies: deps, + install: true, +) + +executable( + 'smol-pthreads', + 'src/smol-pthreads.c', + dependencies: deps, + install: true, +) + +executable( + 'smol-http', + 'src/smol-http.c', + install: true, +) + +executable( + 'smol-http-pthreads', + 'src/smol-http-pthreads.c', + install: true, +) \ No newline at end of file diff --git a/src/smol-http-pthreads.c b/src/smol-http-pthreads.c new file mode 100644 index 0000000..0089e84 --- /dev/null +++ b/src/smol-http-pthreads.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 8081 +#define BUFFER_SIZE 512 +#define MAX_PATH 128 +#define ROOT_DIR "." + +typedef struct handle_request_data { + int socket; + char *req; +} handle_request_data; + +static const struct { const char *ext; const char *type; } mime_types[] = { + {".html", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {NULL, "application/octet-stream"} +}; + +static const char *get_mime_type(const char *path) { + const char *ext = strrchr(path, '.'); + if (!ext) return mime_types[0].type; + for (int i = 0; mime_types[i].ext; i++) { + if (!strcmp(ext, mime_types[i].ext)) return mime_types[i].type; + } + return mime_types[0].type; +} + +static void send_response(int ssl, const char *status, const char *type, const char *body, size_t len) { + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\n\r\n", status, type, len); + write(ssl, buf, n); + if (len && body) write(ssl, body, len); +} + +static void write_full(int ssl, const char *data, size_t len) { + while (len > 0) { + int sent = write(ssl, data, len); + if (sent <= 0) break; + data += sent; + len -= sent; + } +} + +static void list_files(int ssl) { + char buf[8192] = "File Server

Files

    "; + size_t pos = strlen(buf); + DIR *dir = opendir(ROOT_DIR); + if (!dir) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Cannot open directory", 20); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) && pos < sizeof(buf) - 100) { + if (entry->d_name[0] == '.') continue; + struct stat st; + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", ROOT_DIR, entry->d_name); + if (stat(path, &st) < 0 || S_ISDIR(st.st_mode)) continue; + pos += snprintf(buf + pos, sizeof(buf) - pos, + "
  • %s
  • ", entry->d_name, entry->d_name); + } + closedir(dir); + + pos += snprintf(buf + pos, sizeof(buf) - pos, "
"); + send_response(ssl, "200 OK", "text/html", buf, pos); +} + +void *handle_request(void *param) { + handle_request_data *param_data = (handle_request_data*)param; + int ssl = param_data->socket; + char *req = param_data->req; + char method[8], path[MAX_PATH], proto[8]; + if (sscanf(req, "%7s %127s %7s", method, path, proto) != 3) { + send_response(ssl, "400 Bad Request", "text/plain", "Bad Request", 11); + close(ssl); + pthread_exit(0); + } + + if (strcmp(method, "GET")) { + send_response(ssl, "405 Method Not Allowed", "text/plain", "Method Not Allowed", 18); + close(ssl); + pthread_exit(0); + } + + if (strstr(path, "..")) { + send_response(ssl, "403 Forbidden", "text/plain", "Forbidden", 9); + close(ssl); + pthread_exit(0); + } + + if (!strcmp(path, "/favicon.ico")) { + send_response(ssl, "204 No Content", "text/plain", NULL, 0); + close(ssl); + pthread_exit(0); + } + + if (!strcmp(path, "/")) { + list_files(ssl); + close(ssl); + pthread_exit(0); + } + + char file_path[MAX_PATH]; + snprintf(file_path, sizeof(file_path), "%s/%s", ROOT_DIR, path[0] == '/' ? path + 1 : path); + + struct stat st; + if (stat(file_path, &st) < 0 || S_ISDIR(st.st_mode)) { + send_response(ssl, st.st_mode ? "403 Forbidden" : "404 Not Found", + "text/plain", st.st_mode ? "Directory listing not allowed" : "Not Found", + st.st_mode ? 28 : 9); + close(ssl); + pthread_exit(0); + } + + int fd = open(file_path, O_RDONLY); + if (fd < 0) { + send_response(ssl, "404 Not Found", "text/plain", "Not Found", 9); + close(ssl); + pthread_exit(0); + } + + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lld\r\n\r\n", + get_mime_type(file_path), (long long)st.st_size); + write_full(ssl, buf, n); + + char data[16384]; + off_t total_sent = 0; + ssize_t bytes; + while ((bytes = read(fd, data, sizeof(data))) > 0) { + write_full(ssl, data, bytes); + total_sent += bytes; + } + close(fd); + + if (total_sent != st.st_size) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Incomplete transfer", 18); + } + close(ssl); + pthread_exit(0); +} + +int main() { + struct stat st; + if (stat(ROOT_DIR, &st) < 0 || !S_ISDIR(st.st_mode)) return 1; + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + return 1; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(PORT) + }; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(sock, 5) < 0) { + close(sock); + return 1; + } + + printf("Server started on https://localhost:%d\n", PORT); + + char buf[BUFFER_SIZE]; + struct sockaddr_in client; + socklen_t len = sizeof(client); + + while (1) { + int client_sock = accept(sock, (struct sockaddr *)&client, &len); + if (client_sock < 0) continue; + + int n = read(client_sock, buf, BUFFER_SIZE - 1); + if (n > 0) { + buf[n] = '\0'; + handle_request_data data; + data.socket = client_sock; + data.req = buf; + pthread_t tid; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&tid, &attr, handle_request, &data); + // pthread_join(tid, NULL); + } + } + + close(sock); + return 0; +} diff --git a/src/smol-http.c b/src/smol-http.c new file mode 100644 index 0000000..5696d07 --- /dev/null +++ b/src/smol-http.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 8081 +#define BUFFER_SIZE 512 +#define MAX_PATH 128 +#define ROOT_DIR "." + +static const struct { const char *ext; const char *type; } mime_types[] = { + {".html", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {NULL, "application/octet-stream"} +}; + +static const char *get_mime_type(const char *path) { + const char *ext = strrchr(path, '.'); + if (!ext) return mime_types[0].type; + for (int i = 0; mime_types[i].ext; i++) { + if (!strcmp(ext, mime_types[i].ext)) return mime_types[i].type; + } + return mime_types[0].type; +} + +static void send_response(int ssl, const char *status, const char *type, const char *body, size_t len) { + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\n\r\n", status, type, len); + write(ssl, buf, n); + if (len && body) write(ssl, body, len); +} + +static void write_full(int ssl, const char *data, size_t len) { + while (len > 0) { + int sent = write(ssl, data, len); + if (sent <= 0) break; + data += sent; + len -= sent; + } +} + +static void list_files(int ssl) { + char buf[8192] = "File Server

Files

    "; + size_t pos = strlen(buf); + DIR *dir = opendir(ROOT_DIR); + if (!dir) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Cannot open directory", 20); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) && pos < sizeof(buf) - 100) { + if (entry->d_name[0] == '.') continue; + struct stat st; + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", ROOT_DIR, entry->d_name); + if (stat(path, &st) < 0 || S_ISDIR(st.st_mode)) continue; + pos += snprintf(buf + pos, sizeof(buf) - pos, + "
  • %s
  • ", entry->d_name, entry->d_name); + } + closedir(dir); + + pos += snprintf(buf + pos, sizeof(buf) - pos, "
"); + send_response(ssl, "200 OK", "text/html", buf, pos); +} + +static void handle_request(int ssl, char *req) { + char method[8], path[MAX_PATH], proto[8]; + if (sscanf(req, "%7s %127s %7s", method, path, proto) != 3) { + send_response(ssl, "400 Bad Request", "text/plain", "Bad Request", 11); + return; + } + + if (strcmp(method, "GET")) { + send_response(ssl, "405 Method Not Allowed", "text/plain", "Method Not Allowed", 18); + return; + } + + if (strstr(path, "..")) { + send_response(ssl, "403 Forbidden", "text/plain", "Forbidden", 9); + return; + } + + if (!strcmp(path, "/favicon.ico")) { + send_response(ssl, "204 No Content", "text/plain", NULL, 0); + return; + } + + if (!strcmp(path, "/")) { + list_files(ssl); + return; + } + + char file_path[MAX_PATH]; + snprintf(file_path, sizeof(file_path), "%s/%s", ROOT_DIR, path[0] == '/' ? path + 1 : path); + + struct stat st; + if (stat(file_path, &st) < 0 || S_ISDIR(st.st_mode)) { + send_response(ssl, st.st_mode ? "403 Forbidden" : "404 Not Found", + "text/plain", st.st_mode ? "Directory listing not allowed" : "Not Found", + st.st_mode ? 28 : 9); + return; + } + + int fd = open(file_path, O_RDONLY); + if (fd < 0) { + send_response(ssl, "404 Not Found", "text/plain", "Not Found", 9); + return; + } + + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lld\r\n\r\n", + get_mime_type(file_path), (long long)st.st_size); + write_full(ssl, buf, n); + + char data[16384]; + off_t total_sent = 0; + ssize_t bytes; + while ((bytes = read(fd, data, sizeof(data))) > 0) { + write_full(ssl, data, bytes); + total_sent += bytes; + } + close(fd); + + if (total_sent != st.st_size) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Incomplete transfer", 18); + } + close(ssl); +} + +int main() { + struct stat st; + if (stat(ROOT_DIR, &st) < 0 || !S_ISDIR(st.st_mode)) return 1; + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + return 1; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(PORT) + }; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(sock, 5) < 0) { + close(sock); + return 1; + } + + printf("Server started on https://localhost:%d\n", PORT); + + char buf[BUFFER_SIZE]; + struct sockaddr_in client; + socklen_t len = sizeof(client); + + while (1) { + int client_sock = accept(sock, (struct sockaddr *)&client, &len); + if (client_sock < 0) continue; + + int n = read(client_sock, buf, BUFFER_SIZE - 1); + if (n > 0) { + buf[n] = '\0'; + handle_request(client_sock, buf); + } + + // close(client_sock); + } + + close(sock); + return 0; +} diff --git a/src/smol-pthreads.c b/src/smol-pthreads.c new file mode 100644 index 0000000..0bf33fa --- /dev/null +++ b/src/smol-pthreads.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 8081 +#define BUFFER_SIZE 512 +#define MAX_PATH 128 +#define ROOT_DIR "." + +typedef struct handle_request_data { + SSL *ssl; + int socket; + char *req; +} handle_request_data; + +static const struct { const char *ext; const char *type; } mime_types[] = { + {".html", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {NULL, "application/octet-stream"} +}; + +static const char *get_mime_type(const char *path) { + const char *ext = strrchr(path, '.'); + if (!ext) return mime_types[0].type; + for (int i = 0; mime_types[i].ext; i++) { + if (!strcmp(ext, mime_types[i].ext)) return mime_types[i].type; + } + return mime_types[0].type; +} + +static void send_response(SSL *ssl, const char *status, const char *type, const char *body, size_t len) { + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\n\r\n", status, type, len); + SSL_write(ssl, buf, n); + if (len && body) SSL_write(ssl, body, len); +} + +static void write_full(SSL *ssl, const char *data, size_t len) { + while (len > 0) { + int sent = SSL_write(ssl, data, len); + if (sent <= 0) break; + data += sent; + len -= sent; + } +} + +static void list_files(SSL *ssl) { + char buf[8192] = "File Server

Files

    "; + size_t pos = strlen(buf); + DIR *dir = opendir(ROOT_DIR); + if (!dir) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Cannot open directory", 20); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) && pos < sizeof(buf) - 100) { + if (entry->d_name[0] == '.') continue; + struct stat st; + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", ROOT_DIR, entry->d_name); + if (stat(path, &st) < 0 || S_ISDIR(st.st_mode)) continue; + pos += snprintf(buf + pos, sizeof(buf) - pos, + "
  • %s
  • ", entry->d_name, entry->d_name); + } + closedir(dir); + + pos += snprintf(buf + pos, sizeof(buf) - pos, "
"); + send_response(ssl, "200 OK", "text/html", buf, pos); +} + +void *handle_request(void *param) { + handle_request_data *param_data = (handle_request_data*)param; + int client_sock = param_data->socket; + SSL *ssl = param_data->ssl; + char *req = param_data->req; + char method[8], path[MAX_PATH], proto[8]; + if (sscanf(req, "%7s %127s %7s", method, path, proto) != 3) { + send_response(ssl, "400 Bad Request", "text/plain", "Bad Request", 11); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + if (strcmp(method, "GET")) { + send_response(ssl, "405 Method Not Allowed", "text/plain", "Method Not Allowed", 18); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + if (strstr(path, "..")) { + send_response(ssl, "403 Forbidden", "text/plain", "Forbidden", 9); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + if (!strcmp(path, "/favicon.ico")) { + send_response(ssl, "204 No Content", "text/plain", NULL, 0); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + if (!strcmp(path, "/")) { + list_files(ssl); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + char file_path[MAX_PATH]; + snprintf(file_path, sizeof(file_path), "%s/%s", ROOT_DIR, path[0] == '/' ? path + 1 : path); + + struct stat st; + if (stat(file_path, &st) < 0 || S_ISDIR(st.st_mode)) { + send_response(ssl, st.st_mode ? "403 Forbidden" : "404 Not Found", + "text/plain", st.st_mode ? "Directory listing not allowed" : "Not Found", + st.st_mode ? 28 : 9); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + int fd = open(file_path, O_RDONLY); + if (fd < 0) { + send_response(ssl, "404 Not Found", "text/plain", "Not Found", 9); + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); + } + + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lld\r\n\r\n", + get_mime_type(file_path), (long long)st.st_size); + write_full(ssl, buf, n); + + char data[16384]; + off_t total_sent = 0; + ssize_t bytes; + while ((bytes = read(fd, data, sizeof(data))) > 0) { + write_full(ssl, data, bytes); + total_sent += bytes; + } + close(fd); + + if (total_sent != st.st_size) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Incomplete transfer", 18); + } + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + pthread_exit(0); +} + +int main() { + struct stat st; + if (stat(ROOT_DIR, &st) < 0 || !S_ISDIR(st.st_mode)) return 1; + + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + const SSL_METHOD *method = TLS_server_method(); + SSL_CTX *ctx = SSL_CTX_new(method); + if (!ctx) return 1; + + if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0 || + SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { + SSL_CTX_free(ctx); + return 1; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + SSL_CTX_free(ctx); + return 1; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(PORT) + }; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(sock, 5) < 0) { + close(sock); + SSL_CTX_free(ctx); + return 1; + } + + printf("Server started on https://localhost:%d\n", PORT); + + char buf[BUFFER_SIZE]; + struct sockaddr_in client; + socklen_t len = sizeof(client); + + while (1) { + int client_sock = accept(sock, (struct sockaddr *)&client, &len); + if (client_sock < 0) continue; + + SSL *ssl = SSL_new(ctx); + SSL_set_fd(ssl, client_sock); + + if (SSL_accept(ssl) <= 0) { + SSL_free(ssl); + close(client_sock); + continue; + } + + int n = SSL_read(ssl, buf, BUFFER_SIZE - 1); + if (n > 0) { + buf[n] = '\0'; + handle_request_data data; + data.ssl = ssl; + data.socket = client_sock; + data.req = buf; + pthread_t tid; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&tid, &attr, handle_request, &data); + } + } + + close(sock); + SSL_CTX_free(ctx); + return 0; +} diff --git a/src/smol.c b/src/smol.c new file mode 100644 index 0000000..7637b2d --- /dev/null +++ b/src/smol.c @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 8081 +#define BUFFER_SIZE 512 +#define MAX_PATH 128 +#define ROOT_DIR "." + +static const struct { const char *ext; const char *type; } mime_types[] = { + {".html", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {NULL, "application/octet-stream"} +}; + +static const char *get_mime_type(const char *path) { + const char *ext = strrchr(path, '.'); + if (!ext) return mime_types[0].type; + for (int i = 0; mime_types[i].ext; i++) { + if (!strcmp(ext, mime_types[i].ext)) return mime_types[i].type; + } + return mime_types[0].type; +} + +static void send_response(SSL *ssl, const char *status, const char *type, const char *body, size_t len) { + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\n\r\n", status, type, len); + SSL_write(ssl, buf, n); + if (len && body) SSL_write(ssl, body, len); +} + +static void write_full(SSL *ssl, const char *data, size_t len) { + while (len > 0) { + int sent = SSL_write(ssl, data, len); + if (sent <= 0) break; + data += sent; + len -= sent; + } +} + +static void list_files(SSL *ssl) { + char buf[8192] = "File Server

Files

    "; + size_t pos = strlen(buf); + DIR *dir = opendir(ROOT_DIR); + if (!dir) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Cannot open directory", 20); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) && pos < sizeof(buf) - 100) { + if (entry->d_name[0] == '.') continue; + struct stat st; + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", ROOT_DIR, entry->d_name); + if (stat(path, &st) < 0 || S_ISDIR(st.st_mode)) continue; + pos += snprintf(buf + pos, sizeof(buf) - pos, + "
  • %s
  • ", entry->d_name, entry->d_name); + } + closedir(dir); + + pos += snprintf(buf + pos, sizeof(buf) - pos, "
"); + send_response(ssl, "200 OK", "text/html", buf, pos); +} + +static void handle_request(SSL *ssl, char *req) { + char method[8], path[MAX_PATH], proto[8]; + if (sscanf(req, "%7s %127s %7s", method, path, proto) != 3) { + send_response(ssl, "400 Bad Request", "text/plain", "Bad Request", 11); + return; + } + + if (strcmp(method, "GET")) { + send_response(ssl, "405 Method Not Allowed", "text/plain", "Method Not Allowed", 18); + return; + } + + if (strstr(path, "..")) { + send_response(ssl, "403 Forbidden", "text/plain", "Forbidden", 9); + return; + } + + if (!strcmp(path, "/favicon.ico")) { + send_response(ssl, "204 No Content", "text/plain", NULL, 0); + return; + } + + if (!strcmp(path, "/")) { + list_files(ssl); + return; + } + + char file_path[MAX_PATH]; + snprintf(file_path, sizeof(file_path), "%s/%s", ROOT_DIR, path[0] == '/' ? path + 1 : path); + + struct stat st; + if (stat(file_path, &st) < 0 || S_ISDIR(st.st_mode)) { + send_response(ssl, st.st_mode ? "403 Forbidden" : "404 Not Found", + "text/plain", st.st_mode ? "Directory listing not allowed" : "Not Found", + st.st_mode ? 28 : 9); + return; + } + + int fd = open(file_path, O_RDONLY); + if (fd < 0) { + send_response(ssl, "404 Not Found", "text/plain", "Not Found", 9); + return; + } + + char buf[BUFFER_SIZE]; + int n = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lld\r\n\r\n", + get_mime_type(file_path), (long long)st.st_size); + write_full(ssl, buf, n); + + char data[16384]; + off_t total_sent = 0; + ssize_t bytes; + while ((bytes = read(fd, data, sizeof(data))) > 0) { + write_full(ssl, data, bytes); + total_sent += bytes; + } + close(fd); + + if (total_sent != st.st_size) { + send_response(ssl, "500 Internal Server Error", "text/plain", "Incomplete transfer", 18); + } +} + +int main() { + struct stat st; + if (stat(ROOT_DIR, &st) < 0 || !S_ISDIR(st.st_mode)) return 1; + + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + const SSL_METHOD *method = TLS_server_method(); + SSL_CTX *ctx = SSL_CTX_new(method); + if (!ctx) return 1; + + if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0 || + SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { + SSL_CTX_free(ctx); + return 1; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + SSL_CTX_free(ctx); + return 1; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(PORT) + }; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(sock, 5) < 0) { + close(sock); + SSL_CTX_free(ctx); + return 1; + } + + printf("Server started on https://localhost:%d\n", PORT); + + char buf[BUFFER_SIZE]; + struct sockaddr_in client; + socklen_t len = sizeof(client); + + while (1) { + int client_sock = accept(sock, (struct sockaddr *)&client, &len); + if (client_sock < 0) continue; + + SSL *ssl = SSL_new(ctx); + SSL_set_fd(ssl, client_sock); + + if (SSL_accept(ssl) <= 0) { + SSL_free(ssl); + close(client_sock); + continue; + } + + int n = SSL_read(ssl, buf, BUFFER_SIZE - 1); + if (n > 0) { + buf[n] = '\0'; + handle_request(ssl, buf); + } + + SSL_shutdown(ssl); + SSL_free(ssl); + close(client_sock); + } + + close(sock); + SSL_CTX_free(ctx); + return 0; +}