Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
SigningErrorHandler
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
7 / 7
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handleException
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 handleLibresignException
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 handleGenericException
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 isKnownError
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 translateKnownError
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 formatUnknownError
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Handler;
10
11use OCA\Libresign\Exception\LibresignException;
12use OCA\Libresign\Helper\JSActions;
13use OCP\IL10N;
14use OCP\IRequest;
15use Psr\Log\LoggerInterface;
16
17class SigningErrorHandler {
18    public function __construct(
19        private IL10N $l10n,
20        private IRequest $request,
21        private LoggerInterface $logger,
22    ) {
23    }
24
25    /**
26     * @return array{action: int, errors: list<array{message: string, code?: int, title?: string}>}
27     */
28    public function handleException(\Throwable $exception): array {
29        if ($exception instanceof LibresignException) {
30            return $this->handleLibresignException($exception);
31        }
32
33        return $this->handleGenericException($exception);
34    }
35
36    /**
37     * @return array{action: int, errors: list<array{message: string, code: int}>}
38     */
39    private function handleLibresignException(LibresignException $exception): array {
40        $code = $exception->getCode();
41        $action = $code === 400
42            ? JSActions::ACTION_CREATE_SIGNATURE_PASSWORD
43            : JSActions::ACTION_DO_NOTHING;
44
45        return [
46            'action' => $action,
47            'errors' => [[
48                'message' => $exception->getMessage(),
49                'code' => $code,
50            ]],
51        ];
52    }
53
54    /**
55     * @return array{action: int, errors: list<array{message: string, code?: int, title?: string}>}
56     */
57    private function handleGenericException(\Throwable $exception): array {
58        $message = $exception->getMessage();
59
60        return [
61            'action' => JSActions::ACTION_DO_NOTHING,
62            'errors' => $this->isKnownError($message)
63                ? [['message' => $this->translateKnownError($message)]]
64                : $this->formatUnknownError($message, $exception),
65        ];
66    }
67
68    private function isKnownError(string $message): bool {
69        return in_array($message, [
70            'Host violates local access rules.',
71            'Certificate Password Invalid.',
72            'Certificate Password is Empty.',
73        ], true);
74    }
75
76    private function translateKnownError(string $message): string {
77        return match ($message) {
78            // TRANSLATORS: Error returned when the signing service rejects a request to a local/private host for security reasons.
79            'Host violates local access rules.' => $this->l10n->t('Host violates local access rules.'),
80            // TRANSLATORS: Error shown when the password provided to unlock the signing certificate is incorrect.
81            'Certificate Password Invalid.' => $this->l10n->t('Certificate Password Invalid.'),
82            // TRANSLATORS: Error shown when no password was provided to unlock the signing certificate.
83            'Certificate Password is Empty.' => $this->l10n->t('Certificate Password is Empty.'),
84            default => $message,
85        };
86    }
87
88    /**
89     * @return list<array{message: string, title: string}>
90     */
91    private function formatUnknownError(string $message, \Throwable $exception): array {
92        $this->logger->error($message, ['exception' => $exception]);
93
94        return [[
95            // TRANSLATORS: Multi-line error shown when an unexpected server error happens during signing. %1$s is the client IP address, %2$s is the request ID, and %3$s is the technical error message. Keep the Markdown formatting and line breaks.
96            'message' => $this->l10n->t(
97                "The server was unable to complete your request.\n"
98                . "If this happens again, please send the technical details below to the server administrator.\n"
99                . "## Technical details:\n"
100                . "**Remote Address**: %1\$s\n"
101                . "**Request ID**: %2\$s\n"
102                . '**Message**: %3$s',
103                [
104                    $this->request->getRemoteAddress(),
105                    $this->request->getId(),
106                    $message,
107                ],
108            ),
109            // TRANSLATORS Dialog title shown to the signer when applying the digital signature fails with an unexpected server error.
110            'title' => $this->l10n->t('Internal Server Error'),
111        ]];
112    }
113}