← All tasks
cppclaude-code/cpp-t1 #28Not a task: already works

Static HTTP File Server (cpp, written by Claude Code)

envgap__claude-code__cpp-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
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #28 · read the task the agent was given
Claude Code wrote this cpp 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 C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt 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.

CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(static-file-server VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# cpp-httplib
FetchContent_Declare(
    httplib
    GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
    GIT_TAG v0.15.3
)
FetchContent_MakeAvailable(httplib)

# nlohmann/json
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)

add_executable(file_server main.cpp)

target_link_libraries(file_server PRIVATE
    httplib::httplib
    nlohmann_json::nlohmann_json
)

# Optional OpenSSL support for HTTPS
find_package(OpenSSL QUIET)
if(OpenSSL_FOUND)
    target_compile_definitions(file_server PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
    target_link_libraries(file_server PRIVATE OpenSSL::SSL OpenSSL::Crypto)
    message(STATUS "OpenSSL found - HTTPS support enabled")
else()
    message(STATUS "OpenSSL not found - HTTPS support disabled")
endif()

# Platform-specific settings
if(WIN32)
    target_link_libraries(file_server PRIVATE ws2_32)
endif()
main.cpp
/**
 * Static HTTP File Server using cpp-httplib.
 *
 * 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
 */

#include <httplib.h>
#include <nlohmann/json.hpp>

#include <algorithm>
#include <chrono>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>

namespace fs = std::filesystem;

// MIME type mapping
static const std::unordered_map<std::string, std::string> MIME_TYPES = {
    {".html", "text/html"},
    {".htm", "text/html"},
    {".css", "text/css"},
    {".js", "application/javascript"},
    {".json", "application/json"},
    {".xml", "application/xml"},
    {".txt", "text/plain"},
    {".csv", "text/csv"},
    {".png", "image/png"},
    {".jpg", "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".gif", "image/gif"},
    {".svg", "image/svg+xml"},
    {".ico", "image/x-icon"},
    {".webp", "image/webp"},
    {".bmp", "image/bmp"},
    {".pdf", "application/pdf"},
    {".zip", "application/zip"},
    {".gz", "application/gzip"},
    {".tar", "application/x-tar"},
    {".mp3", "audio/mpeg"},
    {".mp4", "video/mp4"},
    {".webm", "video/webm"},
    {".ogg", "audio/ogg"},
    {".wav", "audio/wav"},
    {".woff", "font/woff"},
    {".woff2", "font/woff2"},
    {".ttf", "font/ttf"},
    {".otf", "font/otf"},
    {".eot", "application/vnd.ms-fontobject"},
    {".wasm", "application/wasm"},
};

// Configuration
struct Config {
    std::string serve_dir = "public";
    int port = 8080;
    std::string host = "0.0.0.0";
    int cache_max_age = 3600;
    std::string cors_origin = "*";
    std::string ssl_cert;
    std::string ssl_key;
};

/**
 * Get MIME type for a file extension.
 */
std::string get_mime_type(const std::string& filepath) {
    auto ext = fs::path(filepath).extension().string();
    std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
    auto it = MIME_TYPES.find(ext);
    if (it != MIME_TYPES.end()) {
        return it->second;
    }
    return "application/octet-stream";
}

/**
 * Compute a simple ETag from file metadata.
 */
std::string compute_etag(const fs::path& filepath) {
    auto ftime = fs::last_write_time(filepath);
    auto size = fs::file_size(filepath);
    auto time_val = ftime.time_since_epoch().count();
    std::hash<std::string> hasher;
    std::string raw = std::to_string(time_val) + "-" + std::to_string(size) + "-" + filepath.string();
    size_t hash = hasher(raw);
    std::stringstream ss;
    ss << std::hex << hash;
    return ss.str();
}

/**
 * Format file size into human-readable string.
 */
std::string format_size(uintmax_t bytes) {
    std::stringstream ss;
    if (bytes < 1024) {
        ss << bytes << " B";
    } else if (bytes < 1024 * 1024) {
        ss << std::fixed << std::setprecision(1) << (double)bytes / 1024.0 << " KB";
    } else if (bytes < 1024ULL * 1024 * 1024) {
        ss << std::fixed << std::setprecision(1) << (double)bytes / (1024.0 * 1024) << " MB";
    } else {
        ss << std::fixed << std::setprecision(1) << (double)bytes / (1024.0 * 1024 * 1024) << " GB";
    }
    return ss.str();
}

/**
 * Format time_t to HTTP date string.
 */
std::string format_http_date(std::time_t t) {
    std::tm* gmt = std::gmtime(&t);
    char buf[128];
    std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
    return std::string(buf);
}

/**
 * Format time_t to display string.
 */
std::string format_display_date(std::time_t t) {
    std::tm* gmt = std::gmtime(&t);
    char buf[64];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", gmt);
    return std::string(buf);
}

/**
 * Convert filesystem time to time_t.
 */
std::time_t to_time_t(fs::file_time_type ftime) {
    auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
        ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
    return std::chrono::system_clock::to_time_t(sctp);
}

/**
 * Escape HTML special characters.
 */
std::string escape_html(const std::string& str) {
    std::string result;
    result.reserve(str.size());
    for (char c : str) {
        switch (c) {
            case '&': result += "&amp;"; break;
            case '<': result += "&lt;"; break;
            case '>': result += "&gt;"; break;
            case '"': result += "&quot;"; break;
            default: result += c;
        }
    }
    return result;
}

/**
 * Generate directory listing HTML.
 */
std::string generate_directory_listing(const fs::path& dir_path, const std::string& url_path) {
    std::stringstream html;
    html << "<!DOCTYPE html><html lang=\"en\"><head>";
    html << "<meta charset=\"UTF-8\">";
    html << "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
    html << "<title>Index of " << escape_html(url_path) << "</title>";
    html << "<style>";
    html << "body{font-family:monospace;margin:40px;background:#f5f5f5}";
    html << "h1{color:#333;border-bottom:1px solid #ccc;padding-bottom:10px}";
    html << "table{border-collapse:collapse;width:100%}";
    html << "th,td{text-align:left;padding:8px 16px}";
    html << "th{background:#e0e0e0}tr:nth-child(even){background:#fff}";
    html << "tr:hover{background:#e8e8ff}a{color:#0066cc;text-decoration:none}";
    html << "a:hover{text-decoration:underline}.size{text-align:right}";
    html << "</style></head><body>";
    html << "<h1>Index of " << escape_html(url_path) << "</h1>";
    html << "<table><thead><tr><th>Name</th><th>Size</th><th>Last Modified</th></tr></thead><tbody>";

    if (url_path != "/") {
        html << "<tr><td><a href=\"../\">..</a></td><td>-</td><td>-</td></tr>";
    }

    // Collect and sort entries
    std::vector<fs::directory_entry> entries;
    for (const auto& entry : fs::directory_iterator(dir_path)) {
        entries.push_back(entry);
    }
    std::sort(entries.begin(), entries.end(),
              [](const auto& a, const auto& b) {
                  return a.path().filename().string() < b.path().filename().string();
              });

    for (const auto& entry : entries) {
        bool is_dir = entry.is_directory();
        std::string name = entry.path().filename().string() + (is_dir ? "/" : "");
        std::string href = entry.path().filename().string() + (is_dir ? "/" : "");
        std::string size = is_dir ? "-" : format_size(entry.file_size());
        std::string modified = format_display_date(to_time_t(entry.last_write_time()));

        html << "<tr><td><a href=\"" << escape_html(href) << "\">";
        html << escape_html(name) << "</a></td>";
        html << "<td class=\"size\">" << size << "</td>";
        html << "<td>" << modified << "</td></tr>";
    }

    html << "</tbody></table></body></html>";
    return html.str();
}

/**
 * Add CORS headers to a response.
 */
void add_cors_headers(httplib::Response& res, const Config& config) {
    res.set_header("Access-Control-Allow-Origin", config.cors_origin);
    res.set_header("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
    res.set_header("Access-Control-Allow-Headers", "Range, Content-Type");
    res.set_header("Access-Control-Expose-Headers", "Content-Length, Content-Range, Accept-Ranges");
}

/**
 * Parse command-line arguments.
 */
Config parse_args(int argc, char* argv[]) {
    Config config;

    // Check environment variables first
    if (const char* env = std::getenv("SERVE_DIR")) config.serve_dir = env;
    if (const char* env = std::getenv("CACHE_MAX_AGE")) config.cache_max_age = std::stoi(env);
    if (const char* env = std::getenv("CORS_ORIGIN")) config.cors_origin = env;

    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "--dir" && i + 1 < argc) config.serve_dir = argv[++i];
        else if (arg == "--port" && i + 1 < argc) config.port = std::stoi(argv[++i]);
        else if (arg == "--host" && i + 1 < argc) config.host = argv[++i];
        else if (arg == "--ssl-cert" && i + 1 < argc) config.ssl_cert = argv[++i];
        else if (arg == "--ssl-key" && i + 1 < argc) config.ssl_key = argv[++i];
        else if (arg == "--cors-origin" && i + 1 < argc) config.cors_origin = argv[++i];
        else if (arg == "--cache-max-age" && i + 1 < argc) config.cache_max_age = std::stoi(argv[++i]);
    }

    config.serve_dir = fs::absolute(config.serve_dir).string();
    return config;
}

int main(int argc, char* argv[]) {
    Config config = parse_args(argc, argv);

    // Ensure serve directory exists
    fs::create_directories(config.serve_dir);

    // Create server (HTTP or HTTPS)
    std::unique_ptr<httplib::Server> svr;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
    if (!config.ssl_cert.empty() && !config.ssl_key.empty()) {
        svr = std::make_unique<httplib::SSLServer>(
            config.ssl_cert.c_str(), config.ssl_key.c_str());
        std::cout << "HTTPS enabled with cert: " << config.ssl_cert << std::endl;
    } else {
        svr = std::make_unique<httplib::Server>();
    }
#else
    svr = std::make_unique<httplib::Server>();
    if (!config.ssl_cert.empty()) {
        std::cerr << "Warning: SSL support not compiled in. Running HTTP only." << std::endl;
    }
#endif

    // OPTIONS preflight handler
    svr->Options(".*", [&config](const httplib::Request&, httplib::Response& res) {
        add_cors_headers(res, config);
        res.status = 204;
    });

    // Main catch-all handler
    svr->Get(".*", [&config](const httplib::Request& req, httplib::Response& res) {
        add_cors_headers(res, config);

        // Resolve path safely
        fs::path base_path = fs::canonical(config.serve_dir);
        std::string request_path = req.path;
        if (request_path.empty() || request_path[0] == '/') {
            request_path = request_path.substr(request_path.find_first_not_of('/'));
        }

        fs::path resolved;
        try {
            resolved = fs::weakly_canonical(base_path / request_path);
        } catch (...) {
            res.status = 400;
            res.set_content("Bad Request", "text/plain");
            return;
        }

        // Prevent directory traversal
        std::string resolved_str = resolved.string();
        std::string base_str = base_path.string();
        if (resolved_str.substr(0, base_str.size()) != base_str) {
            res.status = 403;
            res.set_content("Forbidden", "text/plain");
            return;
        }

        if (!fs::exists(resolved)) {
            res.status = 404;
            res.set_content("Not Found", "text/plain");
            return;
        }

        // Directory handling
        if (fs::is_directory(resolved)) {
            // Check for index.html
            fs::path index_path = resolved / "index.html";
            if (fs::is_regular_file(index_path)) {
                resolved = index_path;
            } else {
                std::string listing = generate_directory_listing(resolved, req.path);
                res.set_content(listing, "text/html");
                return;
            }
        }

        // File serving
        auto file_size = fs::file_size(resolved);
        auto ftime = fs::last_write_time(resolved);
        std::time_t mtime = to_time_t(ftime);
        std::string etag = compute_etag(resolved);
        std::string mime_type = get_mime_type(resolved.string());

        // Check If-None-Match
        auto if_none_match = req.get_header_value("If-None-Match");
        if (!if_none_match.empty()) {
            std::string clean_etag = if_none_match;
            clean_etag.erase(std::remove(clean_etag.begin(), clean_etag.end(), '"'), clean_etag.end());
            if (clean_etag == etag) {
                res.status = 304;
                return;
            }
        }

        // Set cache headers
        res.set_header("ETag", "\"" + etag + "\"");
        res.set_header("Last-Modified", format_http_date(mtime));
        res.set_header("Cache-Control", "public, max-age=" + std::to_string(config.cache_max_age));
        res.set_header("Accept-Ranges", "bytes");

        // Handle range requests
        auto range_header = req.get_header_value("Range");
        if (!range_header.empty() && range_header.substr(0, 6) == "bytes=") {
            std::string range_spec = range_header.substr(6);
            size_t dash = range_spec.find('-');
            if (dash != std::string::npos) {
                int64_t start = range_spec.substr(0, dash).empty() ? 0 : std::stoll(range_spec.substr(0, dash));
                int64_t end = (dash + 1 >= range_spec.size()) ? (int64_t)file_size - 1 : std::stoll(range_spec.substr(dash + 1));

                if (start >= (int64_t)file_size || end >= (int64_t)file_size || start > end) {
                    res.status = 416;
                    res.set_header("Content-Range", "bytes */" + std::to_string(file_size));
                    return;
                }

                int64_t content_length = end - start + 1;
                std::ifstream file(resolved, std::ios::binary);
                file.seekg(start);
                std::string data(content_length, '\0');
                file.read(&data[0], content_length);

                res.status = 206;
                res.set_header("Content-Range",
                    "bytes " + std::to_string(start) + "-" + std::to_string(end) + "/" + std::to_string(file_size));
                res.set_content(data, mime_type);
                return;
            }
        }

        // Full file response
        std::ifstream file(resolved, std::ios::binary);
        std::string content((std::istreambuf_iterator<char>(file)),
                             std::istreambuf_iterator<char>());
        res.set_content(content, mime_type);
    });

    std::string protocol = (!config.ssl_cert.empty() && !config.ssl_key.empty()) ? "https" : "http";
    std::cout << "Serving files from: " << config.serve_dir << std::endl;
    std::cout << "Server running at " << protocol << "://" << config.host << ":" << config.port << std::endl;

    svr->listen(config.host, config.port);
    return 0;
}
README.md
# Static HTTP File Server - C++ (cpp-httplib)

A static HTTP file server built with cpp-httplib that serves files with MIME type detection,
directory listing, caching, range requests, CORS, and optional HTTPS support.

## Dependencies

- cpp-httplib 0.15.3
- nlohmann/json 3.11.3
- OpenSSL (optional, for HTTPS)

## Build

```bash
mkdir build && cd build
cmake ..
cmake --build .
```

## Usage

```bash
# Basic usage (serves ./public on port 8080)
./file_server

# Custom directory and port
./file_server --dir /path/to/files --port 3000

# With HTTPS (requires OpenSSL)
./file_server --ssl-cert cert.pem --ssl-key key.pem

# Custom CORS origin
./file_server --cors-origin "https://example.com"
```

## Features

- **MIME Detection**: Automatic content type detection based on file extension
- **Directory Listing**: HTML directory browser with file sizes and modification dates
- **Caching**: ETag, Last-Modified, and Cache-Control headers
- **Range Requests**: Partial content delivery for large files and media streaming
- **CORS**: Configurable cross-origin resource sharing
- **HTTPS**: Optional TLS/SSL support via OpenSSL (compile-time flag)

## Environment Variables

- `SERVE_DIR`: Directory to serve (default: `./public`)
- `CACHE_MAX_AGE`: Cache max-age in seconds (default: `3600`)
- `CORS_ORIGIN`: Allowed CORS origin (default: `*`)