274 lines
7.2 KiB
C
274 lines
7.2 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#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 <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
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/<rel> ---- */
|
|
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;
|
|
}
|