Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
GatewayPayload
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
26
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
 validateOptions
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
12
 validateExpectedCount
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 validateTemplateOptions
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
8
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\Service\Notification;
10
11/** Validate structured gateway options before both immediate delivery and queue persistence. */
12final readonly class GatewayPayload implements NotificationPayload {
13    private const MAX_PARAMETERS = 32;
14    private const MAX_PARAMETER_BYTES = 4096;
15
16    public function __construct(
17        public string $gatewayName,
18        public string $body,
19        /** @var array<string, mixed> Transport options; never authentication credentials. */
20        public array $extra = [],
21    ) {
22        $this->validateOptions($extra);
23    }
24
25    /** @param array<string, mixed> $extra */
26    private function validateOptions(array $extra): void {
27        if ($extra === []) {
28            return;
29        }
30
31        $allowed = ['body_parameters', 'expected_body_parameter_count', 'template_name', 'template_language', 'template_category'];
32        if (array_diff(array_keys($extra), $allowed) !== []) {
33            throw new \InvalidArgumentException('Unsupported gateway option.');
34        }
35        $values = $extra['body_parameters'] ?? null;
36        if (!is_array($values) || !array_is_list($values) || $values === [] || count($values) > self::MAX_PARAMETERS) {
37            throw new \InvalidArgumentException('Invalid ordered gateway body parameters.');
38        }
39        foreach ($values as $value) {
40            if (!is_string($value) || trim($value) === '' || !mb_check_encoding($value, 'UTF-8')
41                || strlen($value) > self::MAX_PARAMETER_BYTES) {
42                throw new \InvalidArgumentException('Invalid gateway body parameter.');
43            }
44        }
45        $this->validateExpectedCount($extra, count($values));
46        $this->validateTemplateOptions($extra);
47    }
48
49    /** @param array<string, mixed> $extra */
50    private function validateExpectedCount(array $extra, int $count): void {
51        if (!array_key_exists('expected_body_parameter_count', $extra)) {
52            return;
53        }
54        $expected = $extra['expected_body_parameter_count'];
55        if (!is_int($expected) || $expected < 1 || $expected !== $count) {
56            throw new \InvalidArgumentException('Invalid gateway body parameter count.');
57        }
58    }
59
60    /** @param array<string, mixed> $extra */
61    private function validateTemplateOptions(array $extra): void {
62        foreach (['template_name', 'template_language', 'template_category'] as $key) {
63            if (!array_key_exists($key, $extra)) {
64                continue;
65            }
66            $value = $extra[$key];
67            if (!is_string($value) || trim($value) === '' || strlen($value) > 256
68                || !mb_check_encoding($value, 'UTF-8') || preg_match('/[\x00-\x1F\x7F]/', $value) === 1) {
69                throw new \InvalidArgumentException('Invalid gateway template option.');
70            }
71        }
72    }
73}