115 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			115 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| 
 | |
| namespace BookStack\Http;
 | |
| 
 | |
| use Illuminate\Http\Request;
 | |
| use Illuminate\Http\Response;
 | |
| use Symfony\Component\HttpFoundation\StreamedResponse;
 | |
| 
 | |
| class DownloadResponseFactory
 | |
| {
 | |
|     public function __construct(
 | |
|         protected Request $request,
 | |
|     ) {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a response that directly forces a download in the browser.
 | |
|      */
 | |
|     public function directly(string $content, string $fileName): Response
 | |
|     {
 | |
|         return response()->make($content, 200, $this->getHeaders($fileName, strlen($content)));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a response that forces a download, from a given stream of content.
 | |
|      */
 | |
|     public function streamedDirectly($stream, string $fileName, int $fileSize): StreamedResponse
 | |
|     {
 | |
|         $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,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a response that downloads the given file via a stream.
 | |
|      * Has the option to delete the provided file once the stream is closed.
 | |
|      */
 | |
|     public function streamedFileDirectly(string $filePath, string $fileName, bool $deleteAfter = false): StreamedResponse
 | |
|     {
 | |
|         $fileSize = filesize($filePath);
 | |
|         $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);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * 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.
 | |
|      */
 | |
|     public function streamedInline($stream, string $fileName, int $fileSize): StreamedResponse
 | |
|     {
 | |
|         $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request);
 | |
|         $mime = $rangeStream->sniffMime(pathinfo($fileName, PATHINFO_EXTENSION));
 | |
|         $headers = array_merge($this->getHeaders($fileName, $fileSize, $mime), $rangeStream->getResponseHeaders());
 | |
| 
 | |
|         return response()->stream(
 | |
|             fn() => $rangeStream->outputAndClose(),
 | |
|             $rangeStream->getResponseStatus(),
 | |
|             $headers,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the common headers to provide for a download response.
 | |
|      */
 | |
|     protected function getHeaders(string $fileName, int $fileSize, string $mime = 'application/octet-stream'): array
 | |
|     {
 | |
|         $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline';
 | |
|         $downloadName = str_replace('"', '', $fileName);
 | |
| 
 | |
|         return [
 | |
|             'Content-Type'           => $mime,
 | |
|             'Content-Length'         => $fileSize,
 | |
|             'Content-Disposition'    => "{$disposition}; filename=\"{$downloadName}\"",
 | |
|             'X-Content-Type-Options' => 'nosniff',
 | |
|         ];
 | |
|     }
 | |
| }
 |