#include #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #pragma comment(lib, "ws2_32.lib") typedef SOCKET socket_t; #define CLOSESOCKET closesocket #define ISVALIDSOCK(s) ((s) != INVALID_SOCKET) #define STAT_FUNC _stat #define STAT_ST struct _stat #else #include #include #include #include #include typedef int socket_t; #define CLOSESOCKET close #define ISVALIDSOCK(s) ((s) >= 0) #define INVALID_SOCKET (-1) #define STAT_FUNC stat #define STAT_ST struct stat #endif #define LISTEN_PORT 8080 #define REQ_BUF_SIZE 4096 #define FILE_BUF_SIZE 8192 #define HDR_BUF_SIZE 512 #define PATH_BUF_SIZE 512 /* ------------------------------------------------------------------ */ /* MIME type lookup */ /* ------------------------------------------------------------------ */ typedef struct { const char *ext; const char *mime; } mime_entry_t; static const mime_entry_t mime_map[] = { { ".html", "text/html;charset=utf-8" }, { ".css", "text/css;charset=utf-8" }, { ".js", "application/javascript;charset=utf-8" }, { ".json", "application/json" }, { ".png", "image/png" }, { ".jpg", "image/jpeg" }, { ".jpeg", "image/jpeg" }, { ".gif", "image/gif" }, { ".svg", "image/svg+xml" }, { ".ico", "image/x-icon" }, { ".woff", "font/woff" }, { ".woff2", "font/woff2" }, { ".ttf", "font/ttf" }, { ".wasm", "application/wasm" }, { ".pdf", "application/pdf" }, { ".txt", "text/plain;charset=utf-8" }, { ".xml", "application/xml" }, { NULL, NULL } }; static const char *mime_from_path(const char *path) { const char *dot = strrchr(path, '.'); if (dot) { for (const mime_entry_t *e = mime_map; e->ext; ++e) { if (strcmp(dot, e->ext) == 0) return e->mime; } } return "application/octet-stream"; } /* ------------------------------------------------------------------ */ /* Send an HTTP error response */ /* ------------------------------------------------------------------ */ static void send_error(socket_t sock, const char *status) { char hdr[256]; int len = (int)strlen(status); int n = snprintf(hdr, sizeof(hdr), "HTTP/1.1 %s\r\n" "Content-Type: text/plain\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n", status, len); if (n > 0 && n < (int)sizeof(hdr)) { send(sock, hdr, n, 0); send(sock, status, len, 0); } } /* ------------------------------------------------------------------ */ /* Handle one accepted connection */ /* ------------------------------------------------------------------ */ static void handle_request(socket_t sock) { char req_buf[REQ_BUF_SIZE]; int n = (int)recv(sock, req_buf, sizeof(req_buf) - 1, 0); if (n <= 0) return; req_buf[n] = '\0'; /* ---- Parse "GET /path HTTP/1.x\r\n..." ---- */ if (strncmp(req_buf, "GET ", 4) != 0) { send_error(sock, "405 Method Not Allowed"); return; } char *path_start = req_buf + 4; char *path_end = strchr(path_start, ' '); if (!path_end) { send_error(sock, "400 Bad Request"); return; } *path_end = '\0'; const char *path = path_start; /* Default document */ if (strcmp(path, "/") == 0) path = "p2601.html"; /* Strip leading '/' */ while (*path == '/') ++path; /* Reject suspicious paths */ if (*path == '\0' || strstr(path, "..") != NULL || strchr(path, '\\') != NULL) { send_error(sock, "403 Forbidden"); return; } /* ---- Build filesystem path: out/ ---- */ char filepath[PATH_BUF_SIZE]; int pn = snprintf(filepath, sizeof(filepath), "out/%s", path); if (pn < 0 || pn >= (int)sizeof(filepath)) { send_error(sock, "414 URI Too Long"); return; } /* ---- Open and stat the file ---- */ FILE *fp = fopen(filepath, "rb"); if (!fp) { send_error(sock, "404 Not Found"); return; } STAT_ST st; if (STAT_FUNC(filepath, &st) != 0) { fclose(fp); send_error(sock, "500 Internal Server Error"); return; } long long file_size = (long long)st.st_size; const char *mime = mime_from_path(path); /* ---- Send response header ---- */ char hdr_buf[HDR_BUF_SIZE]; int hdr_len = snprintf(hdr_buf, sizeof(hdr_buf), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %lld\r\n" "Connection: close\r\n" "\r\n", mime, file_size); if (hdr_len < 0 || hdr_len >= (int)sizeof(hdr_buf)) { fclose(fp); return; } if (send(sock, hdr_buf, hdr_len, 0) <= 0) { fclose(fp); return; } /* ---- Stream the file body in chunks ---- */ char file_buf[FILE_BUF_SIZE]; size_t bytes; while ((bytes = fread(file_buf, 1, sizeof(file_buf), fp)) > 0) { size_t sent = 0; while (sent < bytes) { int s = send(sock, file_buf + sent, (int)(bytes - sent), 0); if (s <= 0) { fclose(fp); return; } sent += (size_t)s; } } fclose(fp); } /* ------------------------------------------------------------------ */ /* main — bind, listen, accept loop */ /* ------------------------------------------------------------------ */ int main(void) { #ifdef _WIN32 WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { fprintf(stderr, "WSAStartup failed\n"); return 1; } #endif socket_t srv = socket(AF_INET, SOCK_STREAM, 0); if (!ISVALIDSOCK(srv)) { perror("socket"); return 1; } /* SO_REUSEADDR */ int opt = 1; #ifdef _WIN32 setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); #else setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); #endif struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(LISTEN_PORT); if (bind(srv, (struct sockaddr *)&addr, sizeof(addr)) != 0) { perror("bind"); CLOSESOCKET(srv); return 1; } if (listen(srv, SOMAXCONN) != 0) { perror("listen"); CLOSESOCKET(srv); return 1; } fprintf(stderr, ":: Listening at http://localhost:%d\n", LISTEN_PORT); for (;;) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); #ifdef _WIN32 socket_t client = accept(srv, (struct sockaddr *)&client_addr, (int *)&client_len); #else socket_t client = accept(srv, (struct sockaddr *)&client_addr, &client_len); #endif if (!ISVALIDSOCK(client)) continue; handle_request(client); CLOSESOCKET(client); } /* unreachable, but tidy */ CLOSESOCKET(srv); #ifdef _WIN32 WSACleanup(); #endif return 0; }