diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:30:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:30:08 +0000 |
commit | 4ce65d59ca91871cfd126497158200a818720bce (patch) | |
tree | e277def01fc7eba7dbc21c4a4ae5576e8aa2cf1f /vendor/ipl/validator | |
parent | Initial commit. (diff) | |
download | icinga-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 '')
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; + } +} |