| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-19 03:53:39 +08:00
										 |  |  | namespace BookStack\Http; | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | use Illuminate\Http\Request; | 
					
						
							|  |  |  | use Illuminate\Http\Response; | 
					
						
							|  |  |  | use Symfony\Component\HttpFoundation\StreamedResponse; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class DownloadResponseFactory | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2024-01-07 22:03:13 +08:00
										 |  |  |     public function __construct( | 
					
						
							| 
									
										
										
										
											2024-12-31 23:13:50 +08:00
										 |  |  |         protected Request $request, | 
					
						
							| 
									
										
										
										
											2024-01-07 22:03:13 +08:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create a response that directly forces a download in the browser. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function directly(string $content, string $fileName): Response | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |         return response()->make($content, 200, $this->getHeaders($fileName, strlen($content))); | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create a response that forces a download, from a given stream of content. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-01-07 22:03:13 +08:00
										 |  |  |     public function streamedDirectly($stream, string $fileName, int $fileSize): StreamedResponse | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |         $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request); | 
					
						
							|  |  |  |         $headers = array_merge($this->getHeaders($fileName, $fileSize), $rangeStream->getResponseHeaders()); | 
					
						
							|  |  |  |         return response()->stream( | 
					
						
							|  |  |  |             fn() => $rangeStream->outputAndClose(), | 
					
						
							|  |  |  |             $rangeStream->getResponseStatus(), | 
					
						
							|  |  |  |             $headers, | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-31 23:13:50 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a response that downloads the given file via a stream. | 
					
						
							|  |  |  |      * Has the option to delete the provided file once the stream is closed. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-01-13 22:30:53 +08:00
										 |  |  |     public function streamedFileDirectly(string $filePath, string $fileName, bool $deleteAfter = false): StreamedResponse | 
					
						
							| 
									
										
										
										
											2024-12-31 23:13:50 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-01-13 22:30:53 +08:00
										 |  |  |         $fileSize = filesize($filePath); | 
					
						
							| 
									
										
										
										
											2024-12-31 23:13:50 +08:00
										 |  |  |         $stream = fopen($filePath, 'r'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($deleteAfter) { | 
					
						
							|  |  |  |             // Delete the given file if it still exists after the app terminates
 | 
					
						
							|  |  |  |             $callback = function () use ($filePath) { | 
					
						
							|  |  |  |                 if (file_exists($filePath)) { | 
					
						
							|  |  |  |                     unlink($filePath); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // We watch both app terminate and php shutdown to cover both normal app termination
 | 
					
						
							|  |  |  |             // as well as other potential scenarios (connection termination).
 | 
					
						
							|  |  |  |             app()->terminating($callback); | 
					
						
							|  |  |  |             register_shutdown_function($callback); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $this->streamedDirectly($stream, $fileName, $fileSize); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a file download response that provides the file with a content-type | 
					
						
							|  |  |  |      * correct for the file, in a way so the browser can show the content in browser, | 
					
						
							|  |  |  |      * for a given content stream. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-01-07 22:03:13 +08:00
										 |  |  |     public function streamedInline($stream, string $fileName, int $fileSize): StreamedResponse | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |         $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request); | 
					
						
							| 
									
										
										
										
											2025-01-14 00:51:07 +08:00
										 |  |  |         $mime = $rangeStream->sniffMime(pathinfo($fileName, PATHINFO_EXTENSION)); | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |         $headers = array_merge($this->getHeaders($fileName, $fileSize, $mime), $rangeStream->getResponseHeaders()); | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |         return response()->stream( | 
					
						
							|  |  |  |             fn() => $rangeStream->outputAndClose(), | 
					
						
							|  |  |  |             $rangeStream->getResponseStatus(), | 
					
						
							|  |  |  |             $headers, | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 22:30:53 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a response that provides the given file via a stream with detected content-type. | 
					
						
							|  |  |  |      * Has the option to delete the provided file once the stream is closed. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function streamedFileInline(string $filePath, ?string $fileName = null): StreamedResponse | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $fileSize = filesize($filePath); | 
					
						
							|  |  |  |         $stream = fopen($filePath, 'r'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($fileName === null) { | 
					
						
							|  |  |  |             $fileName = basename($filePath); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $this->streamedInline($stream, $fileName, $fileSize); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the common headers to provide for a download response. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |     protected function getHeaders(string $fileName, int $fileSize, string $mime = 'application/octet-stream'): array | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline'; | 
					
						
							|  |  |  |         $downloadName = str_replace('"', '', $fileName); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return [ | 
					
						
							|  |  |  |             'Content-Type'           => $mime, | 
					
						
							| 
									
										
										
										
											2024-01-08 04:34:03 +08:00
										 |  |  |             'Content-Length'         => $fileSize, | 
					
						
							| 
									
										
										
										
											2022-06-09 06:50:42 +08:00
										 |  |  |             'Content-Disposition'    => "{$disposition}; filename=\"{$downloadName}\"", | 
					
						
							|  |  |  |             'X-Content-Type-Options' => 'nosniff', | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-10 19:37:14 +08:00
										 |  |  | } |