diff --git a/src/Http/Response.php b/src/Http/Response.php index 984aed4a56..9146832729 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -176,6 +176,18 @@ public static function file(string $file, array $props = []): static 'type' => F::extensionToMime(F::extension($file)) ], $props); + // if we couldn't serve a correct MIME type, force + // the browser to display the file as plain text to + // harden against attacks from malicious file uploads + if ($props['type'] === null) { + if (isset($props['headers']) !== true) { + $props['headers'] = []; + } + + $props['type'] = 'text/plain'; + $props['headers']['X-Content-Type-Options'] = 'nosniff'; + } + return new static($props); } diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 0ba52fa787..efbd6e359e 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -188,13 +188,41 @@ public function testJsonWithPrettyArray() public function testFile() { - $file = __DIR__ . '/fixtures/download.txt'; + $file = __DIR__ . '/fixtures/download.json'; + + $response = Response::file($file); + + $this->assertSame('application/json', $response->type()); + $this->assertSame(200, $response->code()); + $this->assertSame('{"foo": "bar"}', $response->body()); + + $response = Response::file($file, [ + 'code' => '201', + 'headers' => [ + 'Pragma' => 'no-cache' + ] + ]); + + $this->assertSame('application/json', $response->type()); + $this->assertSame(201, $response->code()); + $this->assertSame('{"foo": "bar"}', $response->body()); + $this->assertSame([ + 'Pragma' => 'no-cache' + ], $response->headers()); + } + + public function testFileInvalid() + { + $file = __DIR__ . '/fixtures/download.xyz'; $response = Response::file($file); $this->assertSame('text/plain', $response->type()); $this->assertSame(200, $response->code()); $this->assertSame('test', $response->body()); + $this->assertSame([ + 'X-Content-Type-Options' => 'nosniff', + ], $response->headers()); $response = Response::file($file, [ 'code' => '201', @@ -207,7 +235,8 @@ public function testFile() $this->assertSame(201, $response->code()); $this->assertSame('test', $response->body()); $this->assertSame([ - 'Pragma' => 'no-cache' + 'Pragma' => 'no-cache', + 'X-Content-Type-Options' => 'nosniff', ], $response->headers()); } diff --git a/tests/Http/fixtures/download.json b/tests/Http/fixtures/download.json new file mode 100644 index 0000000000..18d7acf586 --- /dev/null +++ b/tests/Http/fixtures/download.json @@ -0,0 +1 @@ +{"foo": "bar"} \ No newline at end of file diff --git a/tests/Http/fixtures/download.txt b/tests/Http/fixtures/download.xyz similarity index 100% rename from tests/Http/fixtures/download.txt rename to tests/Http/fixtures/download.xyz