tar: make members with duplicate slashes reachable via ls/find/glob (#1947)#2063
Open
Sanjays2402 wants to merge 1 commit into
Open
tar: make members with duplicate slashes reachable via ls/find/glob (#1947)#2063Sanjays2402 wants to merge 1 commit into
Sanjays2402 wants to merge 1 commit into
Conversation
A tar member whose name contains redundant consecutive slashes
(e.g. "path/with/extra/slash//test.txt") was silently invisible to
`ls`, `find`, `glob` and `walk`, so `open_files("tar://**/*::archive.tar")`
returned nothing for such archives even though the file could be opened by
its exact name.
Root cause: the raw member name (including the "//") was used both as the
index key and to derive the directory structure. `_parent()` of
"a/b//c.txt" is "a/b/" (with a trailing slash), so the inferred parent
directory key never matched a listing of "a/b", and the member's own
computed root likewise carried the trailing slash. The entry therefore fell
out of every directory-traversal comparison in the archive base class.
Fix: normalise the filesystem-facing name by collapsing runs of slashes
(new `_collapse_slashes` helper) when building both `self.index` and the
`dir_cache`, while retaining the original member name so extraction still
works. `_open` normalises its lookup path too, so the file can be opened by
either its normalised name (as now returned by find/glob) or its original
name (backwards compatible).
Adds test_ls_with_duplicate_slashes (parametrized over all four tar
compressions), which fails without the fix and passes with it.
Fixes fsspec#1947
martindurant
reviewed
Jul 3, 2026
Comment on lines
+15
to
+25
| def _collapse_slashes(name): | ||
| """Collapse runs of consecutive slashes in a tar member name. | ||
|
|
||
| Some tar archives contain members with redundant duplicate slashes | ||
| (e.g. ``"a/b//c.txt"``). Left as-is these break the derived directory | ||
| structure: ``_parent("a/b//c.txt")`` yields ``"a/b/"`` (trailing slash), | ||
| so the entry becomes unreachable through ``ls``/``find``/``glob``/``walk`` | ||
| even though it can be opened by its exact name. Normalising the | ||
| filesystem-facing name keeps ``"a/b//c.txt"`` reachable as ``"a/b/c.txt"``. | ||
| """ | ||
| return re.sub("/+", "/", name) |
Member
There was a problem hiding this comment.
Just inline this with a one-line comment
| dirname: {"name": dirname, "size": 0, "type": "directory"} | ||
| for dirname in self._all_dirnames(self.tar.getnames()) | ||
| for dirname in self._all_dirnames( | ||
| [_collapse_slashes(name) for name in self.tar.getnames()] |
Member
There was a problem hiding this comment.
tar.getnames and the normalisation happens twice here, we should probably only do it once.
Comment on lines
+114
to
+117
| orig_name = ti.get_info()["name"].rstrip("/") | ||
| name = _collapse_slashes(orig_name) | ||
| info["name"] = name | ||
| out[name] = (info, ti.offset_data, orig_name) |
Member
There was a problem hiding this comment.
I think this is also iterating the same information as in _get_dirs(), please see if it could be simplified.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1947.
A tar member whose name contains redundant consecutive slashes (e.g.
path/with/extra/slash//test.txt) was silently invisible tols,find,globandwalk. As a resultfsspec.open_files("tar://**/*::archive.tar")returned zero files for such archives, even though the file could be opened by its exact name.Reproducer (from the issue)
Root cause
The raw member name (including the
//) was used both as theself.indexkey and to derive the directory structure in_get_dirs.AbstractFileSystem._parent("a/b//c.txt")returns"a/b/"— with a trailing slash — becausersplit("/", 1)splits on the second slash of the//. So:path/with/extra/slash/(trailing slash), which never matched a listing ofpath/with/extra/slash, andThe entry therefore fell out of every directory-traversal comparison in
AbstractArchiveFileSystemand was unreachable throughls/find/glob/walk— while still openable by its exact name (which is why direct access appeared to work).Fix
Normalise the filesystem-facing name by collapsing runs of slashes (new
_collapse_slasheshelper) when building bothself.indexanddir_cache, while retaining the original member name sotar.extractfile()still receives the exact archive key._opennormalises its lookup path too, so a member is openable by either:path/with/extra/slash/test.txt, as now returned byfind/glob), orpath/with/extra/slash//test.txt) — backwards compatible.This matches the approach the maintainer agreed with in the issue thread ("normalizing the look-up name, while keeping the original tar key for the actual extraction").
Before / after
fs.find("/")[]['path/with/extra/slash/test.txt']fs.glob("**/*")['path', 'path/with', 'path/with/extra'][..., 'path/with/extra/slash', 'path/with/extra/slash/test.txt']fs.ls("path/with/extra/slash")[]['path/with/extra/slash/test.txt']open_files("tar://**/*::…")fs.open(".../slash//test.txt")Normal single-slash archives are unaffected.
Tests
Adds
test_ls_with_duplicate_slashes, parametrized over all four tar compressions (tar,tar.gz,tar.bz2,tar.xz), asserting discoverability viafind/glob/ls, directory inference viaisdir, and openability by both name forms.Proven to guard the bug (source stashed, test kept):
Full local verification (py3.13):
test_tar.py,test_archive.py,test_zip.py+ memory/local/api: 328 passed, 32 skipped, 1 xfailed, no regressions.ruff checkandruff format --checkclean on both changed files.Diff is +76/−6 across
fsspec/implementations/tar.pyand its test module.