Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/Http/Controllers/GlideController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Statamic\Facades\Config;
use Statamic\Facades\Glide;
use Statamic\Facades\Site;
use Statamic\Facades\URL;
use Statamic\Imaging\ImageGenerator;
use Statamic\Support\Str;

Expand Down Expand Up @@ -218,12 +219,31 @@ private function validateSignature()
return;
}

$path = Str::after($this->request->url(), Site::current()->absoluteUrl());
$url = $this->request->url();
$siteUrl = Site::current()->absoluteUrl();

$path = Str::startsWith(Str::ensureRight($url, '/'), Str::ensureRight($siteUrl, '/'))
? Str::after($url, $siteUrl)
: $this->removeSitePrefix($this->request->getPathInfo());

try {
SignatureFactory::create(Config::getAppKey())->validateRequest($path, $this->request->query->all());
} catch (SignatureException $e) {
abort(400, $e->getMessage());
}
}

/**
* Remove the longest site path prefix (e.g. /fr) from the path.
*/
private function removeSitePrefix(string $path): string
{
$prefix = Site::all()
->map(fn ($site) => Str::removeRight(URL::makeRelative($site->url()), '/'))
->filter(fn ($prefix) => $prefix !== '' && Str::startsWith($path, $prefix.'/'))
->sortByDesc(fn ($prefix) => strlen($prefix))
->first();

return $prefix ? Str::after($path, $prefix) : $path;
}
}
55 changes: 55 additions & 0 deletions tests/Imaging/GlideTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,53 @@ public function hybrid_caching_only_registers_the_cache_path_route()
$this->get($signedUrl)->assertNotFound();
}

#[Test]
#[DataProvider('signedUrlSiteProvider')]
public function it_validates_the_signature_against_the_site_url($siteUrl)
{
$this->setSites([
'en' => ['url' => $siteUrl, 'locale' => 'en_US'],
]);

$url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]);

$this->get($url)->assertOk()->streamedContent();
}

public static function signedUrlSiteProvider()
{
return [
'same domain' => ['http://localhost/'],
'different domain' => ['https://www.example.com/'],
'domain starting with the app domain' => ['http://local/'],
];
}

#[Test]
#[DefineEnvironment('pathPrefixedSitesOnAnotherDomain')]
public function it_validates_the_signature_for_a_path_prefixed_site_on_a_different_domain()
{
Site::setCurrent('fr');

$url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]);

$this->assertStringStartsWith('/fr/img/', $url);

$this->get($url)->assertOk()->streamedContent();
}

#[Test]
public function it_rejects_an_invalid_signature_when_the_site_url_is_on_a_different_domain()
{
$this->setSites([
'en' => ['url' => 'https://www.example.com/', 'locale' => 'en_US'],
]);

$url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]);

$this->get(str_replace('w=100', 'w=200', $url))->assertStatus(400);
}

#[Test]
public function cache_true_without_cache_path_will_throw_exception()
{
Expand Down Expand Up @@ -619,6 +666,14 @@ private function fakeFfmpeg()
});
}

protected function pathPrefixedSitesOnAnotherDomain($app)
{
$app->booting(fn () => $this->setSites([
'en' => ['url' => 'https://www.example.com/', 'locale' => 'en_US'],
'fr' => ['url' => 'https://www.example.com/fr/', 'locale' => 'fr_FR'],
]));
}

protected function hybridCaching($app)
{
$app['config']->set('statamic.assets.image_manipulation.cache', 'hybrid');
Expand Down
Loading