]> BookStack Code Mirror - bookstack/commitdiff
Exports: Made pdf command timeout configurable
authorDan Brown <redacted>
Fri, 27 Sep 2024 15:33:58 +0000 (16:33 +0100)
committerDan Brown <redacted>
Fri, 27 Sep 2024 15:33:58 +0000 (16:33 +0100)
Added test to cover.
For #5119

.env.example.complete
app/Config/exports.php
app/Entities/Tools/PdfGenerator.php
tests/Entity/ExportTest.php

index 1a4f421f0244a87dc0190292d9cc82ccaa7c9e28..6b4936648f0484b6a2282b6d3309478672c9ba0c 100644 (file)
@@ -334,6 +334,11 @@ EXPORT_PAGE_SIZE=a4
 # Example: EXPORT_PDF_COMMAND="/scripts/convert.sh {input_html_path} {output_pdf_path}"
 EXPORT_PDF_COMMAND=false
 
+# Export PDF Command Timeout
+# The number of seconds that the export PDF command will run before a timeout occurs.
+# Only applies for the EXPORT_PDF_COMMAND option, not for DomPDF or wkhtmltopdf.
+EXPORT_PDF_COMMAND_TIMEOUT=15
+
 # Set path to wkhtmltopdf binary for PDF generation.
 # Can be 'false' or a path path like: '/home/bins/wkhtmltopdf'
 # When false, BookStack will attempt to find a wkhtmltopdf in the application
index 88dc08cba354ca0922631b6d0e99bebb770a2558..ba535ca7e4e8026175c76e49d09d1cf8914e2e3b 100644 (file)
@@ -29,6 +29,10 @@ return [
     // Example: EXPORT_PDF_COMMAND="/scripts/convert.sh {input_html_path} {output_pdf_path}"
     'pdf_command' => env('EXPORT_PDF_COMMAND', false),
 
+    // The amount of time allowed for PDF generation command to run
+    // before the process times out and is stopped.
+    'pdf_command_timeout' => env('EXPORT_PDF_COMMAND_TIMEOUT', 15),
+
     // 2024-04: Snappy/WKHTMLtoPDF now considered deprecated in regard to BookStack support.
     'snappy' => [
         'pdf_binary' => env('WKHTMLTOPDF', false),
index 7c6dfaa6e8b2e563ea18742b42924726127e7eff..79cd1b02f7ff1daac18cbd30c6c1c3679ed0c29f 100644 (file)
@@ -5,6 +5,7 @@ namespace BookStack\Entities\Tools;
 use BookStack\Exceptions\PdfExportException;
 use Knp\Snappy\Pdf as SnappyPdf;
 use Dompdf\Dompdf;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
 use Symfony\Component\Process\Process;
 
 class PdfGenerator
@@ -85,9 +86,15 @@ class PdfGenerator
 
         file_put_contents($inputHtml, $html);
 
+        $timeout = intval(config('exports.pdf_command_timeout'));
         $process = Process::fromShellCommandline($command);
-        $process->setTimeout(15);
-        $process->run();
+        $process->setTimeout($timeout);
+
+        try {
+            $process->run();
+        } catch (ProcessTimedOutException $e) {
+            throw new PdfExportException("PDF Export via command failed due to timeout at {$timeout} second(s)");
+        }
 
         if (!$process->isSuccessful()) {
             throw new PdfExportException("PDF Export via command failed with exit code {$process->getExitCode()}, stdout: {$process->getOutput()}, stderr: {$process->getErrorOutput()}");
index 040f69013a5f1bee44b48759f10a13f644410c36..7aafa3b79277414cdcca202dfd1c17732b81cb2a 100644 (file)
@@ -529,6 +529,22 @@ class ExportTest extends TestCase
         }, PdfExportException::class);
     }
 
+    public function test_pdf_command_timout_option_limits_export_time()
+    {
+        $page = $this->entities->page();
+        $command = 'php -r \'sleep(4);\'';
+        config()->set('exports.pdf_command', $command);
+        config()->set('exports.pdf_command_timeout', 1);
+
+        $this->assertThrows(function () use ($page) {
+            $start = time();
+            $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
+
+            $this->assertTrue(time() < ($start + 3));
+        }, PdfExportException::class,
+            "PDF Export via command failed due to timeout at 1 second(s)");
+    }
+
     public function test_html_exports_contain_csp_meta_tag()
     {
         $entities = [