diff --git a/phpstan.neon b/phpstan.neon index 37ff335..41b6b74 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,5 +9,5 @@ parameters: level: 8 ignoreErrors: # This is a global alias that cannot be detected by Larastan. - - '#Call to static method loadHtml\(\) on an unknown class PDF\.#' - - '#Call to static method loadHtml\(\) on an unknown class Pdf\.#' + - '#Call to static method loadHTML\(\) on an unknown class PDF\.#' + - '#Call to static method loadHTML\(\) on an unknown class Pdf\.#' diff --git a/src/PDF.php b/src/PDF.php index 0378fee..c2c8027 100644 --- a/src/PDF.php +++ b/src/PDF.php @@ -11,6 +11,8 @@ use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; +use Symfony\Component\HttpFoundation\HeaderUtils; /** * A Laravel wrapper for Dompdf @@ -210,9 +212,11 @@ public function save(string $filename, string $disk = null): self public function download(string $filename = 'document.pdf'): Response { $output = $this->output(); + $fallback = $this->fallbackName($filename); + return new Response($output, 200, [ 'Content-Type' => 'application/pdf', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + 'Content-Disposition' => HeaderUtils::makeDisposition('attachment', $filename, $fallback), 'Content-Length' => strlen($output), ]); } @@ -223,9 +227,12 @@ public function download(string $filename = 'document.pdf'): Response public function stream(string $filename = 'document.pdf'): Response { $output = $this->output(); + $fallback = $this->fallbackName($filename); + + return new Response($output, 200, [ 'Content-Type' => 'application/pdf', - 'Content-Disposition' => 'inline; filename="' . $filename . '"', + 'Content-Disposition' => HeaderUtils::makeDisposition('inline', $filename, $fallback), ]); } @@ -301,4 +308,12 @@ public function __call($method, $parameters) throw new \UnexpectedValueException("Method [{$method}] does not exist on PDF instance."); } + + /** + * Make a safe fallback filename + */ + protected function fallbackName(string $filename): string + { + return str_replace('%', '', Str::ascii($filename)); + } } diff --git a/tests/PdfTest.php b/tests/PdfTest.php index 27bbe13..5f87da3 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -10,62 +10,62 @@ class PdfTest extends TestCase { public function testAlias(): void { - $pdf = \Pdf::loadHtml('

Test

'); + $pdf = \Pdf::loadHTML('

Test

'); /** @var Response $response */ $response = $pdf->download('test.pdf'); $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('attachment; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('attachment; filename=test.pdf', $response->headers->get('Content-Disposition')); } - + public function testAliasCaps(): void { - $pdf = \PDF::loadHtml('

Test

'); + $pdf = \PDF::loadHTML('

Test

'); /** @var Response $response */ $response = $pdf->download('test.pdf'); $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('attachment; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('attachment; filename=test.pdf', $response->headers->get('Content-Disposition')); } public function testFacade(): void { - $pdf = Facade\Pdf::loadHtml('

Test

'); + $pdf = Facade\Pdf::loadHTML('

Test

'); /** @var Response $response */ $response = $pdf->download('test.pdf'); $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('attachment; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('attachment; filename=test.pdf', $response->headers->get('Content-Disposition')); } public function testDownload(): void { - $pdf = Facade\Pdf::loadHtml('

Test

'); + $pdf = Facade\Pdf::loadHTML('

Test

'); /** @var Response $response */ $response = $pdf->download('test.pdf'); $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('attachment; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('attachment; filename=test.pdf', $response->headers->get('Content-Disposition')); } public function testStream(): void { - $pdf = Facade\Pdf::loadHtml('

Test

'); + $pdf = Facade\Pdf::loadHTML('

Test

'); /** @var Response $response */ $response = $pdf->stream('test.pdf'); $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('inline; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('inline; filename=test.pdf', $response->headers->get('Content-Disposition')); } public function testView(): void @@ -77,7 +77,31 @@ public function testView(): void $this->assertInstanceOf(Response::class, $response); $this->assertNotEmpty($response->getContent()); $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); - $this->assertEquals('attachment; filename="test.pdf"', $response->headers->get('Content-Disposition')); + $this->assertEquals('attachment; filename=test.pdf', $response->headers->get('Content-Disposition')); + } + + public function testQuoteFilename(): void + { + $pdf = Facade\Pdf::loadHTML('

Test

'); + /** @var Response $response */ + $response = $pdf->download('Test file.pdf'); + + $this->assertInstanceOf(Response::class, $response); + $this->assertNotEmpty($response->getContent()); + $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); + $this->assertEquals('attachment; filename="Test file.pdf"', $response->headers->get('Content-Disposition')); + } + + public function testFallbackFilename(): void + { + $pdf = Facade\Pdf::loadHTML('

Test

'); + /** @var Response $response */ + $response = $pdf->download('Test%file.pdf'); + + $this->assertInstanceOf(Response::class, $response); + $this->assertNotEmpty($response->getContent()); + $this->assertEquals('application/pdf', $response->headers->get('Content-Type')); + $this->assertEquals("attachment; filename=Testfile.pdf; filename*=utf-8''Test%25file.pdf", $response->headers->get('Content-Disposition')); } public function testSaveOnDisk(): void @@ -130,8 +154,8 @@ public function testSave(): void public function testMultipleInstances(): void { - $pdf1 = Facade\Pdf::loadHtml('

Test

'); - $pdf2 = Facade\Pdf::loadHtml('

Test

'); + $pdf1 = Facade\Pdf::loadHTML('

Test

'); + $pdf2 = Facade\Pdf::loadHTML('

Test

'); $pdf1->getDomPDF()->setBaseHost('host1'); $pdf2->getDomPDF()->setBaseHost('host2');