Static HTTP File Server (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-28
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/javascript-t1 #28 · read the task the agent was given
Claude Code wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Static HTTP File Server Write a program that serves static files over HTTP from a local directory, supporting directory listings, MIME type detection, caching headers, range requests for large files, and access logging. FUNCTIONAL REQUIREMENTS: - Accept a directory path to serve as a command-line argument (default: current directory) - Start an HTTP server on a configurable port via --port flag (default: 8080) and bind address via --host flag (default: 0.0.0.0) - Serve files with correct MIME types auto-detected from file extensions (HTML, CSS, JS, images, fonts, JSON, XML, PDF, video, audio, etc.) - Generate directory listing pages when accessing a directory URL, showing file names, sizes, modification dates, and file type icons, with a --no-listing flag to disable this - Support index files: automatically serve index.html or index.htm if present in a directory - Implement HTTP caching headers: ETag based on file content hash, Last-Modified from file timestamp, and Cache-Control with configurable max-age via --cache flag (default: 3600 seconds) - Support HTTP Range requests for partial content delivery (needed for video/audio streaming and resumable downloads) - Log all requests to console in Common Log Format (CLF): remote address, timestamp, method, path, status code, response size, and response time - Save access logs to a file via --log flag - Support CORS headers via --cors flag to enable cross-origin requests (configurable allowed origins) - Support HTTPS via --ssl flag with --cert and --key flags for certificate and private key file paths - Print server startup information: URL, served directory, features enabled - If no directory is given, create a sample directory with HTML, CSS, JS, image, and text files, then start serving them and print the URL to access each - Handle errors: port already in use, permission denied, symlink traversal prevention, and graceful shutdown on SIGINT Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
package.json
{
"name": "static-file-server",
"version": "1.0.0",
"description": "A static HTTP file server with MIME detection, directory listing, caching, range requests, CORS, and HTTPS support",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js --dir ./public --port 8080"
},
"keywords": [
"static",
"file-server",
"http",
"express"
],
"license": "MIT",
"dependencies": {
"express": "4.18.2",
"serve-static": "1.15.0",
"cors": "2.8.5",
"mime-types": "2.1.35"
}
}
README.md
# Static HTTP File Server - JavaScript (Express) A static HTTP file server built with Express that serves files with MIME type detection, directory listing, caching, range requests, CORS, and optional HTTPS support. ## Dependencies - express 4.18.2 - serve-static 1.15.0 - cors 2.8.5 - mime-types 2.1.35 ## Setup ```bash npm install ``` ## Usage ```bash # Basic usage (serves ./public on port 8080) npm start # Custom directory and port node server.js --dir /path/to/files --port 3000 # With HTTPS node server.js --ssl-cert cert.pem --ssl-key key.pem # Custom CORS origin node server.js --cors-origin "https://example.com" ``` ## Features - **MIME Detection**: Automatic content type detection using mime-types - **Directory Listing**: HTML directory browser with file sizes and modification dates - **Caching**: ETag, Last-Modified, and Cache-Control headers - **Range Requests**: Partial content delivery with streaming for large files - **CORS**: Configurable cross-origin resource sharing - **HTTPS**: Optional TLS/SSL support via certificate files ## Environment Variables - `SERVE_DIR`: Directory to serve (default: `./public`) - `PORT`: Server port (default: `8080`) - `HOST`: Server host (default: `0.0.0.0`) - `CACHE_MAX_AGE`: Cache max-age in seconds (default: `3600`) - `CORS_ORIGIN`: Allowed CORS origin (default: `*`)
server.js
/**
* Static HTTP File Server using Express.
*
* Features:
* - Serves static files with automatic MIME type detection
* - Directory listing with HTML interface
* - Cache control headers (ETag, Last-Modified, Cache-Control)
* - Range request support for partial content delivery
* - CORS support for cross-origin requests
* - Optional HTTPS via SSL certificates
*/
const express = require('express');
const serveStatic = require('serve-static');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const https = require('https');
const mime = require('mime-types');
// Configuration
const SERVE_DIR = process.env.SERVE_DIR || path.join(process.cwd(), 'public');
const PORT = parseInt(process.env.PORT || '8080', 10);
const HOST = process.env.HOST || '0.0.0.0';
const CACHE_MAX_AGE = parseInt(process.env.CACHE_MAX_AGE || '3600', 10);
const CORS_ORIGIN = process.env.CORS_ORIGIN || '*';
// Parse CLI arguments
const args = process.argv.slice(2);
let serveDir = SERVE_DIR;
let port = PORT;
let host = HOST;
let sslCert = null;
let sslKey = null;
let corsOrigin = CORS_ORIGIN;
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--dir':
serveDir = path.resolve(args[++i]);
break;
case '--port':
port = parseInt(args[++i], 10);
break;
case '--host':
host = args[++i];
break;
case '--ssl-cert':
sslCert = args[++i];
break;
case '--ssl-key':
sslKey = args[++i];
break;
case '--cors-origin':
corsOrigin = args[++i];
break;
}
}
// Ensure serve directory exists
if (!fs.existsSync(serveDir)) {
fs.mkdirSync(serveDir, { recursive: true });
}
const app = express();
// CORS middleware
app.use(cors({
origin: corsOrigin,
methods: ['GET', 'HEAD', 'OPTIONS'],
allowedHeaders: ['Range', 'Content-Type'],
exposedHeaders: ['Content-Length', 'Content-Range', 'Accept-Ranges'],
}));
/**
* Format file size into human-readable string.
*/
function formatSize(bytes) {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
/**
* Compute ETag for a file.
*/
function computeEtag(filePath, stat) {
const raw = `${stat.mtimeMs}-${stat.size}-${filePath}`;
return crypto.createHash('md5').update(raw).digest('hex');
}
/**
* Escape HTML special characters.
*/
function escapeHtml(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
/**
* Generate directory listing HTML.
*/
function generateDirectoryListing(dirPath, urlPath) {
const entries = fs.readdirSync(dirPath).sort();
let html = `<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index of ${escapeHtml(urlPath)}</title>
<style>
body{font-family:monospace;margin:40px;background:#f5f5f5}
h1{color:#333;border-bottom:1px solid #ccc;padding-bottom:10px}
table{border-collapse:collapse;width:100%}
th,td{text-align:left;padding:8px 16px}
th{background:#e0e0e0}tr:nth-child(even){background:#fff}
tr:hover{background:#e8e8ff}a{color:#0066cc;text-decoration:none}
a:hover{text-decoration:underline}.size{text-align:right}
</style></head><body>
<h1>Index of ${escapeHtml(urlPath)}</h1>
<table><thead><tr><th>Name</th><th>Size</th><th>Last Modified</th></tr></thead><tbody>`;
if (urlPath !== '/') {
html += '<tr><td><a href="../">..</a></td><td>-</td><td>-</td></tr>';
}
for (const entry of entries) {
const entryPath = path.join(dirPath, entry);
let stat;
try {
stat = fs.statSync(entryPath);
} catch {
continue;
}
const isDir = stat.isDirectory();
const name = entry + (isDir ? '/' : '');
const href = encodeURIComponent(entry) + (isDir ? '/' : '');
const size = isDir ? '-' : formatSize(stat.size);
const modified = new Date(stat.mtimeMs).toISOString().replace('T', ' ').substring(0, 19);
html += `<tr><td><a href="${href}">${escapeHtml(name)}</a></td>`;
html += `<td class="size">${size}</td><td>${modified}</td></tr>`;
}
html += '</tbody></table></body></html>';
return html;
}
/**
* Handle range requests.
*/
function handleRangeRequest(req, res, filePath, stat, mimeType, rangeHeader) {
const fileSize = stat.size;
const rangeSpec = rangeHeader.replace('bytes=', '');
const parts = rangeSpec.split('-');
const start = parts[0] ? parseInt(parts[0], 10) : 0;
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
if (start >= fileSize || end >= fileSize || start > end) {
res.status(416).set('Content-Range', `bytes */${fileSize}`).end();
return;
}
const contentLength = end - start + 1;
res.status(206);
res.set({
'Content-Type': mimeType,
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Content-Length': contentLength,
});
const stream = fs.createReadStream(filePath, { start, end });
stream.pipe(res);
}
// Main request handler
app.use((req, res, next) => {
// Decode the URL path
let urlPath;
try {
urlPath = decodeURIComponent(req.path);
} catch {
res.status(400).send('Bad Request');
return;
}
// Resolve absolute path, prevent directory traversal
const absPath = path.normalize(path.join(serveDir, urlPath));
if (!absPath.startsWith(path.normalize(serveDir))) {
res.status(403).send('Forbidden');
return;
}
// Check if path exists
if (!fs.existsSync(absPath)) {
res.status(404).send('Not Found');
return;
}
const stat = fs.statSync(absPath);
// Directory handling
if (stat.isDirectory()) {
// Redirect if missing trailing slash
if (!req.path.endsWith('/')) {
res.redirect(301, req.path + '/');
return;
}
// Check for index.html
const indexPath = path.join(absPath, 'index.html');
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
// Serve index.html as a file
const filePath = indexPath;
const fileStat = fs.statSync(filePath);
const mimeType = 'text/html';
const etag = computeEtag(filePath, fileStat);
res.set({
'ETag': `"${etag}"`,
'Last-Modified': new Date(fileStat.mtimeMs).toUTCString(),
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`,
'Accept-Ranges': 'bytes',
'Content-Type': mimeType,
});
fs.createReadStream(filePath).pipe(res);
return;
}
// Generate directory listing
const html = generateDirectoryListing(absPath, urlPath);
res.set('Content-Type', 'text/html').send(html);
return;
}
// File handling
const mimeType = mime.lookup(absPath) || 'application/octet-stream';
const etag = computeEtag(absPath, stat);
const lastModified = new Date(stat.mtimeMs).toUTCString();
// Check conditional headers
const ifNoneMatch = req.get('If-None-Match');
if (ifNoneMatch && ifNoneMatch.replace(/"/g, '') === etag) {
res.status(304).end();
return;
}
const ifModifiedSince = req.get('If-Modified-Since');
if (ifModifiedSince) {
const imsDate = new Date(ifModifiedSince);
if (stat.mtimeMs <= imsDate.getTime()) {
res.status(304).end();
return;
}
}
// Set cache headers
res.set({
'ETag': `"${etag}"`,
'Last-Modified': lastModified,
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`,
'Accept-Ranges': 'bytes',
'Content-Type': mimeType,
'Content-Length': stat.size,
});
// Handle range requests
const rangeHeader = req.get('Range');
if (rangeHeader) {
handleRangeRequest(req, res, absPath, stat, mimeType, rangeHeader);
return;
}
// Stream full file
fs.createReadStream(absPath).pipe(res);
});
// Start server
function startServer() {
let server;
if (sslCert && sslKey) {
const options = {
cert: fs.readFileSync(sslCert),
key: fs.readFileSync(sslKey),
};
server = https.createServer(options, app);
server.listen(port, host, () => {
console.log(`Serving files from: ${serveDir}`);
console.log(`Server running at https://${host}:${port}`);
});
} else {
server = app.listen(port, host, () => {
console.log(`Serving files from: ${serveDir}`);
console.log(`Server running at http://${host}:${port}`);
});
}
return server;
}
startServer();
module.exports = { app, startServer };