mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
BUGFIX: Improved RedirectorPage's handling of invalid configuration options to prevent infinite loops and segfaults
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@63939 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
679f694c67
commit
866853f31a
@ -33,13 +33,54 @@ class RedirectorPage extends Page {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the the link that should be used for this redirector page, in navigation, etc.
|
||||
* If the redirectorpage has been appropriately configured, then it will return the redirection
|
||||
* destination, to prevent unnecessary 30x redirections. However, if it's misconfigured, then
|
||||
* it will return a link to itself, which will then display an error message.
|
||||
*/
|
||||
function Link() {
|
||||
if($link = $this->redirectionLink()) return $link;
|
||||
else return $this->regularLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the normal link directly to this page. Once you visit this link, a 30x redirection
|
||||
* will take you to your final destination.
|
||||
*/
|
||||
function regularLink() {
|
||||
return parent::Link();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the link that we should redirect to.
|
||||
* Only return a value if there is a legal redirection destination.
|
||||
*/
|
||||
function redirectionLink() {
|
||||
if($this->RedirectionType == 'External') {
|
||||
return Convert::raw2att($this->ExternalURL);
|
||||
if($this->ExternalURL) {
|
||||
return Convert::raw2att($this->ExternalURL);
|
||||
}
|
||||
|
||||
} else {
|
||||
$linkTo = $this->LinkToID ? DataObject::get_by_id("SiteTree", $this->LinkToID) : null;
|
||||
|
||||
if($linkTo) {
|
||||
return $linkTo->Link();
|
||||
// We shouldn't point to ourselves - that would create an infinite loop! Return null since we have a
|
||||
// bad configuration
|
||||
if($this->ID == $linkTo->ID) {
|
||||
return null;
|
||||
|
||||
// If we're linking to another redirectorpage then just return the URLSegment, to prevent a cycle of redirector
|
||||
// pages from causing an infinite loop. Instead, they will cause a 30x redirection loop in the browser, but
|
||||
// this can be handled sufficiently gracefully by the browser.
|
||||
} elseif($linkTo instanceof RedirectorPage) {
|
||||
return $linkTo->regularLink();
|
||||
|
||||
// For all other pages, just return the link of the page.
|
||||
} else {
|
||||
return $linkTo->Link();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,17 +149,10 @@ class RedirectorPage extends Page {
|
||||
*/
|
||||
class RedirectorPage_Controller extends Page_Controller {
|
||||
function init() {
|
||||
if($this->RedirectionType == 'External') {
|
||||
if($this->ExternalURL) {
|
||||
Director::redirect($this->ExternalURL);
|
||||
}
|
||||
} else {
|
||||
$linkTo = DataObject::get_by_id("SiteTree", $this->LinkToID);
|
||||
if($linkTo) {
|
||||
Director::redirect($linkTo->Link(), 301);
|
||||
}
|
||||
if($link = $this->redirectionLink()) {
|
||||
Director::redirect($link, 301);
|
||||
}
|
||||
|
||||
|
||||
parent::init();
|
||||
}
|
||||
|
||||
@ -126,9 +160,11 @@ class RedirectorPage_Controller extends Page_Controller {
|
||||
* If we ever get this far, it means that the redirection failed.
|
||||
*/
|
||||
function index() {
|
||||
return "<p>" .
|
||||
return array(
|
||||
"Content" => "<p>" .
|
||||
_t('RedirectorPage.HASBEENSETUP', 'A redirector page has been set up without anywhere to redirect to.') .
|
||||
"</p>";
|
||||
"</p>"
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
48
tests/RedirectorPageTest.php
Normal file
48
tests/RedirectorPageTest.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
class RedirectorPageTest extends FunctionalTest {
|
||||
static $fixture_file = 'sapphire/tests/RedirectorPageTest.yml';
|
||||
static $use_draft_site = true;
|
||||
|
||||
function testGoodRedirectors() {
|
||||
/* For good redirectors, the final destination URL will be returned */
|
||||
$this->assertEquals("http://www.google.com", $this->objFromFixture('RedirectorPage','goodexternal')->Link());
|
||||
$this->assertEquals(Director::baseURL() . "redirection-dest/", $this->objFromFixture('RedirectorPage','goodinternal')->redirectionLink());
|
||||
$this->assertEquals(Director::baseURL() . "redirection-dest/", $this->objFromFixture('RedirectorPage','goodinternal')->Link());
|
||||
}
|
||||
|
||||
function testEmptyRedirectors() {
|
||||
/* If a redirector page is misconfigured, then its link method will just return the usual URLSegment-generated value */
|
||||
$page1 = $this->objFromFixture('RedirectorPage','badexternal');
|
||||
$this->assertEquals(Director::baseURL() . 'bad-external/', $page1->Link());
|
||||
|
||||
/* An error message will be shown if you visit it */
|
||||
$content = $this->get(Director::makeRelative($page1->Link()))->getBody();
|
||||
$this->assertContains("A redirector page has been set up without anywhere to redirect to.", $content);
|
||||
|
||||
/* This also applies for internal links */
|
||||
$page2 = $this->objFromFixture('RedirectorPage','badinternal');
|
||||
$this->assertEquals(Director::baseURL() . 'bad-internal/', $page2->Link());
|
||||
$content = $this->get(Director::makeRelative($page2->Link()))->getBody();
|
||||
$this->assertContains("A redirector page has been set up without anywhere to redirect to.", $content);
|
||||
}
|
||||
|
||||
function testReflexiveAndTransitiveInternalRedirectors() {
|
||||
/* Reflexive redirectors are those that point to themselves. They should behave the same as an empty redirector */
|
||||
$page = $this->objFromFixture('RedirectorPage','reflexive');
|
||||
$this->assertEquals(Director::baseURL() . 'reflexive/', $page->Link());
|
||||
$content = $this->get(Director::makeRelative($page->Link()))->getBody();
|
||||
$this->assertContains("A redirector page has been set up without anywhere to redirect to.", $content);
|
||||
|
||||
/* Transitive redirectors are those that point to another redirector page. They should send people to the URLSegment
|
||||
* of the destination page - the middle-stop, so to speak. That should redirect to the final destination */
|
||||
$page = $this->objFromFixture('RedirectorPage','transitive');
|
||||
$this->assertEquals(Director::baseURL() . 'good-internal/', $page->Link());
|
||||
|
||||
$this->autoFollowRedirection = false;
|
||||
$response = $this->get(Director::makeRelative($page->Link()));
|
||||
$this->assertEquals(Director::baseURL() . "redirection-dest/", $response->getHeader("Location"));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
34
tests/RedirectorPageTest.yml
Normal file
34
tests/RedirectorPageTest.yml
Normal file
@ -0,0 +1,34 @@
|
||||
Page:
|
||||
dest:
|
||||
Title: Redirection Dest
|
||||
URLSegment: redirection-dest
|
||||
|
||||
RedirectorPage:
|
||||
goodexternal:
|
||||
Title: Good External
|
||||
URLSegment: good-external
|
||||
RedirectionType: External
|
||||
ExternalURL: http://www.google.com
|
||||
goodinternal:
|
||||
Title: Good Internal
|
||||
URLSegment: good-internal
|
||||
RedirectionType: Internal:
|
||||
LinkTo: =>Page.dest
|
||||
badexternal:
|
||||
Title: Bad External
|
||||
RedirectionType: External
|
||||
URLSegment: bad-external
|
||||
badinternal:
|
||||
Title: Bad Internal
|
||||
RedirectionType: Internal
|
||||
URLSegment: bad-internal
|
||||
reflexive:
|
||||
Title: Reflexive
|
||||
RedirectionType: Internal
|
||||
LinkTo: =>RedirectorPage.reflexive
|
||||
URLSegment: reflexive
|
||||
transitive:
|
||||
Title: Transitive
|
||||
RedirectionType: Internal
|
||||
LinkTo: =>RedirectorPage.goodinternal
|
||||
URLSegment: transitive
|
Loading…
Reference in New Issue
Block a user