const std = @import("std"); pub fn main() !void { const addr = try std.net.Address.parseIp("0.0.0.0", 8080); var server = try addr.listen(.{ .reuse_address = true }); defer server.deinit(); std.debug.print(":: Listening at http://localhost:8080\n", .{}); while (true) { const conn = server.accept() catch continue; defer conn.stream.close(); handle_request(conn.stream) catch continue; } } fn handle_request(stream: std.net.Stream) !void { var req_buf: [4096]u8 = undefined; const n = try stream.read(&req_buf); if (n == 0) return error.Empty; const request = req_buf[0..n]; // Parse "GET /path HTTP/1.x\r\n..." const path = blk: { if (!std.mem.startsWith(u8, request, "GET ")) return send_error(stream, "405 Method Not Allowed"); const rest = request[4..]; const end = std.mem.indexOfScalar(u8, rest, ' ') orelse return error.BadRequest; break :blk rest[0..end]; }; const raw = if (std.mem.eql(u8, path, "/")) "/p2601.html" else path; // Strip leading '/' and reject anything suspicious const rel = std.mem.trimLeft(u8, raw, "/"); if (rel.len == 0 or std.mem.indexOf(u8, rel, "..") != null or std.mem.indexOfScalar(u8, rel, '\\') != null or rel[0] == '/') { return send_error(stream, "403 Forbidden"); } // Open from ./out/ const dir = std.fs.cwd().openDir("out", .{}) catch return send_error(stream, "500 Internal Server Error"); const file = dir.openFile(rel, .{}) catch return send_error(stream, "404 Not Found"); defer file.close(); const stat = try file.stat(); const size = stat.size; const mime = mime_from_path(rel); // Send header var hdr_buf: [512]u8 = undefined; const header = std.fmt.bufPrint(&hdr_buf, "HTTP/1.1 200 OK\r\n" ++ "Content-Type: {s}\r\n" ++ "Content-Length: {d}\r\n" ++ "Connection: close\r\n" ++ "\r\n", .{ mime, size }, ) catch return error.HeaderTooLarge; stream.writeAll(header) catch return; // Stream the file in chunks var buf: [8192]u8 = undefined; while (true) { const bytes = file.read(&buf) catch return; if (bytes == 0) break; stream.writeAll(buf[0..bytes]) catch return; } } fn send_error(stream: std.net.Stream, comptime status: []const u8) error{SendFailed} { const body = status; var hdr_buf: [256]u8 = undefined; const header = std.fmt.bufPrint(&hdr_buf, "HTTP/1.1 " ++ status ++ "\r\n" ++ "Content-Type: text/plain\r\n" ++ "Content-Length: {d}\r\n" ++ "Connection: close\r\n" ++ "\r\n", .{body.len}, ) catch return error.SendFailed; stream.writeAll(header) catch {}; stream.writeAll(body) catch {}; return error.SendFailed; } fn mime_from_path(path: []const u8) []const u8 { const ext = std.fs.path.extension(path); const 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" }, }; inline for (map) |entry| { if (std.mem.eql(u8, ext, entry[0])) return entry[1]; } return "application/octet-stream"; }