Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
9 / 10
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PdfParser
90.00% covered (success)
90.00%
9 / 10
66.67% covered (warning)
66.67%
2 / 3
5.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 throwIfEncryptedPdf
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3declare(strict_types=1);
4
5/**
6 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
7 * SPDX-License-Identifier: AGPL-3.0-or-later
8 */
9
10namespace OCA\Libresign\Service\File\Pdf;
11
12use OCA\Libresign\Exception\LibresignException;
13use OCA\Libresign\Vendor\Smalot\PdfParser\Document;
14use OCA\Libresign\Vendor\Smalot\PdfParser\Parser;
15use OCP\IL10N;
16use Psr\Log\LoggerInterface;
17
18class PdfParser {
19    /**
20     * Semantic error code for a file that could not be parsed as a valid PDF document.
21     */
22    public const int CODE_INVALID_PDF = 4220;
23
24    public function __construct(
25        private LoggerInterface $logger,
26        private IL10N $l10n,
27    ) {
28    }
29
30    /**
31     * Parse PDF content and return Document object
32     *
33     * @param string $content PDF content
34     * @param string $fileName File name for error messages
35     * @return Document
36     * @throws LibresignException
37     */
38    public function parse(string $content, string $fileName): Document {
39        try {
40            $parser = new Parser();
41            return $parser->parseContent($content);
42        } catch (\Throwable $th) {
43            $this->throwIfEncryptedPdf($th, $fileName);
44
45            $this->logger->error('PDF parsing failed: ' . $th->getMessage());
46
47            // TRANSLATORS %s is the file name that could not be processed as PDF
48            throw new LibresignException($this->l10n->t('Unable to process the file as a PDF document: %s', [$fileName]), self::CODE_INVALID_PDF);
49        }
50    }
51
52    /**
53     * @throws LibresignException Only if the PDF is encrypted/password-protected
54     */
55    private function throwIfEncryptedPdf(\Throwable $th, string $fileName): void {
56        if ($th->getMessage() !== 'Secured pdf file are currently not supported.') {
57            return;
58        }
59
60        // TRANSLATORS %s is the name of the password-protected PDF file
61        throw new LibresignException($this->l10n->t('The file "%s" is password-protected. Please remove the protection and try again.', [$fileName]));
62    }
63}