Skip to content

Commit d334eb4

Browse files
committed
fix: S3 exists() should not match directories for file paths
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.
1 parent 10fe0c0 commit d334eb4

File tree

2 files changed

+21
-2
lines changed

2 files changed

+21
-2
lines changed

src/Storage/Device/S3.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,8 @@ public function exists(string $path): bool
636636
$prefix = $root.'/'.ltrim($path, '/');
637637
}
638638

639-
if (! empty($path) && ! str_ends_with($prefix, '/')) {
640-
$prefix .= '/';
639+
if (! empty($path) && str_ends_with($path, '/')) {
640+
$prefix = rtrim($prefix, '/').'/';
641641
}
642642

643643
$objects = $this->listObjects($prefix, 1);

tests/Storage/S3Base.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,25 @@ public function testDirectoryExists()
165165
$this->assertEquals(false, $this->object->exists($this->object->getPath('nested/deep/structure')));
166166
}
167167

168+
/**
169+
* Test that file paths without trailing slash don't incorrectly match directories
170+
* Verifies fix: exists('file.html') should not return true when directory 'file.html/' exists
171+
*/
172+
public function testFileExistsDoesNotMatchDirectoryPrefix()
173+
{
174+
$this->object->write($this->object->getPath('builds/index.html'), 'content', 'text/html');
175+
$this->object->write($this->object->getPath('builds/other.css'), 'body {}', 'text/css');
176+
177+
$this->assertEquals(true, $this->object->exists($this->object->getPath('builds/index.html')));
178+
179+
$this->object->delete($this->object->getPath('builds/index.html'));
180+
181+
// File should not exist even though directory 'builds/' still contains other.css
182+
$this->assertEquals(false, $this->object->exists($this->object->getPath('builds/index.html')));
183+
184+
$this->object->delete($this->object->getPath('builds/other.css'));
185+
}
186+
168187
public function testMove()
169188
{
170189
$this->assertEquals(true, $this->object->write($this->object->getPath('text-for-move.txt'), 'Hello World', 'text/plain'));

0 commit comments

Comments
 (0)