diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index fd8b0f9d5ea..1ac0766f30b 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -417,7 +417,7 @@ public function url() return null; } - return URL::assemble($this->container()->url(), $this->path()); + return URL::assemble($this->container()->url(), $this->encodedPath()); } public function absoluteUrl() @@ -426,7 +426,12 @@ public function absoluteUrl() return null; } - return URL::assemble($this->container()->absoluteUrl(), $this->path()); + return URL::assemble($this->container()->absoluteUrl(), $this->encodedPath()); + } + + private function encodedPath() + { + return implode('/', array_map('rawurlencode', explode('/', $this->path()))); } public function thumbnailUrl($preset = null) diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index 786b107f087..609ee2ed997 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -59,7 +59,7 @@ public function findByUrl(string $url) $url = $siteUrl.$url; } - $path = Str::after($url, $containerUrl); + $path = rawurldecode(Str::after($url, $containerUrl)); return $container->asset($path); } diff --git a/tests/Assets/AssetRepositoryTest.php b/tests/Assets/AssetRepositoryTest.php index be2ea47f8a3..342f5d096af 100644 --- a/tests/Assets/AssetRepositoryTest.php +++ b/tests/Assets/AssetRepositoryTest.php @@ -5,6 +5,7 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Storage; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Statamic\Assets\AssetRepository; use Statamic\Contracts\Assets\Asset as AssetContract; @@ -106,6 +107,32 @@ public function it_finds_assets_using_find_or_fail() $this->assertEquals($assetShortUrl->id(), $asset->id()); } + #[Test] + #[DataProvider('encodedUrlProvider')] + public function it_finds_assets_by_an_encoded_url($path, $expectedUrl) + { + Storage::fake('test', ['url' => 'test']); + Storage::disk('test')->put($path, UploadedFile::fake()->image('bar.jpg')->getContent()); + + $container = tap(AssetContainer::make('test_container')->disk('test'))->save(); + $asset = tap($container->makeAsset($path))->save(); + + $this->assertEquals($expectedUrl, $asset->url()); + + $found = Asset::findByUrl($asset->url()); + + $this->assertInstanceOf(AssetContract::class, $found); + $this->assertEquals($asset->id(), $found->id()); + } + + public static function encodedUrlProvider() + { + return [ + 'spaces and accents' => ['foo/Dún Laoghaire_18 2.jpg', '/test/foo/D%C3%BAn%20Laoghaire_18%202.jpg'], + 'literal percent sequences' => ['foo/photo%20one.jpg', '/test/foo/photo%2520one.jpg'], + ]; + } + #[Test] public function it_finds_assets_by_id_when_the_path_contains_windows_separators() { diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 516df9febdf..b94ef66cc85 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -2375,6 +2375,32 @@ public function it_gets_the_absolute_url() $this->assertEquals('http://example.com/path/to/test.txt', $asset->absoluteUrl()); } + #[Test] + #[DataProvider('urlEncodingProvider')] + public function it_encodes_the_url($path, $expected) + { + $container = $this->mock(AssetContainer::class); + $container->shouldReceive('private')->andReturnFalse(); + $container->shouldReceive('url')->andReturn('http://example.com/container'); + $container->shouldReceive('absoluteUrl')->andReturn('http://example.com/container'); + $asset = (new Asset)->container($container)->path($path); + + $this->assertEquals('http://example.com/container'.$expected, $asset->url()); + $this->assertEquals('http://example.com/container'.$expected, $asset->absoluteUrl()); + $this->assertEquals('http://example.com/container'.$expected, (string) $asset); + } + + public static function urlEncodingProvider() + { + return [ + 'nothing to encode' => ['path/to/test.txt', '/path/to/test.txt'], + 'spaces' => ['path/to/Image X - Whatever_17.jpg', '/path/to/Image%20X%20-%20Whatever_17.jpg'], + 'accents' => ['path/to/Dún Laoghaire_18 2.jpg', '/path/to/D%C3%BAn%20Laoghaire_18%202.jpg'], + 'spaces in folders' => ['path to/my folder/test.txt', '/path%20to/my%20folder/test.txt'], + 'literal percent sequences' => ['path/to/photo%20one.jpg', '/path/to/photo%2520one.jpg'], + ]; + } + #[Test] public function there_is_no_url_for_a_private_asset() {