Files
smol/src/smol-http.c
T
2025-04-14 15:21:10 +07:00

185 lines
5.3 KiB
C

#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;
}