]> BookStack Code Mirror - bookstack/blob - app/Http/DownloadResponseFactory.php
Drawings: Added class to extract drawio data from png files
[bookstack] / app / Http / DownloadResponseFactory.php
1 <?php
2
3 namespace BookStack\Http;
4
5 use Illuminate\Http\Request;
6 use Illuminate\Http\Response;
7 use Symfony\Component\HttpFoundation\StreamedResponse;
8
9 class DownloadResponseFactory
10 {
11     public function __construct(
12         protected Request $request,
13     ) {
14     }
15
16     /**
17      * Create a response that directly forces a download in the browser.
18      */
19     public function directly(string $content, string $fileName): Response
20     {
21         return response()->make($content, 200, $this->getHeaders($fileName, strlen($content)));
22     }
23
24     /**
25      * Create a response that forces a download, from a given stream of content.
26      */
27     public function streamedDirectly($stream, string $fileName, int $fileSize): StreamedResponse
28     {
29         $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request);
30         $headers = array_merge($this->getHeaders($fileName, $fileSize), $rangeStream->getResponseHeaders());
31         return response()->stream(
32             fn() => $rangeStream->outputAndClose(),
33             $rangeStream->getResponseStatus(),
34             $headers,
35         );
36     }
37
38     /**
39      * Create a response that downloads the given file via a stream.
40      * Has the option to delete the provided file once the stream is closed.
41      */
42     public function streamedFileDirectly(string $filePath, string $fileName, bool $deleteAfter = false): StreamedResponse
43     {
44         $fileSize = filesize($filePath);
45         $stream = fopen($filePath, 'r');
46
47         if ($deleteAfter) {
48             // Delete the given file if it still exists after the app terminates
49             $callback = function () use ($filePath) {
50                 if (file_exists($filePath)) {
51                     unlink($filePath);
52                 }
53             };
54
55             // We watch both app terminate and php shutdown to cover both normal app termination
56             // as well as other potential scenarios (connection termination).
57             app()->terminating($callback);
58             register_shutdown_function($callback);
59         }
60
61         return $this->streamedDirectly($stream, $fileName, $fileSize);
62     }
63
64
65     /**
66      * Create a file download response that provides the file with a content-type
67      * correct for the file, in a way so the browser can show the content in browser,
68      * for a given content stream.
69      */
70     public function streamedInline($stream, string $fileName, int $fileSize): StreamedResponse
71     {
72         $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request);
73         $mime = $rangeStream->sniffMime(pathinfo($fileName, PATHINFO_EXTENSION));
74         $headers = array_merge($this->getHeaders($fileName, $fileSize, $mime), $rangeStream->getResponseHeaders());
75
76         return response()->stream(
77             fn() => $rangeStream->outputAndClose(),
78             $rangeStream->getResponseStatus(),
79             $headers,
80         );
81     }
82
83     /**
84      * Create a response that provides the given file via a stream with detected content-type.
85      * Has the option to delete the provided file once the stream is closed.
86      */
87     public function streamedFileInline(string $filePath, ?string $fileName = null): StreamedResponse
88     {
89         $fileSize = filesize($filePath);
90         $stream = fopen($filePath, 'r');
91
92         if ($fileName === null) {
93             $fileName = basename($filePath);
94         }
95
96         return $this->streamedInline($stream, $fileName, $fileSize);
97     }
98
99     /**
100      * Get the common headers to provide for a download response.
101      */
102     protected function getHeaders(string $fileName, int $fileSize, string $mime = 'application/octet-stream'): array
103     {
104         $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline';
105         $downloadName = str_replace('"', '', $fileName);
106
107         return [
108             'Content-Type'           => $mime,
109             'Content-Length'         => $fileSize,
110             'Content-Disposition'    => "{$disposition}; filename=\"{$downloadName}\"",
111             'X-Content-Type-Options' => 'nosniff',
112         ];
113     }
114 }