spatie / laravel-pdf

Create PDF files in Laravel apps
https://spatie.be/docs/laravel-pdf
MIT License
728 stars 56 forks source link

[Bug]: PDF files uploaded to S3 using the disk() method are corrupted #167

Closed danatemple closed 3 months ago

danatemple commented 3 months ago

What happened?

I used the ->disk() method to upload to S3, but when I do this I can see that the file has double the expected size, and does not parse as a valid PDF.

How to reproduce the bug

This process, where I generate a PDF file locally and then manually copy to S3, works:

                Pdf::view('messages.da.invite', $params)
                   ->save($localFilePath);

                $contents = file_get_contents($localFilePath);

                $disk->put($filePath, $contents);

This version fails.

               Pdf::view('messages.da.invite', $params)
                   ->disk($diskName)
                   ->save($filePath);

The file is created, but I can see that it is twice the expected size on S3 and that on copying back from S3, it fails to parse.

$filePath is the path on S3 from the bucket root. $diskName is the string key from config('filesystems.disks') - I expected actually to be able to pass an actual disk instance ($disk), but this did not work.

Hope I am not missing something obvious here!

Package Version

1.5.2

PHP Version

8.2.12

Laravel Version

10.48.20

Which operating systems does with happen with?

Ubuntu 20.04

Notes

Also installed:

league/flysystem-aws-s3-v3 3.28.0

jegardiner commented 3 months ago

I am also experiencing this issue. When using the disk() method, the resulting PDF is corrupt, even on local filesystems.

Saving to the local filesystem without using disk() works correctly.

jegardiner commented 3 months ago

I've looked into this further and I can see that problem is actually occurring in Browsershot.

When saving to a filesystem, Browsershot uses it's pdf() method to capture the output of Chrome, rather than asking Chrome to save the file directly (as using the savePdf() method does).

The pdf() method is expecting Base64 encoded output and calls base64_decode on the output before it returns it. But the output isn't Base64 encoded. It's a string representing a UInt8Array.

Changing the pdf() function to convert the output string back into an array and then decoding before returning it gets everything working as expected:

    public function pdf(): string
    {
        $command = $this->createPdfCommand();

        $encodedPdf = $this->callBrowser($command);

        $this->cleanupTemporaryHtmlFile();

        // split the string into an UInt8Array
        $exploded = explode(',', $encodedPdf);

        // convert the UInt8Array into a string of characters
        $converted = implode(array_map('chr', $exploded));

        // return the converted string
        return $converted;
    }

Please let me know if you would like me to submit this as a PR in the Browsershot repository?

freekmurze commented 3 months ago

Feel free to submit a PR 👍

antgel commented 3 weeks ago

@jegardiner Did you ever submit that PR?