-
Notifications
You must be signed in to change notification settings - Fork 60
fix: S3 exists() should not match directories for file paths #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughChanges to the S3 storage device's Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Previously, exists() would incorrectly add a trailing slash to all paths,
causing file paths to match directories with the same prefix. This led to
exists('/path/to/file.html') returning true when only the directory
'/path/to/file.html/' existed, resulting in NoSuchKey errors when trying
to read the file.
Now, trailing slashes are only added if the original path ends with '/',
ensuring:
- exists('file.html') only returns true if the file exists
- exists('directory/') correctly checks for directory existence
Added test testFileExistsDoesNotMatchDirectoryPrefix() to verify the fix.
1b0fea1 to
d334eb4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a bug in the S3 storage adapter's exists() method where file path existence checks were incorrectly being converted to directory prefix checks, leading to false positives.
Key Changes:
- Modified the logic to only append a trailing slash when the original path explicitly ends with
/(indicating a directory check) - Added comprehensive test coverage to verify files don't match directory prefixes
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Storage/Device/S3.php |
Fixed the exists() method to preserve file vs directory semantics by only adding trailing slashes for paths that explicitly end with / |
tests/Storage/S3Base.php |
Added test case testFileExistsDoesNotMatchDirectoryPrefix() to verify the fix prevents false positives when checking file existence in directories with similar prefixes |
The implementation correctly addresses the reported issue. The fix ensures that:
- File existence checks like
exists('builds/index.html')use prefixbuilds/index.html(no trailing slash) - Directory existence checks like
exists('builds/')use prefixbuilds/(with trailing slash) - Directory checks without trailing slash like
exists('nested')still work because S3'slistObjectswith prefixnestedmatches both exact keys and nested paths likenested/file.txt
The new test appropriately validates the fix by creating two files in a directory, deleting one, and confirming that the deleted file correctly reports as non-existent even though the parent directory still contains other files. The test follows established patterns in the codebase and properly cleans up after itself.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/Storage/Device/S3.php (1)
639-647: Trailing‑slash fix is good, but non‑directory prefix behavior changed; please double‑check semanticsThe new condition correctly standardizes a single trailing
/only when the original$pathends with/, which matches the intent of treating “directory‑like” paths differently from file paths and avoids the previous unconditional folder behavior. That’s a solid improvement.One subtle side effect, though: for paths that do not end with
/, the fallback now callslistObjects()with$prefixwithout a trailing slash. Compared to the old logic (which always forced$prefix .= '/'), this means:
- Previously, for a failing
HEADon$path = 'foo', the fallback prefix was effectivelyfoo/, so only keys underfoo/…could makeexists('foo')returntrue.- Now, the fallback prefix is
foo, so any key starting withfoo(e.g.foo-bar,foo_tmp, etc.) will causeexists('foo')to returntrue, even if there is nofooobject and nofoo/…“directory”.That’s a real behavior change for ambiguous names where the requested path is a strict prefix of other keys. It may be acceptable, but if you want to avoid new false positives while still keeping the directory fix, you might consider tightening the fallback for non‑directory paths, e.g., by only treating the fallback as
truewhen at least one returnedKeyexactly matches the target key you used forHEADrather than just sharing the prefix.tests/Storage/S3Base.php (1)
168-185: Great regression test; consider aligning the comment and optionally covering the ‘file.html/…’ case explicitlyThe new
testFileExistsDoesNotMatchDirectoryPrefix()is a good addition and clearly validates the core regression you’re fixing: after deletingbuilds/index.html,exists('builds/index.html')staysfalseeven thoughbuilds/still contains other files.Two small improvements you might want to consider:
- The docblock mentions “directory
'file.html/'exists”, but the test currently only uses a sibling file (builds/other.css). Either adjust the comment to describe the actual scenario (file absent, parent directory still populated) or- Extend the test to also create something like
builds/index.html/inner.txtand assert thatexists('builds/index.html')is stillfalsewhen only that “directory under file name” structure exists. That would more directly lock in the original bug description.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/Storage/Device/S3.php(1 hunks)tests/Storage/S3Base.php(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: E2E Test (WasabiTest)
- GitHub Check: E2E Test (LinodeTest)
- GitHub Check: E2E Test (BackblazeTest)
- GitHub Check: E2E Test (LocalTest)
- GitHub Check: E2E Test (S3Test)
- GitHub Check: E2E Test (DOSpacesTest)
Summary
Fixes a bug in S3
exists()method where file paths would incorrectly match directories with the same prefix.Problem
Previously,
exists()would add a trailing slash to all paths when falling back tolistObjects(). This caused:exists('/storage/builds/app-123/extracted/abc123/index.html')→ checks for prefix/storage/builds/app-123/extracted/abc123/index.html/trueif any directory starts with that prefix, even if the file doesn't existNoSuchKeyerrors when trying to read the fileSolution
Only add trailing slash if the original path ends with
/:Changes
src/Storage/Device/S3.php:639-641to only add trailing slash when original path indicates a directorytestFileExistsDoesNotMatchDirectoryPrefix()to verify the fixTesting
The new test verifies that:
exists('file.html')returnsfalsewhen the file doesn't existexists('directory/')still works correctly for directory checksSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.