diff --git a/lib/Helper/ConfigureCheckHelper.php b/lib/Helper/ConfigureCheckHelper.php index 6f9ed4ae6b..e45c917e7b 100644 --- a/lib/Helper/ConfigureCheckHelper.php +++ b/lib/Helper/ConfigureCheckHelper.php @@ -11,6 +11,9 @@ use JsonSerializable; /** + * @method ConfigureCheckHelper setSuccessMessage(string $value) + * @method ConfigureCheckHelper setInfoMessage(string $value) + * @method ConfigureCheckHelper setErrorMessage(string $value) * @method ConfigureCheckHelper setStatus(string $value) * @method string getStatus() * @method ConfigureCheckHelper setMessage(string $value) diff --git a/lib/Service/Install/ConfigureCheckService.php b/lib/Service/Install/ConfigureCheckService.php index 2e3f702429..3b6ec6ae92 100644 --- a/lib/Service/Install/ConfigureCheckService.php +++ b/lib/Service/Install/ConfigureCheckService.php @@ -66,11 +66,17 @@ public function checkSign(): array { } public function checkPoppler(): array { + $return = $this->checkPdfSig(); + $return = array_merge($return, $this->checkPdfinfo()); + return $return; + } + + public function checkPdfSig(): array { if (shell_exec('which pdfsig') === null) { return [ (new ConfigureCheckHelper()) ->setInfoMessage('Poppler utils not installed') - ->setResource('poppler-utils') + ->setResource('pdfsig') ->setTip('Install the package poppler-utils at your operational system to be possible get more details about validation of signatures.'), ]; } @@ -80,8 +86,8 @@ public function checkPoppler(): array { if (!$version) { return [ (new ConfigureCheckHelper()) - ->setInfoMessage('Fail to retrieve pdfsig version') - ->setResource('poppler-utils') + ->setErrorMessage('Fail to retrieve pdfsig version') + ->setResource('pdfsig') ->setTip("The command executed by PHP haven't any output."), ]; } @@ -89,8 +95,8 @@ public function checkPoppler(): array { if (!$version) { return [ (new ConfigureCheckHelper()) - ->setInfoMessage('Fail to retrieve pdfsig version') - ->setResource('poppler-utils') + ->setErrorMessage('Fail to retrieve pdfsig version') + ->setResource('pdfsig') ->setTip("This is a poppler-utils dependency and wasn't possible to parse the output of command pdfsig -v"), ]; } @@ -100,6 +106,41 @@ public function checkPoppler(): array { ]; } + public function checkPdfinfo(): array { + if (shell_exec('which pdfinfo') === null) { + return [ + (new ConfigureCheckHelper()) + ->setInfoMessage('Poppler utils not installed') + ->setResource('pdfinfo') + ->setTip('Install the package poppler-utils at your operational system have a fallback to fetch page dimensions.'), + ]; + } + // The output of this command go to STDERR and shell_exec get the STDOUT + // With 2>&1 the STRERR is redirected to STDOUT + $version = shell_exec('pdfinfo -v 2>&1'); + if (!$version) { + return [ + (new ConfigureCheckHelper()) + ->setErrorMessage('Fail to retrieve pdfinfo version') + ->setResource('pdfinfo') + ->setTip("The command executed by PHP haven't any output."), + ]; + } + $version = preg_match('/pdfinfo version (?.*)/', $version, $matches); + if (!$version) { + return [ + (new ConfigureCheckHelper()) + ->setErrorMessage('Fail to retrieve pdfinfo version') + ->setResource('pdfinfo') + ->setTip("This is a poppler-utils dependency and wasn't possible to parse the output of command pdfinfo -v"), + ]; + } + return [(new ConfigureCheckHelper()) + ->setSuccessMessage('pdfinfo version: ' . $matches['version']) + ->setResource('pdfinfo') + ]; + } + /** * Check all requirements to use JSignPdf * diff --git a/lib/Service/PdfParserService.php b/lib/Service/PdfParserService.php index 9915172425..7a925e8856 100644 --- a/lib/Service/PdfParserService.php +++ b/lib/Service/PdfParserService.php @@ -41,14 +41,19 @@ public function setFile(File|string $file): self { return $this; } + private function getContent(): string { + if (!$this->content) { + throw new LibresignException('File not defined to be parsed.'); + } + return $this->content; + } + private function getDocument(): Document { if (!$this->document) { - if (!$this->content) { - throw new LibresignException('File not defined to be parsed.'); - } + $content = $this->getContent(); try { $parser = new \Smalot\PdfParser\Parser(); - $this->document = $parser->parseContent($this->content); + $this->document = $parser->parseContent($content); return $this->document; } catch (\Throwable $th) { if ($th->getMessage() === 'Secured pdf file are currently not supported.') { @@ -67,6 +72,13 @@ private function getDocument(): Document { * @psalm-return array{p: int, d?: non-empty-list} */ public function getPageDimensions(): array { + if ($return = $this->getPageDimensionsWithPdfInfo()) { + return $return; + } + return $this->getPageDimensionsWithSmalotPdfParser(); + } + + private function getPageDimensionsWithSmalotPdfParser(): array { $document = $this->getDocument(); $pages = $document->getPages(); $output = [ @@ -78,15 +90,49 @@ public function getPageDimensions(): array { $pages = $document->getObjectsByType('Pages'); $details = reset($pages)->getHeader()->getDetails(); } - $widthAndHeight = [ - 'w' => $details['MediaBox'][2], - 'h' => $details['MediaBox'][3] - ]; - if (!is_numeric($widthAndHeight['w']) || !is_numeric($widthAndHeight['h'])) { + if (!isset($details['MediaBox']) || !is_numeric($details['MediaBox'][2]) || !is_numeric($details['MediaBox'][3])) { $this->logger->error('Impossible get metadata from this file: Error to get page width and height. If possible, open an issue at github.com/libresign/libresign with the file that you used.'); throw new LibresignException('Impossible get metadata from this file.'); } - $output['d'][] = $widthAndHeight; + $output['d'][] = [ + 'w' => $details['MediaBox'][2], + 'h' => $details['MediaBox'][3], + ]; + } + $pending = $output['p'] - count($output['d']); + if ($pending) { + for ($i = 0; $i < $pending; $i++) { + $output['d'][] = $output['d'][0]; + } + } + return $output; + } + + private function getPageDimensionsWithPdfInfo(): array { + if (shell_exec('which pdfinfo') === null) { + return []; + } + $content = $this->getContent(); + $filename = $this->tempManager->getTemporaryFile('.pdf'); + file_put_contents($filename, $content); + + // The output of this command go to STDERR and shell_exec get the STDOUT + // With 2>&1 the STRERR is redirected to STDOUT + $pdfinfo = shell_exec('pdfinfo ' . $filename . ' -l -1 2>&1'); + if (!$pdfinfo) { + return []; + } + if (!preg_match_all('/Page +\d+ +size: +(\d+\.?\d*) x (\d+\.?\d*)/', $pdfinfo, $pages)) { + return []; + } + $output = [ + 'p' => count($pages[1]), + ]; + foreach ($pages[1] as $page => $width) { + $output['d'][] = [ + 'w' => (float)$width, + 'h' => (float)$pages[2][$page], + ]; } return $output; } diff --git a/tests/Unit/Service/PdfParseServiceTest.php b/tests/Unit/Service/PdfParseServiceTest.php index 2d9cfbcdb8..631d3508fa 100644 --- a/tests/Unit/Service/PdfParseServiceTest.php +++ b/tests/Unit/Service/PdfParseServiceTest.php @@ -6,6 +6,18 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +namespace OCA\Libresign\Service; + +/** + * Overwrite shell_exec in the OCA\Libresign\Service namespace. + */ +function shell_exec($command) { + if (\OCA\Libresign\Tests\Unit\Service\PdfParseServiceTest::$disablePdfInfo) { + return null; + } + return \shell_exec($command); +} + namespace OCA\Libresign\Tests\Unit\Service; use OCA\Libresign\Exception\LibresignException; @@ -21,6 +33,7 @@ final class PdfParseServiceTest extends \OCA\Libresign\Tests\Unit\TestCase { private ITempManager $tempManager; private LoggerInterface&MockObject $loggerInterface; + public static $disablePdfInfo = false; public function setUp(): void { parent::setUp(); @@ -62,7 +75,8 @@ public static function dataGetMetadataWithFail(): array { /** * @dataProvider providerGetMetadataWithSuccess */ - public function testGetMetadataWithSuccess(string $path, array $expected): void { + public function testGetMetadataWithSuccess(bool $disablePdfInfo, string $path, array $expected): void { + self::$disablePdfInfo = $disablePdfInfo; /** @var File|MockObject */ $file = $this->createMock(File::class); $file->method('getContent') @@ -76,6 +90,7 @@ public function testGetMetadataWithSuccess(string $path, array $expected): void public static function providerGetMetadataWithSuccess(): array { return [ [ + 'disablePdfInfo' => true, 'tests/fixtures/small_valid.pdf', [ 'p' => 1, @@ -85,6 +100,7 @@ public static function providerGetMetadataWithSuccess(): array { ] ], [ + 'disablePdfInfo' => true, 'tests/fixtures/small_valid-signed.pdf', [ 'p' => 1, @@ -93,6 +109,26 @@ public static function providerGetMetadataWithSuccess(): array { ], ] ], + [ + 'disablePdfInfo' => false, + 'tests/fixtures/small_valid.pdf', + [ + 'p' => 1, + 'd' => [ + ['w' => 595.276, 'h' => 841.89], + ], + ] + ], + [ + 'disablePdfInfo' => false, + 'tests/fixtures/small_valid-signed.pdf', + [ + 'p' => 1, + 'd' => [ + ['w' => 595.276, 'h' => 841.89], + ], + ] + ], ]; } }