initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
build
|
||||||
|
.vscode
|
||||||
|
server.key
|
||||||
|
server.crt
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
+27
@@ -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,
|
||||||
|
)
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#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] = "<html><head><title>File Server</title></head><body><h1>Files</h1><ul>";
|
||||||
|
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,
|
||||||
|
"<li><a href=\"%s\">%s</a></li>", entry->d_name, entry->d_name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "</ul></body></html>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#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] = "<html><head><title>File Server</title></head><body><h1>Files</h1><ul>";
|
||||||
|
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,
|
||||||
|
"<li><a href=\"%s\">%s</a></li>", entry->d_name, entry->d_name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "</ul></body></html>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#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] = "<html><head><title>File Server</title></head><body><h1>Files</h1><ul>";
|
||||||
|
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,
|
||||||
|
"<li><a href=\"%s\">%s</a></li>", entry->d_name, entry->d_name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "</ul></body></html>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#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] = "<html><head><title>File Server</title></head><body><h1>Files</h1><ul>";
|
||||||
|
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,
|
||||||
|
"<li><a href=\"%s\">%s</a></li>", entry->d_name, entry->d_name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "</ul></body></html>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user