diff --git a/src/FilesystemHandler.php b/src/FilesystemHandler.php index ac9f8b5..3bae275 100644 --- a/src/FilesystemHandler.php +++ b/src/FilesystemHandler.php @@ -9,6 +9,41 @@ class FilesystemHandler { private $root; + /** + * Mapping between file extension and MIME type to send in `Content-Type` response header + * + * @var array + */ + private $mimetypes = array( + 'atom' => 'application/atom+xml', + 'bz2' => 'application/x-bzip2', + 'css' => 'text/css', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'pdf' => 'application/pdf', + 'png' => 'image/png', + 'rss' => 'application/rss+xml', + 'svg' => 'image/svg+xml', + 'tar' => 'application/x-tar', + 'xml' => 'application/xml', + 'zip' => 'application/zip', + ); + + /** + * Assign default MIME type to send in `Content-Type` response header (same as nginx/Apache) + * + * @var string + * @see self::$mimetypes + */ + private $defaultMimetype = 'text/plain'; + public function __construct(string $root) { $this->root = $root; @@ -67,11 +102,11 @@ public function __invoke(ServerRequestInterface $request) ); } - // Assign default MIME type here (same as nginx/Apache). - // Should use mime database in the future with fallback to given default. + // Assign MIME type based on file extension (same as nginx/Apache) or fall back to given default otherwise. // Browers are pretty good at figuring out the correct type if no charset attribute is given. + $ext = \strtolower(\substr($path, \strrpos($path, '.') + 1)); $headers = [ - 'Content-Type' => 'text/plain' + 'Content-Type' => $this->mimetypes[$ext] ?? $this->defaultMimetype ]; $stat = @\stat($path); diff --git a/tests/FilesystemHandlerTest.php b/tests/FilesystemHandlerTest.php index 25ff81a..3e8600f 100644 --- a/tests/FilesystemHandlerTest.php +++ b/tests/FilesystemHandlerTest.php @@ -7,7 +7,23 @@ class FilesystemHandlerTest extends TestCase { - public function testInvokeWithValidPathToLicenseWillReturnResponseWithFileContents() + public function testInvokeWithValidPathToComposerJsonWillReturnResponseWithFileContentsAndContentType() + { + $handler = new FilesystemHandler(dirname(__DIR__)); + + $request = new ServerRequest('GET', '/source/composer.json'); + $request = $request->withAttribute('path', 'composer.json'); + + $response = $handler($request); + + /** @var ResponseInterface $response */ + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('application/json', $response->getHeaderLine('Content-Type')); + $this->assertEquals(file_get_contents(__DIR__ . '/../composer.json'), (string) $response->getBody()); + } + + public function testInvokeWithValidPathToLicenseWillReturnResponseWithFileContentsAndDefaultContentType() { $handler = new FilesystemHandler(dirname(__DIR__)); diff --git a/tests/acceptance.sh b/tests/acceptance.sh index 533e17e..ecc1b0e 100755 --- a/tests/acceptance.sh +++ b/tests/acceptance.sh @@ -68,15 +68,16 @@ out=$(curl -v $base/users 2>&1); match "HTTP/.* 404" out=$(curl -v $base/users/ 2>&1); match "HTTP/.* 404" out=$(curl -v $base/users/a/b 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/LICENSE 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" -out=$(curl -v $base/source 2>&1); match -i "Location: /source/" && match -iP "Content-Type: text/html[\r\n]" -out=$(curl -v $base/source/ 2>&1); match "HTTP/.* 200" -out=$(curl -v $base/source/LICENSE 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" -out=$(curl -v $base/source/LICENSE/ 2>&1); match -i "Location: ../LICENSE" -out=$(curl -v $base/source/LICENSE// 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source//LICENSE 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source/tests 2>&1); match -i "Location: tests/" -out=$(curl -v $base/source/invalid 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source/bin%00ary 2>&1); match "HTTP/.* 40[40]" # expects 404, but not processed with nginx (400) and Apache (404) +out=$(curl -v $base/LICENSE 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" +out=$(curl -v $base/source 2>&1); match -i "Location: /source/" && match -iP "Content-Type: text/html[\r\n]" +out=$(curl -v $base/source/ 2>&1); match "HTTP/.* 200" +out=$(curl -v $base/source/composer.json 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: application/json[\r\n]" +out=$(curl -v $base/source/LICENSE 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" +out=$(curl -v $base/source/LICENSE/ 2>&1); match -i "Location: ../LICENSE" +out=$(curl -v $base/source/LICENSE// 2>&1); match "HTTP/.* 404" +out=$(curl -v $base/source//LICENSE 2>&1); match "HTTP/.* 404" +out=$(curl -v $base/source/tests 2>&1); match -i "Location: tests/" +out=$(curl -v $base/source/invalid 2>&1); match "HTTP/.* 404" +out=$(curl -v $base/source/bin%00ary 2>&1); match "HTTP/.* 40[40]" # expects 404, but not processed with nginx (400) and Apache (404) echo "OK ($n)"