Only strip a tar's root directory when it has one (audit L-2) #144
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
qwc-open/asiakirjat!144
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/tar-single-root"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Audit item L-2, filed as a Low "mirror the zip logic" cleanup. It's a live data-loss bug, so it's worth reading as more than a tidy-up.
What was wrong
stripSingleRootTarremoved the first path component from every tar entry, unconditionally:Zip and 7z don't do that —
detectSingleRootchecks that every entry actually shares one root before stripping anything. Two consequences for.tar.gz/.tar.bz2/.tar.xzuploads:guide/intro.htmlwas written asintro.html,assets/style.cssasstyle.css, whileindex.htmlwas untouched. Every link into those directories 404s.api/index.htmlandguide/index.htmlboth becameindex.html, so one silently overwrote the other. I wrote the test expecting the first symptom and it also caught this one — half the upload just disappears.The fix
Tar is a stream, so it can't be inspected before extraction the way a zip's directory can, and reading it twice would mean buffering the whole archive. So the equivalent check moved to after extraction: write the real paths, then unwrap if exactly one top-level directory came out. Costs a handful of renames on a directory that's already on disk.
The root is staged into a temp directory before its children are moved up — without that, an archive shaped like
docs/docs/index.htmlcan't be unwrapped, since the child would be renamed onto its own parent. There's a test for that case.Tests
internal/docs/tar_single_root_test.gobuilds real tarballs and checks the extracted tree: top-level layout preserved, a genuine single root still stripped (the behaviour worth keeping), two roots both surviving, and the root-sharing-its-child-name case.The first and third fail on
main:Existing archive tests, including
TestExtractTarGz(which covers the single-root case), pass unchanged. Full suite green.Worth knowing
Any docs uploaded as a tarball that wasn't wrapped in a single directory are already extracted wrong on disk — this fixes new uploads, not old ones. Re-uploading those versions after deploying will lay them out correctly. Zip uploads were never affected.
Assisted by Claude Opus 5.
Tar extraction stripped the first path component off every entry, unconditionally, while zip and 7z first check whether the archive really has a single common root. Two ways that went wrong: - An archive whose files are already at the top level lost a real directory: guide/intro.html was written as intro.html, breaking every link to it. - An archive with two top-level directories had them collapse into one, so api/index.html and guide/index.html overwrote each other. Half the upload silently disappeared. A tar is a stream and reading it twice would mean buffering the whole archive, so the single-root check happens after extraction instead of before it: extract the real paths, then unwrap if exactly one top-level directory came out. The root is moved aside first so an archive like docs/docs/ can still be unwrapped. Assisted-by: Claude Opus 5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marcel M. Otte <marcel.otte@mmo.to>