Skip to content

Commit

Permalink
Implement new option "allowedRemoteHosts" to restrict which remote ho…
Browse files Browse the repository at this point in the history
…sts can be requested
  • Loading branch information
bohwaz committed Feb 25, 2024
1 parent e1d262b commit 8608964
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ class Options
*/
private $isRemoteEnabled = false;

/**
* List of allowed remote hosts
*
* Each value of the array must be a valid hostname.
*
* This will be used to filter which resources can be loaded in combination with
* isRemoteEnabled. If isRemoteEnabled is FALSE, then this will have no effect.
*
* Leave to NULL to allow any remote host.
*
* @var array|null
*/
private $allowedRemoteHosts = null;

/**
* Enable inline JavaScript
*
Expand Down Expand Up @@ -379,6 +393,8 @@ public function set($attributes, $value = null)
$this->setIsPhpEnabled($value);
} elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') {
$this->setIsRemoteEnabled($value);
} elseif ($key === 'allowedRemoteHosts' || $key === 'allowed_remote_hosts') {
$this->setAllowedRemoteHosts($value);
} elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') {
$this->setIsJavascriptEnabled($value);
} elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') {
Expand Down Expand Up @@ -446,6 +462,8 @@ public function get($key)
return $this->getIsPhpEnabled();
} elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') {
return $this->getIsRemoteEnabled();
} elseif ($key === 'allowedRemoteHosts' || $key === 'allowed_remote_hosts') {
return $this->getAllowedProtocols();
} elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') {
return $this->getIsJavascriptEnabled();
} elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') {
Expand Down Expand Up @@ -1029,6 +1047,33 @@ public function isRemoteEnabled()
return $this->getIsRemoteEnabled();
}

/**
* @param array|null $allowedRemoteHosts
* @return $this
*/
public function setAllowedRemoteHosts($allowedRemoteHosts)
{
if (is_array($allowedRemoteHosts)) {
// Set hosts to lowercase
foreach ($allowedRemoteHosts as &$host) {
$host = mb_strtolower($host);
}

unset($host);
}

$this->allowedRemoteHosts = $allowedRemoteHosts;
return $this;
}

/**
* @return array|null
*/
public function getAllowedRemoteHosts()
{
return $this->allowedRemoteHosts;
}

/**
* @param string $logOutputFile
* @return $this
Expand Down Expand Up @@ -1154,6 +1199,15 @@ public function validateRemoteUri(string $uri)
return [false, "Remote file requested, but remote file download is disabled."];
}

if (is_array($this->allowedRemoteHosts) && count($this->allowedRemoteHosts) > 0) {
$host = parse_url($uri, PHP_URL_HOST);
$host = mb_strtolower($host);

if (!in_array($host, $this->allowedRemoteHosts, true)) {
return [false, "Remote host is not in allowed list: " . $host];
}
}

return [true, null];
}
}
29 changes: 29 additions & 0 deletions tests/OptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public function testConstructor()
$this->assertTrue($option->getDebugLayoutBlocks());
$this->assertTrue($option->getDebugLayoutInline());
$this->assertTrue($option->getDebugLayoutPaddingBox());
$this->assertNull($option->getAllowedRemoteHosts());

$option = new Options(['tempDir' => 'test1']);
$this->assertEquals('test1', $option->getTempDir());
Expand All @@ -53,6 +54,7 @@ public function testSetters()
'fontHeightRatio' => 1.2,
'isPhpEnabled' => true,
'isRemoteEnabled' => true,
'allowedRemoteHosts' => ['w3.org'],
'isJavascriptEnabled' => false,
'isHtml5ParserEnabled' => true,
'isFontSubsettingEnabled' => false,
Expand Down Expand Up @@ -89,6 +91,7 @@ public function testSetters()
$this->assertFalse($option->getDebugLayoutInline());
$this->assertFalse($option->getDebugLayoutPaddingBox());
$this->assertIsResource($option->getHttpContext());
$this->assertIsArray($option->getAllowedRemoteHosts());

$option->setChroot(['test11']);
$this->assertEquals(['test11'], $option->getChroot());
Expand Down Expand Up @@ -133,4 +136,30 @@ function ($uri) { return [true, null]; }
[$validation_result] = $allowedProtocols["mock://"]["rules"][0]("mock://example.com/");
$this->assertTrue($validation_result);
}

public function testAllowedRemoteHosts()
{
$options = new Options(['isRemoteEnabled' => true]);
$options->setAllowedRemoteHosts(['en.wikipedia.org']);
$options->setAllowedProtocols(["http://"]);
$allowedRemoteHosts = $options->getAllowedRemoteHosts();
$this->assertIsArray($allowedRemoteHosts);
$this->assertEquals(1, count($allowedRemoteHosts));
$this->assertContains("en.wikipedia.org", $allowedRemoteHosts);

$allowedProtocols = $options->getAllowedProtocols();
$this->assertIsArray($allowedProtocols);
$this->assertEquals(1, count($allowedProtocols));
$this->assertArrayHasKey("http://", $allowedProtocols);
$this->assertIsArray($allowedProtocols["http://"]);
$this->assertArrayHasKey("rules", $allowedProtocols["http://"]);
$this->assertIsArray($allowedProtocols["http://"]["rules"]);
$this->assertEquals(1, count($allowedProtocols["http://"]["rules"]));
$this->assertEquals([$options, "validateRemoteUri"], $allowedProtocols["http://"]["rules"][0]);

[$validation_result] = $allowedProtocols["http://"]["rules"][0]("http://example.com/");
$this->assertFalse($validation_result);
[$validation_result] = $allowedProtocols["http://"]["rules"][0]("http://en.wikipedia.org/");
$this->assertTrue($validation_result);
}
}

0 comments on commit 8608964

Please sign in to comment.