summaryrefslogtreecommitdiffstats
path: root/vendor/ipl/validator
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:30:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:30:08 +0000
commit4ce65d59ca91871cfd126497158200a818720bce (patch)
treee277def01fc7eba7dbc21c4a4ae5576e8aa2cf1f /vendor/ipl/validator
parentInitial commit. (diff)
downloadicinga-php-library-4ce65d59ca91871cfd126497158200a818720bce.tar.xz
icinga-php-library-4ce65d59ca91871cfd126497158200a818720bce.zip
Adding upstream version 0.13.1.upstream/0.13.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vendor/ipl/validator/LICENSE21
-rw-r--r--vendor/ipl/validator/composer.json28
-rw-r--r--vendor/ipl/validator/src/BaseValidator.php11
-rw-r--r--vendor/ipl/validator/src/BetweenValidator.php159
-rw-r--r--vendor/ipl/validator/src/CallbackValidator.php45
-rw-r--r--vendor/ipl/validator/src/CidrValidator.php60
-rw-r--r--vendor/ipl/validator/src/DateTimeValidator.php65
-rw-r--r--vendor/ipl/validator/src/DeferredInArrayValidator.php55
-rw-r--r--vendor/ipl/validator/src/EmailAddressValidator.php341
-rw-r--r--vendor/ipl/validator/src/FileValidator.php248
-rw-r--r--vendor/ipl/validator/src/GreaterThanValidator.php69
-rw-r--r--vendor/ipl/validator/src/HexColorValidator.php37
-rw-r--r--vendor/ipl/validator/src/HostnameValidator.php37
-rw-r--r--vendor/ipl/validator/src/InArrayValidator.php128
-rw-r--r--vendor/ipl/validator/src/LessThanValidator.php69
-rw-r--r--vendor/ipl/validator/src/PrivateKeyValidator.php33
-rw-r--r--vendor/ipl/validator/src/StringLengthValidator.php179
-rw-r--r--vendor/ipl/validator/src/ValidatorChain.php284
-rw-r--r--vendor/ipl/validator/src/X509CertValidator.php33
19 files changed, 1902 insertions, 0 deletions
diff --git a/vendor/ipl/validator/LICENSE b/vendor/ipl/validator/LICENSE
new file mode 100644
index 0000000..b247ccf
--- /dev/null
+++ b/vendor/ipl/validator/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 Icinga GmbH https://www.icinga.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/ipl/validator/composer.json b/vendor/ipl/validator/composer.json
new file mode 100644
index 0000000..51ba68d
--- /dev/null
+++ b/vendor/ipl/validator/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "ipl/validator",
+ "type": "library",
+ "description": "Icinga PHP Library - Common validators and validator chaining",
+ "homepage": "https://github.com/Icinga/ipl-validator",
+ "license": "MIT",
+ "require": {
+ "php": ">=7.2",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "ipl/stdlib": ">=0.12.0",
+ "ipl/i18n": ">=0.2.0",
+ "psr/http-message": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "ipl\\Validator\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "ipl\\Tests\\Validator\\": "tests"
+ }
+ },
+ "require-dev": {
+ "guzzlehttp/psr7": "^1"
+ }
+}
diff --git a/vendor/ipl/validator/src/BaseValidator.php b/vendor/ipl/validator/src/BaseValidator.php
new file mode 100644
index 0000000..8faa79b
--- /dev/null
+++ b/vendor/ipl/validator/src/BaseValidator.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\Stdlib\Contract\Validator;
+use ipl\Stdlib\Messages;
+
+abstract class BaseValidator implements Validator
+{
+ use Messages;
+}
diff --git a/vendor/ipl/validator/src/BetweenValidator.php b/vendor/ipl/validator/src/BetweenValidator.php
new file mode 100644
index 0000000..3d7faaf
--- /dev/null
+++ b/vendor/ipl/validator/src/BetweenValidator.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace ipl\Validator;
+
+use Exception;
+use ipl\I18n\Translation;
+
+/**
+ * Validates whether value is between the given min and max
+ */
+class BetweenValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var mixed Min value */
+ protected $min;
+
+ /** @var mixed Max value */
+ protected $max;
+
+ /**
+ * Whether to do inclusive comparisons, allowing equivalence to min and/or max
+ *
+ * If false, then strict comparisons are done, and the value may equal neither
+ * the min nor max options
+ *
+ * @var boolean
+ */
+ protected $inclusive;
+
+ /**
+ * Create a new BetweenValidator
+ *
+ * Required options:
+ *
+ * - min: (scalar) Minimum border
+ * - max: (scalar) Maximum border
+ *
+ * Optional options:
+ *
+ * - inclusive: (bool) Whether inclusive border values, default true
+ *
+ * @param array $options
+ *
+ * @throws Exception When required option is missing
+ */
+ public function __construct(array $options)
+ {
+ if (! isset($options['min'], $options['max'])) {
+ throw new Exception("Missing option. 'min' and 'max' has to be given");
+ }
+
+ $this->setMin($options['min'])
+ ->setMax($options['max'])
+ ->setInclusive($options['inclusive'] ?? true);
+ }
+
+ /**
+ * Return the min option
+ *
+ * @return mixed
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Set the min option
+ *
+ * @param mixed $min
+ *
+ * @return $this
+ */
+ public function setMin($min): self
+ {
+ $this->min = $min;
+
+ return $this;
+ }
+
+ /**
+ * Return the max option
+ *
+ * @return mixed
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Set the max option
+ *
+ * @param mixed $max
+ *
+ * @return $this
+ */
+ public function setMax($max): self
+ {
+ $this->max = $max;
+
+ return $this;
+ }
+
+ /**
+ * Return the inclusive option
+ *
+ * @return bool
+ */
+ public function getInclusive(): bool
+ {
+ return $this->inclusive;
+ }
+
+ /**
+ * Set the inclusive option
+ *
+ * @param bool $inclusive
+ *
+ * @return $this
+ */
+ public function setInclusive($inclusive = true): self
+ {
+ $this->inclusive = (bool) $inclusive;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if ($this->getInclusive()) {
+ if ($this->getMin() > $value || $value > $this->getMax()) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' is not between '%s' and '%s', inclusively"),
+ $value,
+ $this->getMin(),
+ $this->getMax()
+ ));
+
+ return false;
+ }
+ } elseif ($this->getMin() >= $value || $value >= $this->getMax()) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' is not between '%s' and '%s'"),
+ $value,
+ $this->getMin(),
+ $this->getMax()
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/CallbackValidator.php b/vendor/ipl/validator/src/CallbackValidator.php
new file mode 100644
index 0000000..611a45e
--- /dev/null
+++ b/vendor/ipl/validator/src/CallbackValidator.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace ipl\Validator;
+
+/**
+ * Validator that uses a callback for the actual validation
+ *
+ * # Example Usage
+ * ```
+ * $dedup = new CallbackValidator(function ($value, CallbackValidator $validator) {
+ * if (already_exists_in_database($value)) {
+ * $validator->addMessage('Record already exists in database');
+ *
+ * return false;
+ * }
+ *
+ * return true;
+ * });
+ *
+ * $dedup->isValid($id);
+ * ```
+ */
+class CallbackValidator extends BaseValidator
+{
+ /** @var callable Validation callback */
+ protected $callback;
+
+ /**
+ * Create a new callback validator
+ *
+ * @param callable $callback Validation callback
+ */
+ public function __construct(callable $callback)
+ {
+ $this->callback = $callback;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ return call_user_func($this->callback, $value, $this);
+ }
+}
diff --git a/vendor/ipl/validator/src/CidrValidator.php b/vendor/ipl/validator/src/CidrValidator.php
new file mode 100644
index 0000000..32c1162
--- /dev/null
+++ b/vendor/ipl/validator/src/CidrValidator.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+use ipl\Stdlib\Str;
+
+/**
+ * Validate a classless inter-domain routing (CIDR)
+ */
+class CidrValidator extends BaseValidator
+{
+ use Translation;
+
+ public function isValid($value): bool
+ {
+ $this->clearMessages();
+
+ $pieces = Str::trimSplit($value, '/');
+ if (count($pieces) !== 2) {
+ $this->addMessage(sprintf(
+ $this->translate('CIDR "%s" does not conform to the required format $address/$prefix'),
+ $value
+ ));
+
+ return false;
+ }
+
+ list($address, $prefix) = $pieces;
+ $inaddr = @inet_pton($address);
+ if ($inaddr === false) {
+ $this->addMessage(sprintf($this->translate('CIDR "%s" contains an invalid address'), $value));
+
+ return false;
+ }
+
+ if (! is_numeric($prefix)) {
+ $this->addMessage(sprintf($this->translate('Prefix of CIDR "%s" must be a number'), $value));
+
+ return false;
+ }
+
+ $isIPv6 = isset($inaddr[4]);
+ $prefix = (int) $prefix;
+ $maxPrefixLength = $isIPv6 ? 128 : 32;
+
+ if ($prefix < 0 || $prefix > $maxPrefixLength) {
+ $this->addMessage(sprintf(
+ $this->translate('Prefix length of CIDR "%s" must be between 0 and %d for IPv%d addresses'),
+ $value,
+ $maxPrefixLength,
+ $isIPv6 ? 6 : 4
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/DateTimeValidator.php b/vendor/ipl/validator/src/DateTimeValidator.php
new file mode 100644
index 0000000..1e35d61
--- /dev/null
+++ b/vendor/ipl/validator/src/DateTimeValidator.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace ipl\Validator;
+
+use DateTime;
+use ipl\I18n\Translation;
+
+/**
+ * Validator for date-and-time input controls
+ */
+class DateTimeValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var string Default date time format */
+ const FORMAT = 'Y-m-d\TH:i:s';
+
+ /** @var bool Whether to use the default date time format */
+ protected $local;
+
+ /**
+ * Create a new date-and-time input control validator
+ *
+ * @param bool $local
+ */
+ public function __construct($local = true)
+ {
+ $this->local = (bool) $local;
+ }
+
+ /**
+ * Check whether the given date time is valid
+ *
+ * @param string|DateTime $value
+ *
+ * @return bool
+ */
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if (! $value instanceof DateTime && ! is_string($value)) {
+ $this->addMessage($this->translate('Invalid date/time given.'));
+
+ return false;
+ }
+
+ if (! $value instanceof DateTime) {
+ $format = $this->local === true ? static::FORMAT : DateTime::RFC3339;
+ $dateTime = DateTime::createFromFormat($format, $value);
+
+ if ($dateTime === false || $dateTime->format($format) !== $value) {
+ $this->addMessage(sprintf(
+ $this->translate("Date/time string not in the expected format: %s"),
+ $format
+ ));
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/DeferredInArrayValidator.php b/vendor/ipl/validator/src/DeferredInArrayValidator.php
new file mode 100644
index 0000000..55b9b83
--- /dev/null
+++ b/vendor/ipl/validator/src/DeferredInArrayValidator.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace ipl\Validator;
+
+/**
+ * Validates whether the value exists in the haystack created by the callback
+ */
+class DeferredInArrayValidator extends InArrayValidator
+{
+ /** @var callable Callback to create the haystack array */
+ protected $callback;
+
+ /**
+ * Create a new deferredInArray validator
+ *
+ * **Required parameter:**
+ *
+ * - `callback`: (`callable`) The callback to create haystack
+ *
+ * **Optional parameter:**
+ *
+ * *options: (`array`) Following option can be defined:*
+ *
+ * * `strict`: (`bool`) Whether the types of the needle in the haystack should also match, default `false`
+ *
+ * @param callable $callback Validation callback
+ * @param array $options
+ */
+ public function __construct(callable $callback, array $options = [])
+ {
+ $this->callback = $callback;
+
+ parent::__construct($options);
+ }
+
+ public function getHaystack(): array
+ {
+ return $this->haystack ?? call_user_func($this->callback);
+ }
+
+ /**
+ * Set the callback
+ *
+ * @param callable $callback
+ *
+ * @return $this
+ */
+ public function setCallback(callable $callback): self
+ {
+ $this->haystack = null;
+ $this->callback = $callback;
+
+ return $this;
+ }
+}
diff --git a/vendor/ipl/validator/src/EmailAddressValidator.php b/vendor/ipl/validator/src/EmailAddressValidator.php
new file mode 100644
index 0000000..52c3697
--- /dev/null
+++ b/vendor/ipl/validator/src/EmailAddressValidator.php
@@ -0,0 +1,341 @@
+<?php
+
+namespace ipl\Validator;
+
+use Exception;
+use ipl\I18n\Translation;
+
+/**
+ * Validates an email address
+ *
+ * Email Address syntax: (<local part>@<domain-literal part>)
+ *
+ * We currently do not support dot-atom syntax (refer RFC 2822 [https://www.ietf.org/rfc/rfc2822.txt]
+ * documentation for more details) for domain-literal part of an email address
+ *
+ */
+class EmailAddressValidator extends BaseValidator
+{
+ use Translation;
+
+ /**
+ * If MX check should be enabled
+ *
+ * @var bool
+ */
+ protected $mx = false;
+
+ /**
+ * If a deep MX check should be enabled
+ *
+ * @var bool
+ */
+ protected $deep = false;
+
+ /**
+ * Create a new E-mail address validator with optional options
+ *
+ * Optional options:
+ *
+ * 'mx' => If an MX check should be enabled, boolean
+ * 'deep' => If a deep MX check should be enabled, boolean
+ *
+ * @param array $options
+ *
+ * @throws Exception
+ */
+ public function __construct(array $options = [])
+ {
+ if (array_key_exists('mx', $options)) {
+ $this->setEnableMxCheck($options['mx']);
+ }
+
+ if (array_key_exists('deep', $options)) {
+ $this->setEnableDeepMxCheck($options['deep']);
+ }
+ }
+
+ /**
+ * Set MX check
+ *
+ * To validate if the hostname is a DNS mail exchange (MX) record set it to true
+ *
+ * @param bool $mx if MX check should be enabled
+ *
+ * @return $this
+ */
+ public function setEnableMxCheck(bool $mx = true): self
+ {
+ $this->mx = $mx;
+
+ return $this;
+ }
+
+ /**
+ * Set Deep MX check
+ *
+ * To validate if the hostname is a DNS mail exchange (MX) record, and it points to an A record (for IPv4) or
+ * an AAAA / A6 record (for IPv6) set it to true
+ *
+ * @param bool $deep if deep MX check should be enabled
+ *
+ * @return $this
+ *
+ * @throws Exception in case MX check has not been enabled
+ */
+ public function setEnableDeepMxCheck(bool $deep = true): self
+ {
+ if (! $this->mx) {
+ throw new Exception("MX record check has to be enabled to enable deep MX record check");
+ }
+
+ $this->deep = $deep;
+
+ return $this;
+ }
+
+ /**
+ * Validate the local part (username / the part before '@') of the email address
+ *
+ * @param string $localPart
+ * @param string $email
+ *
+ * @return bool
+ */
+ private function validateLocalPart(string $localPart, string $email): bool
+ {
+ // First try to match the local part on the common dot-atom format
+ $result = false;
+
+ // Dot-atom characters are: 1*atext *("." 1*atext)
+ // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*",
+ // "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~"
+ $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e';
+ if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $localPart)) {
+ $result = true;
+ } else {
+ // Try quoted string format (RFC 5321 Chapter 4.1.2)
+
+ // Quoted-string characters are: DQUOTE *(qtext/quoted-pair) DQUOTE
+ $qtext = '\x20-\x21\x23-\x5b\x5d-\x7e'; // %d32-33 / %d35-91 / %d93-126
+ $quotedPair = '\x20-\x7e'; // %d92 %d32-126
+ if (preg_match('/^"([' . $qtext . ']|\x5c[' . $quotedPair . '])*"$/', $localPart)) {
+ $result = true;
+ } else {
+ $this->addMessage(sprintf(
+ $this->translate(
+ "'%s' can not be matched against dot-atom format or quoted-string format"
+ ),
+ $localPart
+ ));
+ $this->addMessage(sprintf(
+ $this->translate("Hence '%s' is not a valid local part for email address '%s'"),
+ $localPart,
+ $email
+ ));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validate the hostname part of the email address
+ *
+ * @param string $hostname
+ * @param string $email
+ *
+ * @return bool
+ */
+ private function validateHostnamePart(string $hostname, string $email): bool
+ {
+ $hostValidator = new HostnameValidator();
+
+ if ($this->validateIp($hostname)) {
+ return true;
+ }
+
+ if (preg_match('/^\[([^\]]*)\]$/i', $hostname, $matches)) {
+ $validHostname = $matches[1];
+ if (! $this->validateIp($validHostname)) {
+ $this->addMessage(sprintf(
+ $this->translate("host name %s is a domain literal and is invalid"),
+ $hostname
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ if (! $hostValidator->isValid($hostname)) {
+ $this->addMessage(sprintf(
+ $this->translate('%s is not a valid domain name for email address %s.'),
+ $hostname,
+ $email
+ ));
+
+ return false;
+ } elseif ($this->mx) {
+ // MX check on hostname
+ return $this->validateMXRecords($hostname, $email);
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the given IP address is valid
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ private function validateIp(string $value): bool
+ {
+ if (! filter_var($value, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if and only if $value is a valid email address
+ * according to RFC2822
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ $this->clearMessages();
+
+ $matches = [];
+ $length = true;
+
+ // Split email address up and disallow '..'
+ if (
+ (strpos($value, '..') !== false)
+ || (! preg_match('/^(.+)@([^@]+)$/', $value, $matches))
+ ) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' is not a valid email address in the basic format local-part@hostname"),
+ $value
+ ));
+ return false;
+ }
+
+ $localPart = $matches[1];
+ $hostname = $matches[2];
+
+ if ((strlen($localPart) > 64) || (strlen($hostname) > 255)) {
+ $length = false;
+ $this->addMessage(sprintf(
+ $this->translate("'%s' exceeds the allowed length"),
+ $value
+ ));
+ }
+
+ $local = $this->validateLocalPart($localPart, $value);
+
+ // If both parts valid, return true
+ if (($local && $this->validateHostnamePart($hostname, $value)) && $length) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Perform deep MX record validation
+ *
+ * Check if the hostname is a valid DNS mail exchange (MX) record in case deep MX record check is enabled,
+ * also checks if the corresponding MX record points to an A record (for IPv4) or an AAAA / A6 record (for IPv6)
+ *
+ * @param string $hostname
+ * @param string $email
+ *
+ * @return bool
+ */
+ private function validateMXRecords(string $hostname, string $email): bool
+ {
+ $mxHosts = [];
+ //decode IDN domain name
+ $decodedHostname = idn_to_ascii($hostname, 0, INTL_IDNA_VARIANT_UTS46);
+
+ $result = getmxrr($decodedHostname, $mxHosts);
+ if (! $result) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' does not appear to have a valid MX record for the email address '%s'"),
+ $hostname,
+ $email
+ ));
+ } elseif ($this->deep) {
+ $validAddress = false;
+ $reserved = true;
+ foreach ($mxHosts as $decodedHostname) {
+ $res = $this->isReserved($decodedHostname);
+ if (! $res) {
+ $reserved = false;
+ }
+
+ if (
+ ! $res
+ && (
+ checkdnsrr($decodedHostname, "A")
+ || checkdnsrr($decodedHostname, "AAAA")
+ || checkdnsrr($decodedHostname, "A6")
+ )
+ ) {
+ $validAddress = true;
+ break;
+ }
+ }
+
+ if (! $validAddress) {
+ $result = false;
+ if ($reserved) {
+ $this->addMessage(sprintf(
+ $this->translate(
+ "'%s' is not in a routable network segment." .
+ " The email address '%s' should not be resolved from public network"
+ ),
+ $hostname,
+ $email
+ ));
+ } else {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' does not appear to have a valid MX record for the email address '%s'"),
+ $hostname,
+ $email
+ ));
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validate whether the given host is reserved
+ *
+ * @param string $host host name or ip address
+ *
+ * @return bool
+ */
+ private function isReserved(string $host): bool
+ {
+ if (! preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) {
+ $host = gethostbyname($host);
+ }
+
+ if (! filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/ipl/validator/src/FileValidator.php b/vendor/ipl/validator/src/FileValidator.php
new file mode 100644
index 0000000..8c5b90e
--- /dev/null
+++ b/vendor/ipl/validator/src/FileValidator.php
@@ -0,0 +1,248 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+use ipl\Stdlib\Str;
+use LogicException;
+use Psr\Http\Message\UploadedFileInterface;
+
+/**
+ * Validates an uploaded file
+ */
+class FileValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var int Minimum allowed file size */
+ protected $minSize;
+
+ /** @var ?int Maximum allowed file size */
+ protected $maxSize;
+
+ /** @var ?string[] Allowed mime types */
+ protected $allowedMimeTypes;
+
+ /** @var ?int Maximum allowed file name length */
+ protected $maxFileNameLength;
+
+ /**
+ * Create a new FileValidator
+ *
+ * Optional options:
+ * - minSize: (int) Minimum allowed file size, by default 0
+ * - maxSize: (int) Maximum allowed file size, by default no limit
+ * - maxFileNameLength: (int) Maximum allowed file name length, by default no limit
+ * - mimeType: (array) Allowed mime types, by default no restriction
+ */
+ public function __construct(array $options = [])
+ {
+ $this
+ ->setMinSize($options['minSize'] ?? 0)
+ ->setMaxSize($options['maxSize'] ?? null)
+ ->setMaxFileNameLength($options['maxFileNameLength'] ?? null)
+ ->setAllowedMimeTypes($options['mimeType'] ?? null);
+ }
+
+ /**
+ * Get the minimum allowed file size
+ *
+ * @return int
+ */
+ public function getMinSize(): int
+ {
+ return $this->minSize;
+ }
+
+ /**
+ * Set the minimum allowed file size
+ *
+ * @param int $minSize
+ *
+ * @return $this
+ */
+ public function setMinSize(int $minSize): self
+ {
+ if (($max = $this->getMaxSize()) !== null && $minSize > $max) {
+ throw new LogicException(
+ sprintf(
+ 'The minSize must be less than or equal to the maxSize, but minSize: %d and maxSize: %d given.',
+ $minSize,
+ $max
+ )
+ );
+ }
+
+ $this->minSize = $minSize;
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum allowed file size
+ *
+ * @return ?int
+ */
+ public function getMaxSize(): ?int
+ {
+ return $this->maxSize;
+ }
+
+ /**
+ * Set the maximum allowed file size
+ *
+ * @param ?int $maxSize
+ *
+ * @return $this
+ */
+ public function setMaxSize(?int $maxSize): self
+ {
+ if ($maxSize !== null && ($min = $this->getMinSize()) !== null && $maxSize < $min) {
+ throw new LogicException(
+ sprintf(
+ 'The minSize must be less than or equal to the maxSize, but minSize: %d and maxSize: %d given.',
+ $min,
+ $maxSize
+ )
+ );
+ }
+
+ $this->maxSize = $maxSize;
+
+ return $this;
+ }
+
+ /**
+ * Get the allowed file mime types
+ *
+ * @return ?string[]
+ */
+ public function getAllowedMimeTypes(): ?array
+ {
+ return $this->allowedMimeTypes;
+ }
+
+ /**
+ * Set the allowed file mime types
+ *
+ * @param ?string[] $allowedMimeTypes
+ *
+ * @return $this
+ */
+ public function setAllowedMimeTypes(?array $allowedMimeTypes): self
+ {
+ $this->allowedMimeTypes = $allowedMimeTypes;
+
+ return $this;
+ }
+
+ /**
+ * Get maximum allowed file name length
+ *
+ * @return ?int
+ */
+ public function getMaxFileNameLength(): ?int
+ {
+ return $this->maxFileNameLength;
+ }
+
+ /**
+ * Set maximum allowed file name length
+ *
+ * @param ?int $maxFileNameLength
+ *
+ * @return $this
+ */
+ public function setMaxFileNameLength(?int $maxFileNameLength): self
+ {
+ $this->maxFileNameLength = $maxFileNameLength;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if (is_array($value)) {
+ foreach ($value as $file) {
+ if (! $this->validateFile($file)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return $this->validateFile($value);
+ }
+
+
+ private function validateFile(UploadedFileInterface $file): bool
+ {
+ $isValid = true;
+ if ($this->getMaxSize() && $file->getSize() > $this->getMaxSize()) {
+ $this->addMessage(sprintf(
+ $this->translate('File %s is bigger than the allowed maximum size of %d'),
+ $file->getClientFileName(),
+ $this->getMaxSize()
+ ));
+
+ $isValid = false;
+ }
+
+ if ($this->getMinSize() && $file->getSize() < $this->getMinSize()) {
+ $this->addMessage(sprintf(
+ $this->translate('File %s is smaller than the minimum required size of %d'),
+ $file->getClientFileName(),
+ $this->getMinSize()
+ ));
+
+ $isValid = false;
+ }
+
+ if ($this->getMaxFileNameLength()) {
+ $strValidator = new StringLengthValidator(['max' => $this->getMaxFileNameLength()]);
+
+ if (! $strValidator->isValid($file->getClientFilename())) {
+ $this->addMessage(sprintf(
+ $this->translate('File name is longer than the allowed length of %d characters.'),
+ $this->maxFileNameLength
+ ));
+
+ $isValid = false;
+ }
+ }
+
+ if (! empty($this->getAllowedMimeTypes())) {
+ $hasAllowedMimeType = false;
+ foreach ($this->getAllowedMimeTypes() as $type) {
+ $fileMimetype = $file->getClientMediaType();
+ if (($pos = strpos($type, '/*')) !== false) { // image/*
+ $typePrefix = substr($type, 0, $pos);
+ if (Str::startsWith($fileMimetype, $typePrefix)) {
+ $hasAllowedMimeType = true;
+ break;
+ }
+ } elseif ($fileMimetype === $type) { // image/png
+ $hasAllowedMimeType = true;
+ break;
+ }
+ }
+
+ if (! $hasAllowedMimeType) {
+ $this->addMessage(sprintf(
+ $this->translate('File %s is of type %s. Only %s allowed.'),
+ $file->getClientFileName(),
+ $file->getClientMediaType(),
+ implode(', ', $this->allowedMimeTypes)
+ ));
+
+ $isValid = false;
+ }
+ }
+
+ return $isValid;
+ }
+}
diff --git a/vendor/ipl/validator/src/GreaterThanValidator.php b/vendor/ipl/validator/src/GreaterThanValidator.php
new file mode 100644
index 0000000..e5de3d0
--- /dev/null
+++ b/vendor/ipl/validator/src/GreaterThanValidator.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validates whether the value is greater than the given min
+ */
+class GreaterThanValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var mixed Comparison value for greater than */
+ protected $min;
+
+ /**
+ * Create a new GreaterThanValidator
+ *
+ * Optional options:
+ * - min: (scalar) Comparison value for greater than, default 0
+ */
+ public function __construct(array $options = [])
+ {
+ $this->setMin($options['min'] ?? 0);
+ }
+
+ /**
+ * Get the min option
+ *
+ * @return mixed
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Set the min option
+ *
+ * @param mixed $min
+ *
+ * @return $this
+ */
+ public function setMin($min): self
+ {
+ $this->min = $min;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if ($this->getMin() >= $value) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' is not greater than '%s'"),
+ $value,
+ $this->min
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/HexColorValidator.php b/vendor/ipl/validator/src/HexColorValidator.php
new file mode 100644
index 0000000..e2da39c
--- /dev/null
+++ b/vendor/ipl/validator/src/HexColorValidator.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validator for color input controls
+ */
+class HexColorValidator extends BaseValidator
+{
+ use Translation;
+
+ /**
+ * Check whether the given color is valid
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if (! preg_match('/\A#[0-9a-f]{6}\z/i', $value)) {
+ $this->addMessage(sprintf(
+ $this->translate('Color string not in the expected format %s'),
+ '#rrggbb'
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/HostnameValidator.php b/vendor/ipl/validator/src/HostnameValidator.php
new file mode 100644
index 0000000..3bb9b66
--- /dev/null
+++ b/vendor/ipl/validator/src/HostnameValidator.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validates Host name
+ */
+class HostnameValidator extends BaseValidator
+{
+ use Translation;
+
+ /**
+ * Validates host names against RFC 1034, RFC 1035, RFC 952, RFC 1123, RFC 2732, RFC 2181, and RFC 1123
+ *
+ * @param string $value
+ *
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->clearMessages();
+
+ $asciiHostname = idn_to_ascii($value, 0, INTL_IDNA_VARIANT_UTS46);
+ if (filter_var($asciiHostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) {
+ $this->addMessage(sprintf(
+ $this->translate("%s is not a valid host name."),
+ $value ?? ''
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/InArrayValidator.php b/vendor/ipl/validator/src/InArrayValidator.php
new file mode 100644
index 0000000..f8c18ef
--- /dev/null
+++ b/vendor/ipl/validator/src/InArrayValidator.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validate if specific single or multiple values exist in an array
+ */
+class InArrayValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var array The array */
+ protected $haystack;
+
+ /** @var bool Whether the types of the needle in the haystack should also match */
+ protected $strict = false;
+
+ /**
+ * Create a new InArray validator
+ *
+ * **Optional options:**
+ *
+ * * `haystack`: (`array`) The array
+ * * `strict`: (`bool`) Whether the types of the needle in the haystack should also match, default `false`
+ *
+ * @param array $options
+ */
+ public function __construct(array $options = [])
+ {
+ if (isset($options['haystack'])) {
+ $this->setHaystack($options['haystack']);
+ }
+
+ $this->setStrict($options['strict'] ?? false);
+ }
+
+ /**
+ * Get the haystack
+ *
+ * @return array
+ */
+ public function getHaystack(): array
+ {
+ return $this->haystack ?? [];
+ }
+
+ /**
+ * Set the haystack
+ *
+ * @param array $haystack
+ *
+ * @return $this
+ */
+ public function setHaystack(array $haystack): self
+ {
+ $this->haystack = $haystack;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the types of the needle in the haystack should also match
+ *
+ * @return bool
+ */
+ public function isStrict(): bool
+ {
+ return $this->strict;
+ }
+
+ /**
+ * Set whether the types of the needle in the haystack should also match
+ *
+ * @param bool $strict
+ *
+ * @return $this
+ */
+ public function setStrict(bool $strict = true): self
+ {
+ $this->strict = $strict;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ $notInArray = $this->findInvalid((array) $value);
+
+ if (empty($notInArray)) {
+ return true;
+ }
+
+ $this->addMessage(sprintf(
+ $this->translatePlural(
+ "%s was not found in the haystack",
+ "%s were not found in the haystack",
+ count($notInArray)
+ ),
+ implode(', ', $notInArray)
+ ));
+
+ return false;
+ }
+
+ /**
+ * Get the values from the specified array that are not present in the haystack
+ *
+ * @param array $values
+ *
+ * @return array Values not found in the haystack
+ */
+ protected function findInvalid(array $values = []): array
+ {
+ $notInArray = [];
+ foreach ($values as $val) {
+ if (! in_array($val, $this->getHaystack(), $this->isStrict())) {
+ $notInArray[] = $val;
+ }
+ }
+
+ return $notInArray;
+ }
+}
diff --git a/vendor/ipl/validator/src/LessThanValidator.php b/vendor/ipl/validator/src/LessThanValidator.php
new file mode 100644
index 0000000..68e3daf
--- /dev/null
+++ b/vendor/ipl/validator/src/LessThanValidator.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validates whether the value is less than the given max
+ */
+class LessThanValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var mixed Comparison value for less than */
+ protected $max;
+
+ /**
+ * Create a new LessThanValidator
+ *
+ * Optional options:
+ * - max: (int) Comparison value for less than, default 0
+ */
+ public function __construct(array $options = [])
+ {
+ $this->setMax($options['max'] ?? 0);
+ }
+
+ /**
+ * Get the max option
+ *
+ * @return mixed
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Set the max option
+ *
+ * @param mixed $max
+ *
+ * @return $this
+ */
+ public function setMax($max): self
+ {
+ $this->max = $max;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if ($this->getMax() <= $value) {
+ $this->addMessage(sprintf(
+ $this->translate("'%s' is not less than '%s'"),
+ $value,
+ $this->getMax()
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/PrivateKeyValidator.php b/vendor/ipl/validator/src/PrivateKeyValidator.php
new file mode 100644
index 0000000..b629398
--- /dev/null
+++ b/vendor/ipl/validator/src/PrivateKeyValidator.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validates a private key
+ */
+class PrivateKeyValidator extends BaseValidator
+{
+ use Translation;
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if (preg_match('/\A\s*\w+:/', $value)) {
+ $this->addMessage($this->translate('URLs are not allowed'));
+
+ return false;
+ }
+
+ if (openssl_pkey_get_private($value) === false) {
+ $this->addMessage($this->translate('Not a valid PEM-encoded private key'));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/StringLengthValidator.php b/vendor/ipl/validator/src/StringLengthValidator.php
new file mode 100644
index 0000000..57df1eb
--- /dev/null
+++ b/vendor/ipl/validator/src/StringLengthValidator.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace ipl\Validator;
+
+use InvalidArgumentException;
+use ipl\I18n\Translation;
+use LogicException;
+
+/**
+ * Validates string length with given options
+ */
+class StringLengthValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var mixed Minimum required length */
+ protected $min;
+
+ /** @var mixed Maximum required length */
+ protected $max;
+
+ /** @var ?string Encoding to use */
+ protected $encoding;
+
+ /**
+ * Create a new StringLengthValidator
+ *
+ * Optional options:
+ * - min: (scalar) Minimum required string length, default 0
+ * - max: (scalar) Maximum required string length, default null
+ * - encoding: (string) Encoding type, default null
+ */
+ public function __construct(array $options = [])
+ {
+ $this
+ ->setMin($options['min'] ?? 0)
+ ->setMax($options['max'] ?? null)
+ ->setEncoding($options['encoding'] ?? null);
+ }
+
+ /**
+ * Get the minimum required string length
+ *
+ * @return mixed
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Set the minimum required string length
+ *
+ * @param mixed $min
+ *
+ * @return $this
+ *
+ * @throws LogicException When the $min is greater than the $max value
+ */
+ public function setMin($min): self
+ {
+ if ($this->getMax() !== null && $min > $this->getMax()) {
+ throw new LogicException(
+ sprintf(
+ 'The min must be less than or equal to the max length, but min: %d and max: %d given.',
+ $min,
+ $this->getMax()
+ )
+ );
+ }
+
+ $this->min = $min;
+
+ return $this;
+ }
+
+ /**
+ * Get the maximum required string length
+ *
+ * @return mixed
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Set the minimum required string length
+ *
+ * @param mixed $max
+ *
+ * @return $this
+ *
+ * @throws LogicException When the $min is greater than the $max value
+ */
+ public function setMax($max): self
+ {
+ if ($max !== null && $this->getMin() > $max) {
+ throw new LogicException(
+ sprintf(
+ 'The min must be less than or equal to the max length, but min: %d and max: %d given.',
+ $this->getMin(),
+ $max
+ )
+ );
+ }
+
+ $this->max = $max;
+
+ return $this;
+ }
+
+ /**
+ * Get the encoding type to use
+ *
+ * @return ?string
+ */
+ public function getEncoding(): ?string
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set the encoding type to use
+ *
+ * @param ?string $encoding
+ *
+ * @return $this
+ */
+ public function setEncoding(?string $encoding): self
+ {
+ if ($encoding !== null) {
+ $availableEncodings = array_map('strtolower', mb_list_encodings());
+ if (! in_array(strtolower($encoding), $availableEncodings, true)) {
+ throw new InvalidArgumentException(
+ sprintf('Given encoding "%s" is not supported on this OS!', $encoding)
+ );
+ }
+ }
+
+ $this->encoding = $encoding;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if ($encoding = $this->getEncoding()) { // because encoding is only nullable in php >= 8.0
+ $length = mb_strlen($value, $encoding);
+ } else {
+ $length = mb_strlen($value);
+ }
+
+ if ($length < $this->getMin()) {
+ $this->addMessage(sprintf(
+ $this->translate('String should be %d characters long, %d given'),
+ $this->getMin(),
+ $length
+ ));
+
+ return false;
+ }
+
+ if ($this->getMax() && $this->getMax() < $length) {
+ $this->addMessage(sprintf(
+ $this->translate('String should be %d characters long, %d given'),
+ $this->getMax(),
+ $length
+ ));
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/ipl/validator/src/ValidatorChain.php b/vendor/ipl/validator/src/ValidatorChain.php
new file mode 100644
index 0000000..2860a12
--- /dev/null
+++ b/vendor/ipl/validator/src/ValidatorChain.php
@@ -0,0 +1,284 @@
+<?php
+
+namespace ipl\Validator;
+
+use Countable;
+use InvalidArgumentException;
+use ipl\Stdlib\Contract\Validator;
+use ipl\Stdlib\Messages;
+use ipl\Stdlib\Plugins;
+use ipl\Stdlib\PriorityQueue;
+use IteratorAggregate;
+use SplObjectStorage;
+use Traversable;
+use UnexpectedValueException;
+
+use function ipl\Stdlib\get_php_type;
+
+class ValidatorChain implements Countable, IteratorAggregate, Validator
+{
+ use Messages;
+ use Plugins;
+
+ /** Default priority at which validators are added */
+ const DEFAULT_PRIORITY = 1;
+
+ /** @var PriorityQueue Validator chain */
+ protected $validators;
+
+ /** @var SplObjectStorage Validators that break the chain on failure */
+ protected $validatorsThatBreakTheChain;
+
+ /**
+ * Create a new validator chain
+ */
+ public function __construct()
+ {
+ $this->validators = new PriorityQueue();
+ $this->validatorsThatBreakTheChain = new SplObjectStorage();
+
+ $this->addDefaultPluginLoader('validator', __NAMESPACE__, 'Validator');
+ }
+
+ /**
+ * Get the validators that break the chain
+ *
+ * @return SplObjectStorage
+ */
+ public function getValidatorsThatBreakTheChain()
+ {
+ return $this->validatorsThatBreakTheChain;
+ }
+
+ /**
+ * Add a validator to the chain
+ *
+ * If $breakChainOnFailure is true and the validator fails, subsequent validators won't be executed.
+ *
+ * @param Validator $validator
+ * @param bool $breakChainOnFailure
+ * @param int $priority Priority at which to add validator
+ *
+ * @return $this
+ *
+ */
+ public function add(Validator $validator, $breakChainOnFailure = false, $priority = self::DEFAULT_PRIORITY)
+ {
+ $this->validators->insert($validator, $priority);
+
+ if ($breakChainOnFailure) {
+ $this->validatorsThatBreakTheChain->attach($validator);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add the validators from the given validator specification to the chain
+ *
+ * @param iterable $validators
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If $validators is not iterable or if the validator specification is invalid
+ */
+ public function addValidators($validators)
+ {
+ if ($validators instanceof static) {
+ return $this->merge($validators);
+ }
+
+ if (! is_iterable($validators)) {
+ throw new InvalidArgumentException(sprintf(
+ '%s expects parameter one to be iterable, got %s instead',
+ __METHOD__,
+ get_php_type($validators)
+ ));
+ }
+
+ foreach ($validators as $name => $validator) {
+ $breakChainOnFailure = false;
+
+ if (! $validator instanceof Validator) {
+ if (is_int($name)) {
+ if (! is_array($validator)) {
+ $name = $validator;
+ $validator = null;
+ } else {
+ if (! isset($validator['name'])) {
+ throw new InvalidArgumentException(
+ 'Invalid validator array specification: Key "name" is missing'
+ );
+ }
+
+ $name = $validator['name'];
+ unset($validator['name']);
+ }
+ }
+
+ if (is_array($validator)) {
+ if (isset($validator['options'])) {
+ $options = $validator['options'];
+
+ unset($validator['options']);
+
+ $validator = array_merge($validator, $options);
+ }
+
+ if (isset($validator['break_chain_on_failure'])) {
+ $breakChainOnFailure = $validator['break_chain_on_failure'];
+
+ unset($validator['break_chain_on_failure']);
+ }
+ }
+
+ $validator = $this->createValidator($name, $validator);
+ }
+
+ $this->add($validator, $breakChainOnFailure);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a validator loader
+ *
+ * @param string $namespace Namespace of the validators
+ * @param string $postfix Validator name postfix, if any
+ *
+ * @return $this
+ */
+ public function addValidatorLoader($namespace, $postfix = null)
+ {
+ $this->addPluginLoader('validator', $namespace, $postfix);
+
+ return $this;
+ }
+
+ /**
+ * Remove all validators from the chain
+ *
+ * @return $this
+ */
+ public function clearValidators()
+ {
+ $this->validators = new PriorityQueue();
+ $this->validatorsThatBreakTheChain = new SplObjectStorage();
+
+ return $this;
+ }
+
+ /**
+ * Create a validator from the given name and options
+ *
+ * @param string $name
+ * @param mixed $options
+ *
+ * @return Validator
+ *
+ * @throws InvalidArgumentException If the validator to load is unknown
+ * @throws UnexpectedValueException If a validator loader did not return an instance of {@link Validator}
+ */
+ public function createValidator($name, $options = null)
+ {
+ $class = $this->loadPlugin('validator', $name);
+
+ if (! $class) {
+ throw new InvalidArgumentException(sprintf(
+ "Can't load validator '%s'. Validator unknown",
+ $name
+ ));
+ }
+
+ if (empty($options)) {
+ $validator = new $class();
+ } else {
+ $validator = new $class($options);
+ }
+
+ if (! $validator instanceof Validator) {
+ throw new UnexpectedValueException(sprintf(
+ "%s expects loader to return an instance of %s for validator '%s', got %s instead",
+ __METHOD__,
+ Validator::class,
+ $name,
+ get_php_type($validator)
+ ));
+ }
+
+ return $validator;
+ }
+
+ /**
+ * Merge all validators from the given chain into this one
+ *
+ * @param ValidatorChain $validatorChain
+ *
+ * @return $this
+ */
+ public function merge(ValidatorChain $validatorChain)
+ {
+ $validatorsThatBreakTheChain = $validatorChain->getValidatorsThatBreakTheChain();
+
+ foreach ($validatorChain->validators->yieldAll() as $priority => $validator) {
+ $this->add($validator, $validatorsThatBreakTheChain->contains($validator), $priority);
+ }
+
+ return $this;
+ }
+
+ public function __clone()
+ {
+ $this->validators = clone $this->validators;
+ }
+
+ /**
+ * Export the chain as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array_values(iterator_to_array($this));
+ }
+
+ public function count(): int
+ {
+ return count($this->validators);
+ }
+
+ /**
+ * Get an iterator for traversing the validators
+ *
+ * @return Validator[]|PriorityQueue
+ */
+ public function getIterator(): Traversable
+ {
+ // Clone validators because the PriorityQueue acts as a heap and thus items are removed upon iteration
+ return clone $this->validators;
+ }
+
+ public function isValid($value)
+ {
+ $this->clearMessages();
+
+ $valid = true;
+
+ foreach ($this as $validator) {
+ if ($validator->isValid($value)) {
+ continue;
+ }
+
+ $valid = false;
+
+ $this->addMessages($validator->getMessages());
+
+ if ($this->validatorsThatBreakTheChain->contains($validator)) {
+ break;
+ }
+ }
+
+ return $valid;
+ }
+}
diff --git a/vendor/ipl/validator/src/X509CertValidator.php b/vendor/ipl/validator/src/X509CertValidator.php
new file mode 100644
index 0000000..7dfc4f7
--- /dev/null
+++ b/vendor/ipl/validator/src/X509CertValidator.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace ipl\Validator;
+
+use ipl\I18n\Translation;
+
+/**
+ * Validates an X.509 certificate
+ */
+class X509CertValidator extends BaseValidator
+{
+ use Translation;
+
+ public function isValid($value)
+ {
+ // Multiple isValid() calls must not stack validation messages
+ $this->clearMessages();
+
+ if (preg_match('/\A\s*\w+:/', $value)) {
+ $this->addMessage($this->translate('URLs are not allowed'));
+
+ return false;
+ }
+
+ if (openssl_x509_parse($value) === false) {
+ $this->addMessage($this->translate('Not a valid PEM-encoded X.509 certificate'));
+
+ return false;
+ }
+
+ return true;
+ }
+}