← All tasks
pythonclaude-code/python-t1 #28Not a task: repair changed code

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

envgap__claude-code__python-t1-28

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

SyntaxError: global declaration after variable use
Not a benchmark task.
  • Its repair changed source code, so it is not an environment task.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
requirements.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/python-t1 #28 · read the task the agent was given
Claude Code wrote this python project from the task below. It does not run 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 Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include:
- Source code
- requirements.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.

README.md
# Static HTTP File Server - Python (Flask)

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

## Dependencies

- Flask 3.0.1

## Setup

```bash
pip install -r requirements.txt
```

## Usage

```bash
# Basic usage (serves ./public on port 8080)
python server.py

# Custom directory and port
python server.py --dir /path/to/files --port 3000

# With HTTPS
python server.py --ssl-cert cert.pem --ssl-key key.pem

# Custom CORS origin
python server.py --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 certificate files

## 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: `*`)
requirements.txt
flask==3.0.1
server.py
"""
Static HTTP File Server using Flask.

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
"""

import os
import hashlib
import mimetypes
import ssl
import argparse
from datetime import datetime, timezone
from pathlib import Path
from email.utils import formatdate, parsedate_to_datetime

from flask import (
    Flask,
    send_file,
    request,
    Response,
    abort,
    render_template_string,
)

app = Flask(__name__)

# Configuration
SERVE_DIRECTORY = os.environ.get("SERVE_DIR", os.path.join(os.getcwd(), "public"))
CACHE_MAX_AGE = int(os.environ.get("CACHE_MAX_AGE", 3600))
CORS_ORIGIN = os.environ.get("CORS_ORIGIN", "*")

# Ensure mimetypes are initialized
mimetypes.init()

# Directory listing HTML template
DIRECTORY_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Index of {{ path }}</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 {{ path }}</h1>
    <table>
        <thead>
            <tr><th>Name</th><th>Size</th><th>Last Modified</th></tr>
        </thead>
        <tbody>
            {% if path != '/' %}
            <tr><td><a href="../">..</a></td><td>-</td><td>-</td></tr>
            {% endif %}
            {% for entry in entries %}
            <tr>
                <td><a href="{{ entry.href }}">{{ entry.name }}</a></td>
                <td class="size">{{ entry.size }}</td>
                <td>{{ entry.modified }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</body>
</html>
"""


def format_size(size_bytes):
    """Format file size into human-readable string."""
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.1f} KB"
    elif size_bytes < 1024 * 1024 * 1024:
        return f"{size_bytes / (1024 * 1024):.1f} MB"
    else:
        return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"


def compute_etag(file_path):
    """Compute ETag based on file modification time and size."""
    stat = os.stat(file_path)
    raw = f"{stat.st_mtime}-{stat.st_size}-{file_path}"
    return hashlib.md5(raw.encode()).hexdigest()


def add_cors_headers(response):
    """Add CORS headers to the response."""
    response.headers["Access-Control-Allow-Origin"] = CORS_ORIGIN
    response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Range, Content-Type"
    response.headers["Access-Control-Expose-Headers"] = (
        "Content-Length, Content-Range, Accept-Ranges"
    )
    return response


@app.after_request
def after_request(response):
    """Apply CORS headers to all responses."""
    return add_cors_headers(response)


@app.route("/", defaults={"req_path": ""})
@app.route("/<path:req_path>")
def serve(req_path):
    """Main handler for serving files and directory listings."""
    # Handle OPTIONS preflight
    if request.method == "OPTIONS":
        response = Response()
        return add_cors_headers(response)

    # Resolve the absolute path, preventing directory traversal
    abs_path = os.path.normpath(os.path.join(SERVE_DIRECTORY, req_path))
    if not abs_path.startswith(os.path.normpath(SERVE_DIRECTORY)):
        abort(403)

    # Check if path exists
    if not os.path.exists(abs_path):
        abort(404)

    # Directory listing
    if os.path.isdir(abs_path):
        return handle_directory(abs_path, req_path)

    # Serve file
    return handle_file(abs_path)


def handle_directory(abs_path, req_path):
    """Generate a directory listing page."""
    # Check for index.html
    index_path = os.path.join(abs_path, "index.html")
    if os.path.isfile(index_path):
        return handle_file(index_path)

    entries = []
    try:
        items = sorted(os.listdir(abs_path))
    except PermissionError:
        abort(403)

    for item in items:
        item_path = os.path.join(abs_path, item)
        try:
            stat = os.stat(item_path)
        except OSError:
            continue

        is_dir = os.path.isdir(item_path)
        entries.append(
            {
                "name": item + ("/" if is_dir else ""),
                "href": item + ("/" if is_dir else ""),
                "size": "-" if is_dir else format_size(stat.st_size),
                "modified": datetime.fromtimestamp(
                    stat.st_mtime, tz=timezone.utc
                ).strftime("%Y-%m-%d %H:%M:%S"),
            }
        )

    display_path = "/" + req_path if req_path else "/"
    html = render_template_string(
        DIRECTORY_TEMPLATE, path=display_path, entries=entries
    )
    return Response(html, mimetype="text/html")


def handle_file(abs_path):
    """Serve a file with caching, MIME detection, and range request support."""
    # MIME type detection
    mime_type, _ = mimetypes.guess_type(abs_path)
    if mime_type is None:
        mime_type = "application/octet-stream"

    stat = os.stat(abs_path)
    file_size = stat.st_size
    last_modified = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
    etag = compute_etag(abs_path)

    # Check If-None-Match (ETag)
    if_none_match = request.headers.get("If-None-Match")
    if if_none_match and if_none_match.strip('"') == etag:
        return Response(status=304)

    # Check If-Modified-Since
    if_modified_since = request.headers.get("If-Modified-Since")
    if if_modified_since:
        try:
            ims_date = parsedate_to_datetime(if_modified_since)
            if last_modified <= ims_date:
                return Response(status=304)
        except (ValueError, TypeError):
            pass

    # Common headers
    headers = {
        "ETag": f'"{etag}"',
        "Last-Modified": formatdate(
            timeval=stat.st_mtime, localtime=False, usegmt=True
        ),
        "Cache-Control": f"public, max-age={CACHE_MAX_AGE}",
        "Accept-Ranges": "bytes",
    }

    # Handle range requests
    range_header = request.headers.get("Range")
    if range_header:
        return handle_range_request(abs_path, file_size, mime_type, headers, range_header)

    # Full file response
    response = send_file(abs_path, mimetype=mime_type)
    for key, value in headers.items():
        response.headers[key] = value
    response.headers["Content-Length"] = file_size
    return response


def handle_range_request(abs_path, file_size, mime_type, headers, range_header):
    """Handle HTTP Range requests for partial content delivery."""
    try:
        range_spec = range_header.replace("bytes=", "")
        parts = range_spec.split("-")
        start = int(parts[0]) if parts[0] else 0
        end = int(parts[1]) if parts[1] else file_size - 1

        if start >= file_size or end >= file_size or start > end:
            return Response(
                "Range Not Satisfiable",
                status=416,
                headers={"Content-Range": f"bytes */{file_size}"},
            )

        content_length = end - start + 1
        with open(abs_path, "rb") as f:
            f.seek(start)
            data = f.read(content_length)

        response = Response(
            data,
            status=206,
            mimetype=mime_type,
            headers={
                **headers,
                "Content-Range": f"bytes {start}-{end}/{file_size}",
                "Content-Length": content_length,
            },
        )
        return response
    except (ValueError, IndexError):
        abort(400)


def create_app():
    """Application factory for creating the Flask app."""
    os.makedirs(SERVE_DIRECTORY, exist_ok=True)
    return app


def main():
    """Main entry point with CLI argument parsing."""
    parser = argparse.ArgumentParser(description="Static HTTP File Server")
    parser.add_argument(
        "--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)"
    )
    parser.add_argument(
        "--port", type=int, default=8080, help="Port to listen on (default: 8080)"
    )
    parser.add_argument(
        "--dir", default=SERVE_DIRECTORY, help="Directory to serve files from"
    )
    parser.add_argument(
        "--ssl-cert", default=None, help="Path to SSL certificate for HTTPS"
    )
    parser.add_argument(
        "--ssl-key", default=None, help="Path to SSL private key for HTTPS"
    )
    parser.add_argument(
        "--cors-origin", default=CORS_ORIGIN, help="CORS allowed origin (default: *)"
    )
    args = parser.parse_args()

    global SERVE_DIRECTORY, CORS_ORIGIN
    SERVE_DIRECTORY = os.path.abspath(args.dir)
    CORS_ORIGIN = args.cors_origin

    os.makedirs(SERVE_DIRECTORY, exist_ok=True)

    ssl_context = None
    if args.ssl_cert and args.ssl_key:
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ssl_context.load_cert_chain(args.ssl_cert, args.ssl_key)
        print(f"HTTPS enabled with cert: {args.ssl_cert}")

    protocol = "https" if ssl_context else "http"
    print(f"Serving files from: {SERVE_DIRECTORY}")
    print(f"Server running at {protocol}://{args.host}:{args.port}")

    app.run(
        host=args.host,
        port=args.port,
        ssl_context=ssl_context,
        debug=False,
    )


if __name__ == "__main__":
    main()