This commit is contained in:
Ed Chipman 2024-03-21 05:30:34 +13:00 committed by GitHub
commit d121f908de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 252 additions and 1 deletions

View File

@ -14,6 +14,7 @@ SilverStripe\Core\Injector\Injector:
ChangeDetectionMiddleware: '%$SilverStripe\Control\Middleware\ChangeDetectionMiddleware'
HTTPCacheControleMiddleware: '%$SilverStripe\Control\Middleware\HTTPCacheControlMiddleware'
CanonicalURLMiddleware: '%$SilverStripe\Control\Middleware\CanonicalURLMiddleware'
RewriteHashLinksMiddleware: '%$SilverStripe\Control\Middleware\RewriteHashLinksMiddleware'
SilverStripe\Control\Middleware\AllowedHostsMiddleware:
properties:
AllowedHosts: '`SS_ALLOWED_HOSTS`'

View File

@ -0,0 +1,64 @@
<?php
namespace SilverStripe\Control\Middleware;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Middleware\HTTPMiddleware;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Config\Configurable;
class RewriteHashLinksMiddleware implements HTTPMiddleware
{
use Configurable;
/**
* Mime types to be handled by this middleware
* @var array
* @config SilverStripe\Control\Middleware\RewriteHashLinksMiddleware.handled_mime_types
*/
private static $handled_mime_types = [
'text/html',
'application/xhtml+xml',
];
/**
* Set if hash links should be rewritten
* @var bool
* @config SilverStripe\Control\Middleware\RewriteHashLinksMiddleware.rewrite_hash_links
*/
private static $rewrite_hash_links = true;
/**
* Rewrites hash links in html responses
* @param HTTPRequest $request
* @param callable $delegate
* @return \SilverStripe\Control\HTTPResponse
*/
public function process(HTTPRequest $request, callable $delegate)
{
/** @var \SilverStripe\Control\HTTPResponse $response **/
$response = $delegate($request);
if (!$this->config()->rewrite_hash_links) {
return $response;
}
$contentType = explode(';', $response->getHeader('content-type'));
$mimeType = strtolower(trim(array_shift($contentType)));
if (!in_array($mimeType, $this->config()->handled_mime_types)) {
return $response;
}
$body = $response->getBody();
if (stripos($body, '<base') === false) {
return $response;
}
$link = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI']));
$body = preg_replace('/(<a[^>]+href *= *)("|\')#/i', '\\1\\2' . $link . '#', $body);
$response->setBody($body);
return $response;
}
}

View File

@ -112,7 +112,7 @@ class SSViewer implements Flushable
* @config
* @var bool
*/
private static $rewrite_hash_links = true;
private static $rewrite_hash_links = false;
/**
* Overridden value of rewrite_hash_links config

View File

@ -0,0 +1,186 @@
<?php
namespace SilverStripe\Control\Tests\Middleware;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\Middleware\RewriteHashLinksMiddleware;
use SilverStripe\Core\Convert;
use SilverStripe\Dev\SapphireTest;
class RewriteHashLinksMiddlewareTest extends SapphireTest
{
protected $currentHost;
protected $currentURI;
/**
* Setup the test
*/
protected function setUp()
{
parent::setUp();
$this->currentHost = (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : false);
$this->currentURI = (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : false);
}
/**
* Clean up the test
*/
protected function tearDown()
{
parent::tearDown();
if ($this->currentHost === false) {
unset($_SERVER['HTTP_HOST']);
} else {
$_SERVER['HTTP_HOST'] = $this->currentHost;
}
if ($this->currentURI === false) {
unset($_SERVER['REQUEST_URI']);
} else {
$_SERVER['REQUEST_URI'] = $this->currentURI;
}
}
/**
* Tests rewriting of HTML content types to ensure that it's properly rewriting anchors when the base tag is present
*/
public function testRewriteHTMLWithBase()
{
$_SERVER['HTTP_HOST'] = 'www.mysite.com';
$_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
$base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
$body = '<!DOCTYPE html>
<html>
<head><base href="' . Director::absoluteBaseURL() . '"><!--[if lte IE 6]></base><![endif]--></head>
<body>
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
<a class="inline" href="#anchor">InlineLink</a>
<svg><use xlink:href="#sprite"></use></svg>
</body>
</html>';
//Mock a request
$request = new HTTPRequest('GET', $_SERVER['REQUEST_URI']);
//Hand through the Middleware to be "processed"
$middleware = new RewriteHashLinksMiddleware();
$result = $middleware->process($request, function (HTTPRequest $request) use ($body) {
return HTTPResponse::create($body);
})->getBody();
$this->assertContains(
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result
);
$this->assertContains(
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result
);
$this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'RewriteHashLinksMiddleware should only rewrite anchor hrefs'
);
}
/**
* Tests rewriting of HTML content types to ensure that it's not rewriting anchors when the base tag is not present
*/
public function testRewriteHTMLWithoutBase()
{
$_SERVER['HTTP_HOST'] = 'www.mysite.com';
$_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
$base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
$body = '<!DOCTYPE html>
<html>
<head></head>
<body>
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
<a class="inline" href="#anchor">InlineLink</a>
<svg><use xlink:href="#sprite"></use></svg>
</body>
</html>';
//Mock a request
$request = new HTTPRequest('GET', $_SERVER['REQUEST_URI']);
//Hand through the Middleware to be "processed"
$middleware = new RewriteHashLinksMiddleware();
$result = $middleware->process($request, function (HTTPRequest $request) use ($body) {
return HTTPResponse::create($body);
})->getBody();
$this->assertNotContains(
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result
);
$this->assertContains(
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result
);
$this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'RewriteHashLinksMiddleware should only rewrite anchor hrefs'
);
}
/**
* Tests rewriting of JSON content type to ensure that it's not rewriting anchors
*/
public function testRewriteJSONWithBase()
{
$_SERVER['HTTP_HOST'] = 'www.mysite.com';
$_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
$base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
$body = json_encode(['test' => '<!DOCTYPE html>
<html>
<head><base href="' . Director::absoluteBaseURL() . '"><!--[if lte IE 6]></base><![endif]--></head>
<body>
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
<a class="inline" href="#anchor">InlineLink</a>
<svg><use xlink:href="#sprite"></use></svg>
</body>
</html>']);
//Mock a request
$request = new HTTPRequest('GET', $_SERVER['REQUEST_URI']);
//Hand through the Middleware to be "processed"
$middleware = new RewriteHashLinksMiddleware();
$result = $middleware->process($request, function (HTTPRequest $request) use ($body) {
return HTTPResponse::create($body)->addHeader('content-type', 'application/json; charset=utf-8');
})->getBody();
$this->assertNotContains(
'<a class=\\"inline\\" href=\\"' . $base . '#anchor\\">InlineLink<\\/a>',
$result
);
$this->assertContains(
'<a class=\\"external-inline\\" href=\\"http:\\/\\/google.com#anchor\\">ExternalInlineLink<\\/a>',
$result
);
$this->assertContains(
'<svg><use xlink:href=\\"#sprite\\"><\\/use><\\/svg>',
$result,
'RewriteHashLinksMiddleware should only rewrite anchor hrefs'
);
}
}