zddc-server can now browse into a .zip file without the client
downloading the whole archive:
- GET …/Foo.zip/ → JSON listing of the zip's members
(Accept: application/json), or the
browse SPA (HTML) — same content
negotiation as ServeDirectory/.archive
- GET …/Foo.zip/sub/doc.pdf → extracts and streams that one member
(Range / ETag / conditional GET via
http.ServeContent)
- GET …/Foo.zip → unchanged: the raw .zip download
- PUT/DELETE/POST …/Foo.zip/… → 405 (zip access is read-only)
New internal/zipfs package reconstructs directory levels from the zip's
flat central directory (synthesising intermediate dirs with no explicit
"<dir>/" entry, mirroring what browse does client-side with JSZip) and
drops zip-slip-unsafe entries ("..", absolute, backslash). New
handler.ServeZip wraps it. The dispatcher gets splitZipPath + an
intercept placed before the file-API branch (so a write to a path under
a .zip is refused, not silently mkdir'd); ACL is the chain of the
directory CONTAINING the zip — a zip carries no .zddc of its own, same
as the .archive virtual surface. The os.Stat-per-segment walk is gated
by a cheap ".zip/" substring check so ordinary requests are unaffected.
Also fixes two pre-existing dispatch-test failures uncovered along the
way: a non-existent top-level "*.html" URL was 302'ing to its slash
form (because the bare "*" project glob makes every first-level segment
"declared") — the cascade-declared no-slash block now requires a
directory-shaped URL (trailing slash, or no file extension); and the
stale TestDispatchSlashRouting expectation that archive/<party>/mdl/
302s to mdl/table.html was updated to match the intended behaviour
(the default-MDL virtual fallback shows the browse listing there; only
a real on-disk tables: + *.table.yaml triggers the bounce).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>