diff --git a/src/View/Requirements_Backend.php b/src/View/Requirements_Backend.php index aa6015ecd..d0bde0851 100644 --- a/src/View/Requirements_Backend.php +++ b/src/View/Requirements_Backend.php @@ -19,6 +19,7 @@ use SilverStripe\Dev\Debug; use SilverStripe\Dev\Deprecation; use SilverStripe\i18n\i18n; use SilverStripe\ORM\FieldType\DBField; +use Symfony\Component\Filesystem\Path as FilesystemPath; class Requirements_Backend { @@ -1454,19 +1455,12 @@ MESSAGE } $fileUrl = Injector::inst()->get(ResourceURLGenerator::class)->urlForResource($filePath); $fileUrlDir = dirname($fileUrl); - $content = preg_replace_callback('#([\s\'"\(])((:?[\.]{1,2}/)+)#', function ($a) use ($fileUrlDir) { - [ $_, $prefix, $match ] = $a; - $full = $fileUrlDir . '/' . $match; - $full = preg_replace('#/{2,}#', '/', $full); // ensure there's no repeated slashes - while (strpos($full, './') > 0) { - $post = $full; - $post = preg_replace('#([^/\.]+)/\.\./#', '', $post); // erase 'something/../' with the predecessor - $post = preg_replace('#([^/\.]+)/\./#', '\\1/', $post); // erase './' - if ($post == $full) { - break; // nothing changed - } - $full = $post; + $content = preg_replace_callback('#(url\([\n\r\s\'"]*)([^\s\)\?\'"]+)#i', function ($match) use ($fileUrlDir) { + [ $_, $prefix, $relativePath ] = $match; + if ($relativePath[0] === '/' || false !== strpos($relativePath, '://')) { + return $prefix . $relativePath; } + $full = FilesystemPath::canonicalize($fileUrlDir . '/' . $relativePath); return $prefix . $full; }, $content); return $content; diff --git a/tests/php/View/RequirementsTest.php b/tests/php/View/RequirementsTest.php index 2728209b3..727d26eb0 100644 --- a/tests/php/View/RequirementsTest.php +++ b/tests/php/View/RequirementsTest.php @@ -164,6 +164,13 @@ class RequirementsTest extends SapphireTest $content = file_get_contents($combinedFilePath); + /* COMBINED CSS URL RESOLVER IGNORE FULL URLS */ + $this->assertStringContainsString( + ".url { background: url(http://example.com/zero.gif); }", + $content, + 'combined css url resolver ignore full urls' + ); + /* COMBINED CSS URL RESOLVER DECODED ONE DOT */ $this->assertStringContainsString( ".p0 { background: url(/css/deep/deeper/zero.gif); }", @@ -171,6 +178,20 @@ class RequirementsTest extends SapphireTest 'combined css url resolver decoded one dot' ); + /* COMBINED CSS URL RESOLVER DECODED NO DOTS */ + $this->assertStringContainsString( + ".p0-plain { background: url(/css/deep/deeper/zero.gif); }", + $content, + 'combined css url resolver decoded no dots' + ); + + /* COMBINED CSS URL RESOLVER DAMAGED A QUERYSTRING */ + $this->assertStringContainsString( + ".p0-qs { background: url(/css/deep/deeper/zero.gif?some=param); }", + $content, + 'combined css url resolver damaged a querystring' + ); + /* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH SINGLE QUOTES */ $this->assertStringContainsString( ".p0sq { background: url('/css/deep/deeper/zero-sq.gif'); }", @@ -255,11 +276,18 @@ class RequirementsTest extends SapphireTest 'combined css url resolver decoded 3 double-dot' ); - /* COMBINED CSS URL RESOLVER DECODED 4 DOUBLE-DOT */ + /* COMBINED CSS URL RESOLVER DECODED 4 DOUBLE-DOT WHEN ONLY 3 LEVELS AVAILABLE*/ $this->assertStringContainsString( - ".p4 { background: url(/../four.gif); }", + ".p4 { background: url(/four.gif); }", $content, - 'combined css url resolver decoded 4 double-dot' + 'combined css url resolver decoded 4 double-dot when only 4 levels available' + ); + + /* COMBINED CSS URL RESOLVER MODIFIED AN ARBITRARY VALUE */ + $this->assertStringContainsString( + ".weird { content: \"./keepme.gif\"; }", + $content, + 'combined css url resolver modified an arbitrary value' ); } diff --git a/tests/php/View/SSViewerTest/public/css/deep/deeper/RequirementsTest_p.css b/tests/php/View/SSViewerTest/public/css/deep/deeper/RequirementsTest_p.css index 291c8ea52..a499064ee 100644 --- a/tests/php/View/SSViewerTest/public/css/deep/deeper/RequirementsTest_p.css +++ b/tests/php/View/SSViewerTest/public/css/deep/deeper/RequirementsTest_p.css @@ -1,4 +1,7 @@ +.url { background: url(http://example.com/zero.gif); } /* don't touch this */ .p0 { background: url(./zero.gif); } +.p0-plain { background: url(zero.gif); } +.p0-qs { background: url(./zero.gif?some=param); } /* keep the query string */ .p0-nl { background: url( ./zero-nl.gif ); } @@ -20,3 +23,4 @@ .p2dq { background: url("../../two-dq.gif"); } .p3 { background: url(../../../three.gif); } .p4 { background: url(../../../../four.gif); } +.weird { content: "./keepme.gif"; }