Skip to content

Commit

Permalink
Implements a whitelist for asset filetypes (#18)
Browse files Browse the repository at this point in the history
* [ch] general grammer fixes

* [f#15] add status code to asset header

fixes #15

* [ch] improve how invalid assets are handled -

refs #15

* [a#14] Allow asset type to be whitelisted -

closed #14
  • Loading branch information
NigelGreenway authored May 24, 2017
1 parent eea9734 commit 585486e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 7 deletions.
37 changes: 35 additions & 2 deletions spec/ServerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
declare(strict_types=1);
namespace spec\ReactiveSlim;

use ReactiveSlim\AssetType;
use ReactiveSlim\DirectoryNotFound;
use ReactiveSlim\Server;
use PhpSpec\ObjectBehavior;
Expand All @@ -17,7 +18,7 @@ function let(App $slimApp)
$this->beConstructedWith($slimApp);
}

function it_is_initializable()
function it_is_initialized()
{
$this->shouldHaveType(Server::class);
}
Expand Down Expand Up @@ -47,10 +48,42 @@ function it_should_contain_a_valid_asset_directory(App $slimInstance)
->beConstructedWith($slimInstance, __DIR__);
}

function it_should_throw_a_DirectoryNotFound_exeption(App $slimInstance)
function it_should_throw_a_DirectoryNotFound_exception(App $slimInstance)
{
$this
->shouldThrow(DirectoryNotFound::class)
->during('__construct', [$slimInstance, __DIR__.'/../directory_does_not_exists']);
}

function it_should_set_the_asset_whitelist_correctly_with_custom_array_of_asset_types()
{
$this
->setAllowedFileTypes([AssetType::STYLESHEET, AssetType::IMAGE])
->shouldReturnAnInstanceOf(Server::class);

$this
->getConfigVariables()
->shouldBe([
'host' => '0.0.0.0',
'port' => 1337,
'env' => ServerEnvironment::PRODUCTION,
'allowedAssetFileTypes' => implode('|', [AssetType::STYLESHEET, AssetType::IMAGE]),
]);
}

function it_should_set_the_asset_whitelist_correctly_with_custom_string_of_file_types()
{
$this
->setAllowedFileTypes('css|png|js')
->shouldReturnAnInstanceOf(Server::class);

$this
->getConfigVariables()
->shouldBe([
'host' => '0.0.0.0',
'port' => 1337,
'env' => ServerEnvironment::PRODUCTION,
'allowedAssetFileTypes' => 'css|png|js',
]);
}
}
10 changes: 10 additions & 0 deletions src/AssetType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace ReactiveSlim;

final class AssetType
{
const STYLESHEET = 'css|css.map';
const IMAGE = 'png|jpg|jpeg|gif';
const SCRIPT = 'js|js.map';
}
53 changes: 48 additions & 5 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ final class Server
private $loop;
/** @var HttpServer $server */
private $server;
/** @var string $whiteListedAssetFileTypes */
private $whiteListedAssetFileTypes = 'css|css.map|png|jpg|jpeg|gif|js|js.map';


/**
Expand Down Expand Up @@ -81,17 +83,45 @@ public function setEnvironment(int $environment) :self
return $this;
}

/**
* @param array|string $fileTypes
* @return Server
*/
public function setAllowedFileTypes($fileTypes) :self
{
if (is_array($fileTypes) === true) {
$fileTypeString = '';
foreach ($fileTypes as $loopIndex => $fileType) {
$fileTypeString .= $loopIndex === 0 ? $fileType : '|' . $fileType;
}
$this->whiteListedAssetFileTypes = $fileTypeString;
return $this;
}

if (is_string($fileTypes) === true) {
$this->whiteListedAssetFileTypes = $fileTypes;
return $this;
}
}

/** @return void */
public function run()
{
$this->initialiseReactPHP();

$this->server->on('request', function (ReactRequest $request, ReactResponse $response) {

if (preg_match('/\.(?:css|png|jpg|jpeg|gif)$/', $request->getPath())) {
$body = file_get_contents($this->webRoot . $request->getPath());
$response->writeHead(['Content-Type' => $request->getHeaders()['Accept'][0]]);
$response->end($body);
$fileTypes = sprintf('/\.(?:%s)$/', $this->whiteListedAssetFileTypes);

if (preg_match($fileTypes, $request->getPath())) {
$assetFilePath = sprintf('%s%s', $this->webRoot, $request->getPath());
if (is_file($assetFilePath) === true) {
$response->writeHead(200, ['Content-Type' => $request->getHeaders()['Accept'][0]]);
$response->end(file_get_contents($assetFilePath));
} else {
$response->writeHead(404, ['Content-Type' => 'text/plain']);
$response->end(sprintf('%s was not found', $request->getPath()));
}
} else {
$stream = new Stream('php://memory', 'w+');

Expand Down Expand Up @@ -123,16 +153,29 @@ public function run()

if ($this->environment !== ServerEnvironment::PRODUCTION) {
echo sprintf(
" >> Listening on http://%s:%d\n\nIn %s environment\n\n",
" >> Listening on http://%s:%d\n\nAssets allowed to be served: %s\n\nIn %s environment\n\n",
$this->host,
$this->port,
str_replace('|', ', ', $this->whiteListedAssetFileTypes),
ServerEnvironment::getEnvironmentName($this->environment)
);
}

$this->loop->run();
}

/**
* @return array
*/
public function getConfigVariables() :array
{
return [
'host' => $this->host,
'port' => $this->port,
'env' => $this->environment,
'allowedAssetFileTypes' => $this->whiteListedAssetFileTypes,
];
}

/**
* Initialise ReactPHP setup
Expand Down

0 comments on commit 585486e

Please sign in to comment.