servery 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. servery-1.1.1/.github/dependabot.yml +17 -0
  2. servery-1.1.1/.github/workflows/ci.yml +131 -0
  3. servery-1.1.1/.github/workflows/release.yml +52 -0
  4. servery-1.1.1/.github/workflows/security.yml +54 -0
  5. servery-1.1.1/.gitignore +26 -0
  6. servery-1.1.1/.pre-commit-config.yaml +36 -0
  7. servery-1.1.1/CHANGELOG.md +178 -0
  8. servery-1.1.1/CONTRIBUTING.md +56 -0
  9. servery-1.1.1/LICENSE +21 -0
  10. servery-1.1.1/Makefile +33 -0
  11. servery-1.1.1/PKG-INFO +139 -0
  12. servery-1.1.1/README.md +110 -0
  13. servery-1.1.1/docs/ARCHITECTURE.md +677 -0
  14. servery-1.1.1/docs/BEST-PRACTICES.md +725 -0
  15. servery-1.1.1/docs/PRINCIPLES.md +305 -0
  16. servery-1.1.1/docs/REFERENCES.md +348 -0
  17. servery-1.1.1/docs/REQUIREMENTS.md +1011 -0
  18. servery-1.1.1/docs/ROADMAP.md +605 -0
  19. servery-1.1.1/docs/STANDARDS.md +460 -0
  20. servery-1.1.1/docs/TRANSPORTS.md +321 -0
  21. servery-1.1.1/docs/VISION.md +159 -0
  22. servery-1.1.1/pyproject.toml +149 -0
  23. servery-1.1.1/scripts/bench.py +126 -0
  24. servery-1.1.1/scripts/check_zero_deps.py +47 -0
  25. servery-1.1.1/scripts/microbench.py +81 -0
  26. servery-1.1.1/src/servery/__init__.py +26 -0
  27. servery-1.1.1/src/servery/__main__.py +5 -0
  28. servery-1.1.1/src/servery/_log.py +28 -0
  29. servery-1.1.1/src/servery/_oscrypto.py +161 -0
  30. servery-1.1.1/src/servery/_version.py +3 -0
  31. servery-1.1.1/src/servery/archive.py +53 -0
  32. servery-1.1.1/src/servery/auth.py +81 -0
  33. servery-1.1.1/src/servery/cli.py +207 -0
  34. servery-1.1.1/src/servery/config.py +114 -0
  35. servery-1.1.1/src/servery/handler.py +710 -0
  36. servery-1.1.1/src/servery/http2/__init__.py +1 -0
  37. servery-1.1.1/src/servery/http2/connection.py +297 -0
  38. servery-1.1.1/src/servery/http2/frames.py +640 -0
  39. servery-1.1.1/src/servery/http2/hpack.py +839 -0
  40. servery-1.1.1/src/servery/http3.py +136 -0
  41. servery-1.1.1/src/servery/listing.py +775 -0
  42. servery-1.1.1/src/servery/py.typed +0 -0
  43. servery-1.1.1/src/servery/ranges.py +89 -0
  44. servery-1.1.1/src/servery/security.py +53 -0
  45. servery-1.1.1/src/servery/server.py +120 -0
  46. servery-1.1.1/src/servery/upload.py +229 -0
  47. servery-1.1.1/tests/__init__.py +0 -0
  48. servery-1.1.1/tests/_harness.py +71 -0
  49. servery-1.1.1/tests/test_archive.py +64 -0
  50. servery-1.1.1/tests/test_auth.py +73 -0
  51. servery-1.1.1/tests/test_h2frames.py +455 -0
  52. servery-1.1.1/tests/test_handler_helpers.py +77 -0
  53. servery-1.1.1/tests/test_hpack.py +555 -0
  54. servery-1.1.1/tests/test_http1_conformance.py +250 -0
  55. servery-1.1.1/tests/test_http2_conformance.py +233 -0
  56. servery-1.1.1/tests/test_http2_server.py +243 -0
  57. servery-1.1.1/tests/test_http3.py +57 -0
  58. servery-1.1.1/tests/test_listing.py +230 -0
  59. servery-1.1.1/tests/test_oscrypto.py +60 -0
  60. servery-1.1.1/tests/test_performance.py +90 -0
  61. servery-1.1.1/tests/test_ranges.py +62 -0
  62. servery-1.1.1/tests/test_request_parsing.py +107 -0
  63. servery-1.1.1/tests/test_security.py +64 -0
  64. servery-1.1.1/tests/test_security_regression.py +125 -0
  65. servery-1.1.1/tests/test_server.py +654 -0
  66. servery-1.1.1/tests/test_smoke.py +123 -0
  67. servery-1.1.1/tests/test_upload.py +104 -0
  68. servery-1.1.1/tests/test_upload_robustness.py +153 -0
  69. servery-1.1.1/uv.lock +938 -0
@@ -0,0 +1,17 @@
1
+ version: 2
2
+ updates:
3
+ # Keep GitHub Actions current (and enable SHA-pin maintenance).
4
+ - package-ecosystem: github-actions
5
+ directory: "/"
6
+ schedule:
7
+ interval: weekly
8
+ commit-message:
9
+ prefix: ci
10
+
11
+ # Dev/CI tooling only (runtime deps are zero by mandate).
12
+ - package-ecosystem: uv
13
+ directory: "/"
14
+ schedule:
15
+ interval: weekly
16
+ commit-message:
17
+ prefix: deps
@@ -0,0 +1,131 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ # Least privilege by default; jobs opt into more if needed.
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: ci-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ lint:
19
+ name: Lint & format (ruff)
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
23
+ with:
24
+ persist-credentials: false
25
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
26
+ with:
27
+ enable-cache: true
28
+ - run: uv run ruff check --output-format=github .
29
+ - run: uv run ruff format --check .
30
+
31
+ type:
32
+ name: Type check (ty)
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
36
+ with:
37
+ persist-credentials: false
38
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
39
+ with:
40
+ enable-cache: true
41
+ # httpx is a dev-only interop dependency used in tests; give ty access to
42
+ # it (in its isolated env) so it resolves and type-checks that code.
43
+ - run: uvx --with "httpx[http2]" ty check src tests
44
+
45
+ security:
46
+ name: SAST (bandit)
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
50
+ with:
51
+ persist-credentials: false
52
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
53
+ with:
54
+ enable-cache: true
55
+ - run: uv run bandit -c pyproject.toml -r src
56
+
57
+ test:
58
+ name: Test (${{ matrix.os }}, py${{ matrix.python }})
59
+ needs: [lint, type, security]
60
+ strategy:
61
+ fail-fast: false
62
+ matrix:
63
+ os: [ubuntu-latest, macos-latest, windows-latest]
64
+ python: ["3.13", "3.14"]
65
+ runs-on: ${{ matrix.os }}
66
+ steps:
67
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
68
+ with:
69
+ persist-credentials: false
70
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
71
+ with:
72
+ enable-cache: true
73
+ python-version: ${{ matrix.python }}
74
+ - run: uv run --no-default-groups --group test --python ${{ matrix.python }} coverage run -m unittest discover -s tests -v
75
+ - run: uv run --no-default-groups --group test --python ${{ matrix.python }} coverage report
76
+
77
+ test-freethreaded:
78
+ name: Test free-threaded (py${{ matrix.python }})
79
+ needs: [lint, type, security]
80
+ strategy:
81
+ fail-fast: false
82
+ matrix:
83
+ python: ["3.13t", "3.14t"]
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
87
+ with:
88
+ persist-credentials: false
89
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
90
+ with:
91
+ enable-cache: true
92
+ python-version: ${{ matrix.python }}
93
+ # PYTHON_GIL=0 ensures the GIL stays disabled so concurrency is exercised.
94
+ # --no-default-groups --group test avoids the publish tooling (cffi has no free-threaded build).
95
+ - run: PYTHON_GIL=0 uv run --no-default-groups --group test --python ${{ matrix.python }} python -m unittest discover -s tests -v
96
+
97
+ test-experimental:
98
+ name: Test (py${{ matrix.python }}, allowed to fail)
99
+ needs: [lint, type, security]
100
+ runs-on: ubuntu-latest
101
+ continue-on-error: true
102
+ strategy:
103
+ fail-fast: false
104
+ matrix:
105
+ python: ["3.15", "3.15t"]
106
+ steps:
107
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
108
+ with:
109
+ persist-credentials: false
110
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
111
+ with:
112
+ enable-cache: true
113
+ python-version: ${{ matrix.python }}
114
+ - run: uv run --no-default-groups --group test --python ${{ matrix.python }} python -m unittest discover -s tests -v
115
+
116
+ build:
117
+ name: Build & zero-dependency gate
118
+ needs: [lint, type, security]
119
+ runs-on: ubuntu-latest
120
+ steps:
121
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
122
+ with:
123
+ persist-credentials: false
124
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
125
+ with:
126
+ enable-cache: true
127
+ - run: uv build
128
+ - name: Enforce zero runtime dependencies
129
+ run: uv run python scripts/check_zero_deps.py
130
+ - name: Validate distribution metadata
131
+ run: uvx twine check dist/*
@@ -0,0 +1,52 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes on a version tag (vX.Y.Z), or manually via the Actions tab. Uses
4
+ # PyPI Trusted Publishing (OIDC): no API token is ever stored — the publish job
5
+ # mints a short-lived token scoped to this repo + the `pypi` environment.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ build:
16
+ name: Build & verify distributions
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
20
+ with:
21
+ persist-credentials: false
22
+ # No build cache in the publish path: a poisoned cache could taint the
23
+ # released artifacts (zizmor cache-poisoning). Releases build clean.
24
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
25
+ with:
26
+ enable-cache: false
27
+ - run: uv build
28
+ - name: Enforce zero runtime dependencies
29
+ run: uv run python scripts/check_zero_deps.py
30
+ - name: Validate distribution metadata
31
+ run: uvx twine check dist/*
32
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+ if-no-files-found: error
37
+
38
+ publish:
39
+ name: Publish to PyPI (Trusted Publishing)
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ environment:
43
+ name: pypi
44
+ url: https://pypi.org/p/servery
45
+ permissions:
46
+ id-token: write # OIDC token for Trusted Publishing — the only privilege needed
47
+ steps:
48
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+ - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -0,0 +1,54 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 6 * * 1" # weekly, Monday 06:00 UTC
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ zizmor:
16
+ name: Audit workflows (zizmor)
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
20
+ with:
21
+ persist-credentials: false
22
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
23
+ # Audit our own GitHub Actions for injection / privilege / pinning issues.
24
+ # Fails on medium-or-higher findings. Actions are SHA-pinned (Dependabot
25
+ # maintains the pins), so unpinned-uses stays clean.
26
+ - run: uvx zizmor --min-severity=medium --format plain .github/workflows
27
+
28
+ bandit:
29
+ name: SAST (bandit)
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
33
+ with:
34
+ persist-credentials: false
35
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
36
+ - run: uv run bandit -c pyproject.toml -r src
37
+
38
+ secrets:
39
+ name: Secret scan (gitleaks)
40
+ runs-on: ubuntu-latest
41
+ # Dependabot PR runs get a read-only token that can't list PR commits via the
42
+ # API (gitleaks-action 403s). Skip them — push, schedule, and human PRs still scan.
43
+ if: github.actor != 'dependabot[bot]'
44
+ permissions:
45
+ contents: read
46
+ pull-requests: read
47
+ steps:
48
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
49
+ with:
50
+ fetch-depth: 0
51
+ persist-credentials: false
52
+ - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
53
+ env:
54
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,26 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+
7
+ # Build artifacts
8
+ build/
9
+ dist/
10
+ *.whl
11
+ *.tar.gz
12
+
13
+ # Test / tooling caches
14
+ .coverage
15
+ .coverage.*
16
+ htmlcov/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .pytest_cache/
20
+
21
+ # Environments
22
+ .venv/
23
+ venv/
24
+
25
+ # uv
26
+ # (uv.lock IS committed; the virtualenv is not)
@@ -0,0 +1,36 @@
1
+ # Local commit gates. Install once with: uv run pre-commit install
2
+ # Keep revs current with: uv run pre-commit autoupdate
3
+ # (mypy runs via CI / `make type`, not here — its isolated env can't resolve the
4
+ # package import graph cleanly.)
5
+ repos:
6
+ - repo: https://github.com/pre-commit/pre-commit-hooks
7
+ rev: v5.0.0
8
+ hooks:
9
+ - id: trailing-whitespace
10
+ - id: end-of-file-fixer
11
+ - id: check-yaml
12
+ - id: check-toml
13
+ - id: check-merge-conflict
14
+ - id: check-added-large-files
15
+ - id: mixed-line-ending
16
+ - id: detect-private-key
17
+
18
+ - repo: https://github.com/astral-sh/ruff-pre-commit
19
+ rev: v0.8.6
20
+ hooks:
21
+ - id: ruff
22
+ args: [--fix]
23
+ - id: ruff-format
24
+
25
+ - repo: https://github.com/PyCQA/bandit
26
+ rev: 1.8.0
27
+ hooks:
28
+ - id: bandit
29
+ args: ["-c", "pyproject.toml", "-r", "src"]
30
+ additional_dependencies: ["bandit[toml]"]
31
+
32
+ - repo: https://github.com/woodruffw/zizmor-pre-commit
33
+ rev: v1.0.0
34
+ hooks:
35
+ - id: zizmor
36
+ args: ["--min-severity=medium"]
@@ -0,0 +1,178 @@
1
+ # Changelog
2
+
3
+ All notable changes to servery are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [1.1.1] — 2026-06-22
8
+
9
+ First release published to PyPI.
10
+
11
+ ### Fixed
12
+
13
+ - **Directory listing on touch devices**: the per-file download button was
14
+ hover-only (invisible/untappable on phones); it is now shown via
15
+ `@media (hover: none)`, which also enlarges the facet chips, theme toggle, and
16
+ pager to finger-sized tap targets. Long filenames get `overflow-wrap` so they
17
+ can't force horizontal scroll on a narrow screen.
18
+
19
+ ### Changed
20
+
21
+ - **Publishing**: releases go to PyPI via GitHub Actions **Trusted Publishing**
22
+ (OIDC) — no API token is stored anywhere.
23
+ - **Packaging**: the version is single-sourced from `servery/_version.py`; added
24
+ the `Changelog` project URL and the `Programming Language :: Python :: Free
25
+ Threading` classifier.
26
+ - **CI/dev**: bumped `actions/checkout` (v7), `astral-sh/setup-uv`,
27
+ `gitleaks-action`, and the `bandit` floor (`>=1.9.4`).
28
+
29
+ ## [1.1.0] — 2026-06-22
30
+
31
+ ### Added
32
+
33
+ - **Directory-listing UI/UX pass** (still zero-dependency, server-side, **no
34
+ JavaScript**, and safe under the existing strict CSP):
35
+ - Clickable **breadcrumb** trail in the heading.
36
+ - Per-type **file icons** (extension-based, with a stdlib `mimetypes` fallback
37
+ for long-tail extensions — a pure lookup, no file content is read) and
38
+ **relative timestamps** ("3h ago", exact time on hover).
39
+ - Inline **size bars** and an aggregate **metrics strip** (file/dir counts,
40
+ total size, largest, newest).
41
+ - **`?ext=` file-type facet** chips alongside the existing `?q=` filter.
42
+ - Pure-**SVG modification timeline** histogram.
43
+ - **Per-file download** affordance (`?download=1` forces
44
+ `Content-Disposition: attachment`).
45
+ - **Pagination** for large directories (`?page=`, 1000 rows/page).
46
+ - Cookie-backed **light/dark/auto theme** toggle (`?theme=`).
47
+ - Friendly **empty / no-match** states, sticky table header, `aria-sort`, and
48
+ visible focus styles.
49
+
50
+ ### Performance
51
+
52
+ A second profiling-driven pass (cProfile / strace / timeit, benchmarked
53
+ before/after each change):
54
+
55
+ - **HTTP/2 HPACK**: Huffman coding is now opt-in on the encoder (raw literals by
56
+ default — for a file server the CPU it costs outweighs the few header bytes it
57
+ saves). Also fixed an O(n²) bit accumulator in `huffman_encode`. **+20%** h2
58
+ throughput.
59
+ - **HTTP/2 framing**: pack the 9-octet frame header in a single `struct` call
60
+ (was two packs + concatenations, ~4 allocations), byte-for-byte identical.
61
+ **+9%** h2; combined h2 throughput **+31%** (~11.6k → ~15k req/s, 1-core).
62
+ - **Path containment**: `security.is_contained` uses a separator-anchored prefix
63
+ test on POSIX (≈15× faster than `os.path.commonpath`, exact-match verified;
64
+ Windows keeps `commonpath`). Runs on every request. **+3%** small-file.
65
+ - **Listing render**: quote each entry name once (not twice) and cache file-type
66
+ extension lookups. **+5–6%** render.
67
+
68
+ ## [1.0.2] — 2026-06-22
69
+
70
+ A profiling-driven performance pass (cProfile + strace, benchmarked before/after
71
+ each change with a new single-core `scripts/microbench.py`).
72
+
73
+ ### Performance
74
+
75
+ - **Fast request-header parser**: the stdlib's email-based
76
+ `http.client.parse_headers` dominated per-request CPU (MIME/multipart work HTTP
77
+ never needs). Replaced with a line-based reader + a minimal case-insensitive
78
+ header map. Faithful `parse_request` (limits, versions, 0.9, expect-100, obs-fold
79
+ per RFC 9112 §5.2 preserved). Small-file serving **8,896 → 10,766 req/s (+21%)**
80
+ single-core; **~42k → ~52k req/s (+23%)** at 16-way concurrency.
81
+ - **Listing render**: `time.localtime` + manual date formatting instead of
82
+ `strftime`, and dropped a redundant `html.escape` on the already-percent-encoded
83
+ href. 50-entry listing **2,486 → 2,843 req/s (+14%)** single-core.
84
+ - **Fewer syscalls per file request**: send the body in one `sendfile` (was two);
85
+ skip the SPA `os.path.exists` stat when SPA is off (the default); drop a
86
+ `tell()` `lseek`. Per small-file GET: `sendfile` 2→1, `stat` 2.2→1.2,
87
+ `lseek` 3→2 (≈13→11 syscalls).
88
+ - Cached the constant `Server` header; guard access logging on the log level so a
89
+ disabled (quiet) logger does no per-request formatting.
90
+
91
+ Cumulative: small-file throughput **+24%** single-core. The large-file `sendfile`
92
+ path was already ~2.5 GB/s. No API or behavior changes; 295+ tests still pass.
93
+
94
+ ### Added (tests)
95
+
96
+ - `test_request_parsing.py`: the fast parser (case-insensitivity, first-wins,
97
+ obs-fold, no-colon, EOF-termination, bad version, HTTP/2.0-in-line, HTTP/0.9).
98
+ - Listing: an XSS guard proving a hostile filename cannot break out of the href
99
+ now that it is no longer html-escaped, plus an mtime-format check.
100
+ - `scripts/microbench.py` (single-core attribution) and a warmup in
101
+ `scripts/bench.py`.
102
+
103
+ ## [1.0.1] — 2026-06-22
104
+
105
+ Fixes surfaced by a large test-suite expansion (RFC reads + cross-checking
106
+ against httpx, curl, and h2spec).
107
+
108
+ ### Performance
109
+
110
+ - **TCP_NODELAY**: every small response previously incurred a ~40 ms
111
+ Nagle/delayed-ACK stall. Disabling Nagle takes small-file throughput from
112
+ ~390 to ~41,600 req/s (p50 41 ms → 0.28 ms) and listings from ~380 to
113
+ ~14,100 req/s on loopback. The sendfile large-file path was already ~2.5 GB/s.
114
+
115
+ ### Fixed
116
+
117
+ - Upload: RFC 5987/8187 `filename*` (non-ASCII filenames) was silently dropped;
118
+ now decoded (charset-validated) and preferred over plain `filename`.
119
+ - Upload: a plain non-ASCII `filename="naïve.txt"` was mojibake'd — part headers
120
+ are decoded as UTF-8 (RFC 7578 §5.1.1).
121
+ - Upload: a zero-part body (just the close-delimiter) is accepted as empty.
122
+
123
+ ### Added (tests)
124
+
125
+ - HTTP/1.1 conformance (methods, 9 path-traversal vectors, conditional
126
+ precedence, MIME, HEAD==GET, multi-range, empty file) + httpx interop.
127
+ - Upload robustness (filename*, multiple files, boundary-in-content, chunked
128
+ rejection) + httpx multipart interop.
129
+ - HTTP/2 conformance (h2spec generic 50/52 + hpack 8/8 validated; padded
130
+ HEADERS, HPACK continuity, concurrent streams, malformed-request RST) + httpx
131
+ h2-over-TLS interop.
132
+ - Security regression + abuse cases (URI/header limits, Slowloris timeout, CRLF
133
+ injection e2e, upload containment, auth bypass).
134
+ - Performance/load smoke (Nagle regression guard, concurrent-correctness under
135
+ free-threading, large-file integrity) + a runnable `scripts/bench.py`.
136
+
137
+ 281 tests total.
138
+
139
+ ## [1.0.0] — 2026-06-21
140
+
141
+ First stable release. A zero-dependency, pure-Python HTTP file server.
142
+
143
+ ### Added
144
+
145
+ - **HTTP/1.1 file serving** (pure stdlib): rich, sortable (`?C=&O=`), searchable
146
+ (`?q=`) directory listings with sizes and modified times; index documents.
147
+ - **RFC 9110 downloads**: `Range`/`206`/`416`, strong `ETag`s, the conditional
148
+ ladder (`If-None-Match`/`If-Modified-Since`/`If-Range` → `304`/`412`), and
149
+ zero-copy `socket.sendfile()` with a userspace fallback.
150
+ - **TLS/HTTPS** via `--tls-cert`/`--tls-key`, ALPN, HSTS over TLS, `--tls-help`.
151
+ - **HTTP Basic Auth** (`--auth`): single credential or pre-hashed
152
+ `user:sha256:…`/`sha512`, constant-time comparison, no-TLS warning.
153
+ - **Upload** (`--upload`): streaming `multipart/form-data` parser (no `cgi`),
154
+ atomic `os.replace`, bounded size (`--max-upload-size`), `--allow-overwrite`.
155
+ - **Archive download**: stream any directory as `tar.gz`/`zip` (`?archive=`),
156
+ chunked, `Content-Disposition` with `filename*`, symlink-safe.
157
+ - **CORS** (`--cors` + preflight), **SPA fallback** (`--spa`), **cache control**
158
+ (`--cache`), and secure headers (`nosniff` everywhere, scoped CSP +
159
+ Referrer-Policy on generated pages; `--no-security-headers`).
160
+ - **Hardening**: `logging`-module access logs, default socket timeout
161
+ (`--timeout`), optional bounded concurrency (`--max-workers`).
162
+ - **HTTP/2** (`--http2`): a pure-stdlib HTTP/2 server — HPACK (RFC 7541) and the
163
+ frame codec (RFC 9113) implemented from the RFCs, ALPN `h2` + h2c, per-stream
164
+ flow control, and DoS limits (concurrent-stream cap, header-block cap, RST
165
+ budget). Verified against `curl --http2`.
166
+ - **HTTP/3** via the optional `servery[http3]` (aioquic) extra; the core stays
167
+ zero-dependency. `servery._oscrypto` provides AES-256-GCM via `ctypes` → OS
168
+ OpenSSL (NIST-vector verified) as the zero-PyPI-dependency crypto foundation.
169
+ - **Safe defaults**: localhost bind, path-traversal + symlink-escape containment
170
+ (`realpath` + `commonpath`), exposure/cleartext-auth warnings.
171
+ - **Free-threading** support (3.13t/3.14t), full type hints (`ty`-checked), and a
172
+ CI gate that enforces zero runtime dependencies in the core wheel.
173
+
174
+ [1.1.1]: https://github.com/mjbommar/servery/releases/tag/v1.1.1
175
+ [1.1.0]: https://github.com/mjbommar/servery/releases/tag/v1.1.0
176
+ [1.0.2]: https://github.com/mjbommar/servery/releases/tag/v1.0.2
177
+ [1.0.1]: https://github.com/mjbommar/servery/releases/tag/v1.0.1
178
+ [1.0.0]: https://github.com/mjbommar/servery/releases/tag/v1.0.0
@@ -0,0 +1,56 @@
1
+ # Contributing to servery
2
+
3
+ ## The one rule that overrides everything
4
+
5
+ **Zero third-party *runtime* dependencies.** The shipped wheel must declare no
6
+ `Requires-Dist`. This is enforced in CI by `scripts/check_zero_deps.py` — if a
7
+ runtime dependency ever appears, the build fails. Dev/CI tooling (ruff, mypy,
8
+ bandit, coverage, build, twine, pre-commit) is fine: it lives in the `dev`
9
+ [dependency group](pyproject.toml) and never ships in the wheel.
10
+
11
+ Tests use the standard-library `unittest` only — no pytest, no hypothesis.
12
+
13
+ ## Setup
14
+
15
+ ```bash
16
+ uv sync # create the env with dev tooling
17
+ uv run pre-commit install # local commit gates
18
+ ```
19
+
20
+ ## The gates
21
+
22
+ Run everything locally before pushing:
23
+
24
+ ```bash
25
+ make check # lint + type + security + test
26
+ make build # build wheel/sdist + zero-dependency gate
27
+ ```
28
+
29
+ Or individually:
30
+
31
+ | Gate | Command | What it enforces |
32
+ |------|---------|------------------|
33
+ | Lint & format | `make lint` / `make format` | `ruff check` + `ruff format` |
34
+ | Types | `make type` | `ty check` over `src` and `tests` |
35
+ | Security (SAST) | `make security` | `bandit` over `src` |
36
+ | Tests + coverage | `make test` | `unittest` discovery, ≥90% coverage |
37
+ | Packaging | `make build` | builds, asserts zero runtime deps, `twine check` |
38
+
39
+ CI (`.github/workflows/`) runs the same gates plus a test matrix
40
+ (Linux/macOS/Windows × Python 3.13/3.14, free-threaded 3.13t/3.14t, and
41
+ 3.15/3.15t allowed to fail), a GitHub Actions audit (`zizmor`), and a secret
42
+ scan (`gitleaks`). servery targets free-threaded builds as a first-class
43
+ configuration — no module-level mutable state, no reliance on the GIL.
44
+
45
+ ## Standards
46
+
47
+ servery aims to be a conformant HTTP/1.1 origin server (RFC 9110/9111/9112);
48
+ see [`docs/STANDARDS.md`](docs/STANDARDS.md). New behavior should cite the RFC
49
+ section it implements and ship with a test. Security-relevant changes should
50
+ keep the safe-by-default posture described in
51
+ [`docs/PRINCIPLES.md`](docs/PRINCIPLES.md).
52
+
53
+ ## Commits
54
+
55
+ Small, focused commits. Reference the milestone from
56
+ [`docs/ROADMAP.md`](docs/ROADMAP.md) where relevant.
servery-1.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Bommarito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
servery-1.1.1/Makefile ADDED
@@ -0,0 +1,33 @@
1
+ .PHONY: install lint format type security test build check all clean
2
+
3
+ install:
4
+ uv sync
5
+
6
+ lint:
7
+ uv run ruff check .
8
+ uv run ruff format --check .
9
+
10
+ format:
11
+ uv run ruff format .
12
+ uv run ruff check --fix .
13
+
14
+ type:
15
+ uvx --with "httpx[http2]" ty check src tests
16
+
17
+ security:
18
+ uv run bandit -c pyproject.toml -r src
19
+
20
+ test:
21
+ uv run coverage run -m unittest discover -s tests -v
22
+ uv run coverage report
23
+
24
+ build:
25
+ uv build
26
+ uv run python scripts/check_zero_deps.py
27
+
28
+ check: lint type security test
29
+
30
+ all: check build
31
+
32
+ clean:
33
+ rm -rf dist build .coverage .mypy_cache .ruff_cache *.egg-info src/*.egg-info