mini-static 0.7.1

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
# mini-static — Development Plan

Coverage target: **≥ 80%** line coverage. Like `mini-serve`, the connection-lifecycle
and traversal-security tests need real filesystem and socket fixtures (`tempfile`), not
pure-function unit tests alone.

## Phase 0.1.0 — MVP: resolve, serve, traverse-safe

*Exit criteria: serve a file under a root; a traversal attempt and a missing file are
indistinguishable over the wire.*

1. **Commit: `resolve()` — segment-based traversal check + canonicalize-and-`starts_with`
   boundary.** Primary test: request `jquery..min.js` (a legitimate filename containing
   `..` as a substring but not as a segment), assert it resolves successfully — the
   regression test for the over-eager substring check. Second test: request `../../etc/passwd`-shaped
   input, assert it's rejected.
2. **Commit: traversal and missing-file both answer 404, `nosniff` header on all
   responses.** Primary test: request a traversal path and a genuinely missing file,
   assert both return status `404` with byte-identical bodies — proving there's no
   distinguishing oracle. Assert `X-Content-Type-Options: nosniff` is present on both.
3. **Commit: `Server::run` canonicalizes root once; `resolve` takes it as a precondition.**
   Primary test: instrument syscall count (or wrap the filesystem in a counting fake) for
   N requests, assert `canonicalize` is called once total, not once per request.

## Phase 0.2.0 — HTTP correctness

*Exit criteria: method handling, Content-Length, directory redirects, range edge cases
all match RFC 9110 behavior.*

4. **Commit: method gate — GET/HEAD proceed, everything else 405 + `Allow`.** Primary
   test: `DELETE` a real file, assert `405` with `Allow: GET, HEAD` and confirm (via a
   counting fake filesystem) zero disk reads occurred.
5. **Commit: `Content-Length` on full 200 responses.** Primary test: request a known-size
   file without a `Range` header, assert the response header's value equals the file's
   actual byte length exactly.
6. **Commit: directory-index 301 redirect.** Primary test: request `/dir` where
   `/dir/index.html` exists, assert a `301` with `Location: /dir/` — not the index
   content directly.
7. **Commit: multi-range → full 200; `If-Range` honored.** Primary test: send a
   multi-range `Range` header, assert `200` with the full body (not `416`); send a
   single range with a stale `If-Range` validator, assert the full current file returns.

## Phase 0.3.0 — Connection lifecycle

8. **Commit: header-read timeout.** Primary test: open a raw socket, send nothing,
   assert the connection closes within the configured timeout and a subsequent normal
   request on a new connection still succeeds.
9. **Commit: loopback-only ephemeral binds.** Primary test: assert `run_ephemeral()`'s
   bound address is exactly `127.0.0.1`.

## Phase 0.4.0 — Performance + non-ASCII filenames

10. **Commit: `BytesMut`-based streaming, `split_to(n).freeze()`.** Primary test:
    benchmark or instrument to confirm one copy per chunk instead of two, on a
    multi-megabyte fixture file.
11. **Commit: byte-level percent-decoding for filenames.** Primary test: serve a file
    named with a non-ASCII character (e.g. `é.png`), assert it resolves and serves
    correctly, with the traversal guard still active on the same request path.

## Phase 0.5.0 — Publish

12. **Commit: `err`/`log` feature docs + doc comments on every public item.**
13. **Commit: CI**`cargo test`, `cargo test --all-features`, `cargo clippy -- -D warnings`,
    `cargo llvm-cov --fail-under-lines 80`.
14. **Publish `0.5.0`** to crates.io (name confirmed available).

Cache-control policy hook and precompressed-sidecar support (both noted as optional in
the README) become their own post-0.5.0 minor-version phases if/when a consumer actually
needs them — not built speculatively ahead of that need.

## Phase 0.5.1 — Conditional requests (patch release)

15. **Commit: honor `If-None-Match` / `If-Modified-Since`, answer `304 Not Modified`
    with no body when the validator matches.** Primary test: request a file with a
    matching `If-None-Match` (the ETag from a prior response), assert `304` with an
    empty body; request the same file with a stale validator, assert the full `200`
    body returns. Tests verify ETag-based (comma-separated list, wildcard support) and
    mtime-based (as Unix seconds) conditional matching.
16. **Publish `0.5.1`** to crates.io.

## Phase 0.6.0 — Cache control + precompression (post-0.5.1)

- Cache-control policy hook: default `no-cache`; builder method for fingerprinted assets.
- Precompressed sidecar support: serve `foo.js.gz`/`.br` when `Accept-Encoding` matches.