From 7e6b88241f888cf64d9177f26bb3e4b1ebb1ea0e Mon Sep 17 00:00:00 2001 From: szaimen Date: Sat, 5 Mar 2022 16:59:37 +0100 Subject: [PATCH 01/10] Fix IDN domain name not being allowed The filter_var function is unfortunately not perfect and doesn't support domain with unicode as well as url with underscores. Replace usage with a regex from Symfony validator. See https://bugs.php.net/search.php?cmd=display&search_for=FILTER_VALIDATE_URL Closes #27906 Signed-off-by: Carl Schwan --- .../lib/Controller/SettingsController.php | 3 +- .../lib/Controller/ThemingController.php | 15 +++------ apps/theming/lib/ThemingDefaults.php | 3 +- lib/private/Setup.php | 5 +-- lib/public/Util.php | 32 +++++++++++++++++++ 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php index 046e6d7704188..1da0d2ecf5dbe 100644 --- a/apps/oauth2/lib/Controller/SettingsController.php +++ b/apps/oauth2/lib/Controller/SettingsController.php @@ -39,6 +39,7 @@ use OCP\IL10N; use OCP\IRequest; use OCP\Security\ISecureRandom; +use OCP\Util; class SettingsController extends Controller { /** @var ClientMapper */ @@ -68,7 +69,7 @@ public function __construct(string $appName, public function addClient(string $name, string $redirectUri): JSONResponse { - if (filter_var($redirectUri, FILTER_VALIDATE_URL) === false) { + if (!Util::isValidUrl($redirectUri)) { return new JSONResponse(['message' => $this->l->t('Your redirect URL needs to be a full URL for example: https://yourdomain.com/path')], Http::STATUS_BAD_REQUEST); } diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index a735dfafc2ce8..c1abdb61a8296 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -54,6 +54,7 @@ use OCP\IRequest; use OCP\ITempManager; use OCP\IURLGenerator; +use OCP\Util; /** * Class ThemingController @@ -143,7 +144,7 @@ public function updateStylesheet($setting, $value) { if (strlen($value) > 500) { $error = $this->l10n->t('The given web address is too long'); } - if (!$this->isValidUrl($value)) { + if (!Util::isValidUrl($value)) { $error = $this->l10n->t('The given web address is not a valid URL'); } break; @@ -151,7 +152,7 @@ public function updateStylesheet($setting, $value) { if (strlen($value) > 500) { $error = $this->l10n->t('The given legal notice address is too long'); } - if (!$this->isValidUrl($value)) { + if (!Util::isValidUrl($value)) { $error = $this->l10n->t('The given legal notice address is not a valid URL'); } break; @@ -159,7 +160,7 @@ public function updateStylesheet($setting, $value) { if (strlen($value) > 500) { $error = $this->l10n->t('The given privacy policy address is too long'); } - if (!$this->isValidUrl($value)) { + if (!Util::isValidUrl($value)) { $error = $this->l10n->t('The given privacy policy address is not a valid URL'); } break; @@ -200,14 +201,6 @@ public function updateStylesheet($setting, $value) { ); } - /** - * Check that a string is a valid http/https url - */ - private function isValidUrl(string $url): bool { - return ((strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) && - filter_var($url, FILTER_VALIDATE_URL) !== false); - } - /** * @AuthorizedAdminSetting(settings=OCA\Theming\Settings\Admin) * @return DataResponse diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index 3d7aaee2064a3..52920963044cf 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -49,6 +49,7 @@ use OCP\IL10N; use OCP\INavigationManager; use OCP\IURLGenerator; +use OCP\Util as OCPUtil; class ThemingDefaults extends \OC_Defaults { @@ -209,7 +210,7 @@ public function getShortFooter() { $divider = ''; foreach ($links as $link) { if ($link['url'] !== '' - && filter_var($link['url'], FILTER_VALIDATE_URL) + && OCPUtil::isValidUrl($link['url']) ) { $legalLinks .= $divider . '' . $link['text'] . ''; diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 177ede1e29254..f8e5a9cfa2f19 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -61,6 +61,7 @@ use OCP\IL10N; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; +use OCP\Util; class Setup { /** @var SystemConfig */ @@ -339,7 +340,7 @@ public function install($options) { 'trusted_domains' => $trustedDomains, 'datadirectory' => $dataDir, 'dbtype' => $dbType, - 'version' => implode('.', \OCP\Util::getVersion()), + 'version' => implode('.', Util::getVersion()), ]; if ($this->config->getValue('overwrite.cli.url', null) === null) { @@ -475,7 +476,7 @@ private static function findWebRoot(SystemConfig $config): string { if ($webRoot === '') { throw new InvalidArgumentException('overwrite.cli.url is empty'); } - if (!filter_var($webRoot, FILTER_VALIDATE_URL)) { + if (!Util::isValidUrl($webRoot)) { throw new InvalidArgumentException('invalid value for overwrite.cli.url'); } $webRoot = rtrim((parse_url($webRoot, PHP_URL_PATH) ?? ''), '/'); diff --git a/lib/public/Util.php b/lib/public/Util.php index cd6f5f34a698a..31ef2fc654a13 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -602,4 +602,36 @@ public static function shortenMultibyteString(string $subject, int $dataLength, } return $temp; } + + /** + * Checks whether the given URL is valid. This function should be + * used instead of filter_var($url, FILTER_VALIDATE_URL) since it + * handles idn urls too. + * + * @return bool true if the url is valid, false otherwise. + * @since 24.0.0 + */ + public static function isValidUrl(string $url, array $protocols = ['http', 'https']): bool { + // from https://github.com/symfony/validator/blob/14337bdf9e2e0b2e3385c9e90f13325f0c95a4f9/Constraints/UrlValidator.php#L24 + // Author: Bernhard Schussek + $pattern = '~^ + (%s):// # protocol + (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth + ( + ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name + | # or + \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address + | # or + \[ + (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) + \] # an IPv6 address + ) + (:[0-9]+)? # a port (optional) + (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path + (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) + (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) + $~ixu'; + $pattern = sprintf($pattern, implode('|', $protocols)); + return preg_match($pattern, $url) !== false; + } } From 0031bfbe52019bcd85a2f79fee05424092c7a965 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 9 Mar 2022 18:40:35 +0100 Subject: [PATCH 02/10] Add an validator service with various constraints The API is heavily inspired by Symfony validator while having a much simpler implementation since this doesn't do object validation with annotation. Signed-off-by: Carl Schwan --- .../lib/Controller/ThemingController.php | 122 +++++++++-------- apps/theming/lib/ThemingDefaults.php | 18 ++- .../Controller/ThemingControllerTest.php | 31 +++-- apps/theming/tests/ThemingDefaultsTest.php | 5 +- build/psalm-baseline.xml | 13 +- lib/composer/composer/autoload_classmap.php | 15 +++ lib/composer/composer/autoload_static.php | 15 +++ lib/private/Server.php | 9 +- lib/private/Setup.php | 36 +++-- lib/private/Validator/CssColorValidator.php | 80 ++++++++++++ lib/private/Validator/EmailValidator.php | 39 ++++++ .../Validator/IConstraintValidator.php | 14 ++ lib/private/Validator/LengthValidator.php | 68 ++++++++++ lib/private/Validator/NotBlankValidator.php | 26 ++++ lib/private/Validator/UrlValidator.php | 53 ++++++++ lib/private/Validator/Validator.php | 32 +++++ lib/public/Util.php | 32 ----- .../Validator/Constraints/Constraint.php | 29 +++++ lib/public/Validator/Constraints/CssColor.php | 74 +++++++++++ lib/public/Validator/Constraints/Email.php | 19 +++ lib/public/Validator/Constraints/Length.php | 87 +++++++++++++ lib/public/Validator/Constraints/NotBlank.php | 26 ++++ lib/public/Validator/Constraints/Url.php | 33 +++++ lib/public/Validator/IValidator.php | 26 ++++ lib/public/Validator/Violation.php | 46 +++++++ tests/lib/Validator/ValidatorTest.php | 123 ++++++++++++++++++ 26 files changed, 925 insertions(+), 146 deletions(-) create mode 100644 lib/private/Validator/CssColorValidator.php create mode 100644 lib/private/Validator/EmailValidator.php create mode 100644 lib/private/Validator/IConstraintValidator.php create mode 100644 lib/private/Validator/LengthValidator.php create mode 100644 lib/private/Validator/NotBlankValidator.php create mode 100644 lib/private/Validator/UrlValidator.php create mode 100644 lib/private/Validator/Validator.php create mode 100644 lib/public/Validator/Constraints/Constraint.php create mode 100644 lib/public/Validator/Constraints/CssColor.php create mode 100644 lib/public/Validator/Constraints/Email.php create mode 100644 lib/public/Validator/Constraints/Length.php create mode 100644 lib/public/Validator/Constraints/NotBlank.php create mode 100644 lib/public/Validator/Constraints/Url.php create mode 100644 lib/public/Validator/IValidator.php create mode 100644 lib/public/Validator/Violation.php create mode 100644 tests/lib/Validator/ValidatorTest.php diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index c1abdb61a8296..2f0e6cd6200ae 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -54,7 +54,11 @@ use OCP\IRequest; use OCP\ITempManager; use OCP\IURLGenerator; -use OCP\Util; +use OCP\Validator\Constraints\CssColor; +use OCP\Validator\Constraints\Length; +use OCP\Validator\Constraints\Url; +use OCP\Validator\IValidator; +use OCP\Validator\Violation; /** * Class ThemingController @@ -64,39 +68,19 @@ * @package OCA\Theming\Controller */ class ThemingController extends Controller { - /** @var ThemingDefaults */ - private $themingDefaults; - /** @var IL10N */ - private $l10n; - /** @var IConfig */ - private $config; - /** @var ITempManager */ - private $tempManager; - /** @var IAppData */ - private $appData; - /** @var SCSSCacher */ - private $scssCacher; - /** @var IURLGenerator */ - private $urlGenerator; - /** @var IAppManager */ - private $appManager; - /** @var ImageManager */ - private $imageManager; + private ThemingDefaults $themingDefaults; + private IL10N $l10n; + private IConfig $config; + private ITempManager $tempManager; + private IAppData $appData; + private SCSSCacher $scssCacher; + private IURLGenerator $urlGenerator; + private IAppManager $appManager; + private ImageManager $imageManager; + private IValidator $validator; /** * ThemingController constructor. - * - * @param string $appName - * @param IRequest $request - * @param IConfig $config - * @param ThemingDefaults $themingDefaults - * @param IL10N $l - * @param ITempManager $tempManager - * @param IAppData $appData - * @param SCSSCacher $scssCacher - * @param IURLGenerator $urlGenerator - * @param IAppManager $appManager - * @param ImageManager $imageManager */ public function __construct( $appName, @@ -109,7 +93,8 @@ public function __construct( SCSSCacher $scssCacher, IURLGenerator $urlGenerator, IAppManager $appManager, - ImageManager $imageManager + ImageManager $imageManager, + IValidator $validator ) { parent::__construct($appName, $request); @@ -122,6 +107,7 @@ public function __construct( $this->urlGenerator = $urlGenerator; $this->appManager = $appManager; $this->imageManager = $imageManager; + $this->validator = $validator; } /** @@ -133,52 +119,64 @@ public function __construct( */ public function updateStylesheet($setting, $value) { $value = trim($value); - $error = null; + $violations = []; switch ($setting) { case 'name': - if (strlen($value) > 250) { - $error = $this->l10n->t('The given name is too long'); - } + $violations = $this->validator->validate($value, [ + new Length([ + 'max' => 250, + 'maxMessage' => $this->l10n->t('The given name is too long'), + ]) + ]); break; case 'url': - if (strlen($value) > 500) { - $error = $this->l10n->t('The given web address is too long'); - } - if (!Util::isValidUrl($value)) { - $error = $this->l10n->t('The given web address is not a valid URL'); - } + $violations = $this->validator->validate($value, [ + new Length([ + 'max' => 500, + 'maxMessage' => $this->l10n->t('The given web address is too long'), + ]), + new Url(false, ['http', 'https'], $this->l10n->t('The given web address is not a valid URL')), + ]); + break; case 'imprintUrl': - if (strlen($value) > 500) { - $error = $this->l10n->t('The given legal notice address is too long'); - } - if (!Util::isValidUrl($value)) { - $error = $this->l10n->t('The given legal notice address is not a valid URL'); - } + $violations = $this->validator->validate($value, [ + new Length([ + 'max' => 500, + 'maxMessage' => $this->l10n->t('The given legal notice address is too long'), + ]), + new Url(false, ['http', 'https'], $this->l10n->t('The given legal notice address is not a valid URL')), + ]); break; case 'privacyUrl': - if (strlen($value) > 500) { - $error = $this->l10n->t('The given privacy policy address is too long'); - } - if (!Util::isValidUrl($value)) { - $error = $this->l10n->t('The given privacy policy address is not a valid URL'); - } + $violations = $this->validator->validate($value, [ + new Length([ + 'max' => 500, + 'maxMessage' => $this->l10n->t('The given privacy policy address is too long'), + ]), + new Url(false, ['http', 'https'], $this->l10n->t('The given privacy policy address is not a valid URL')), + ]); break; case 'slogan': - if (strlen($value) > 500) { - $error = $this->l10n->t('The given slogan is too long'); - } + $violations = $this->validator->validate($value, [ + new Length([ + 'max' => 500, + 'maxMessage' => $this->l10n->t('The given slogan is too long'), + ]) + ]); break; case 'color': - if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { - $error = $this->l10n->t('The given color is invalid'); - } + $violations = $this->validator->validate($value, [ + new CssColor(['message' => $this->l10n->t('The given color is invalid')]), + ]); break; } - if ($error !== null) { + if (count($violations) > 0) { return new DataResponse([ 'data' => [ - 'message' => $error, + 'message' => implode('. ', array_map(function (Violation $violation): string { + return $violation->getMessage(); + }, $violations)) . '.', ], 'status' => 'error' ], Http::STATUS_BAD_REQUEST); diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index 52920963044cf..e6377725c8510 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -50,6 +50,10 @@ use OCP\INavigationManager; use OCP\IURLGenerator; use OCP\Util as OCPUtil; +use OCP\Validator\Constraints\CssColor; +use OCP\Validator\Constraints\NotBlank; +use OCP\Validator\Constraints\Url; +use OCP\Validator\IValidator; class ThemingDefaults extends \OC_Defaults { @@ -91,6 +95,7 @@ class ThemingDefaults extends \OC_Defaults { private $AndroidClientUrl; /** @var string */ private $FDroidClientUrl; + private IValidator $validator; /** * ThemingDefaults constructor. @@ -110,7 +115,8 @@ public function __construct(IConfig $config, Util $util, ImageManager $imageManager, IAppManager $appManager, - INavigationManager $navigationManager + INavigationManager $navigationManager, + IValidator $validator ) { parent::__construct(); $this->config = $config; @@ -121,6 +127,7 @@ public function __construct(IConfig $config, $this->util = $util; $this->appManager = $appManager; $this->navigationManager = $navigationManager; + $this->validator = $validator; $this->name = parent::getName(); $this->title = parent::getTitle(); @@ -209,9 +216,10 @@ public function getShortFooter() { $legalLinks = ''; $divider = ''; foreach ($links as $link) { - if ($link['url'] !== '' - && OCPUtil::isValidUrl($link['url']) - ) { + if ($this->validator->isValid($link['url'], [ + new NotBlank(), + new Url(), + ])) { $legalLinks .= $divider . '' . $link['text'] . ''; $divider = ' ยท '; @@ -231,7 +239,7 @@ public function getShortFooter() { */ public function getColorPrimary() { $color = $this->config->getAppValue('theming', 'color', $this->color); - if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { + if (!$this->validator->isValid($color, [new CssColor()])) { $color = '#0082c9'; } return $color; diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index cff2028809dc9..41499bf619773 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -35,6 +35,7 @@ use OC\L10N\L10N; use OC\Template\SCSSCacher; +use OC\Validator\Validator; use OCA\Theming\Controller\ThemingController; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; @@ -50,6 +51,7 @@ use OCP\IRequest; use OCP\ITempManager; use OCP\IURLGenerator; +use OCP\Validator\IValidator; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -107,7 +109,8 @@ protected function setUp(): void { $this->scssCacher, $this->urlGenerator, $this->appManager, - $this->imageManager + $this->imageManager, + new Validator() ); parent::setUp(); @@ -139,7 +142,7 @@ public function testUpdateStylesheetSuccess($setting, $value, $message) { ->method('set') ->with($setting, $value); $this->l10n - ->expects($this->once()) + ->expects($this->any()) ->method('t') ->willReturnCallback(function ($str) { return $str; @@ -170,18 +173,18 @@ public function testUpdateStylesheetSuccess($setting, $value, $message) { public function dataUpdateStylesheetError() { return [ - ['name', str_repeat('a', 251), 'The given name is too long'], - ['url', 'http://example.com/' . str_repeat('a', 501), 'The given web address is too long'], - ['url', str_repeat('a', 501), 'The given web address is not a valid URL'], - ['url', 'javascript:alert(1)', 'The given web address is not a valid URL'], - ['slogan', str_repeat('a', 501), 'The given slogan is too long'], - ['color', '0082C9', 'The given color is invalid'], - ['color', '#0082Z9', 'The given color is invalid'], - ['color', 'Nextcloud', 'The given color is invalid'], - ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL'], - ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL'], - ['imprintUrl', 'javascript:foo', 'The given legal notice address is not a valid URL'], - ['privacyUrl', '#0082Z9', 'The given privacy policy address is not a valid URL'], + ['name', str_repeat('a', 251), 'The given name is too long.'], + ['url', 'http://example.com/' . str_repeat('a', 501), 'The given web address is too long.'], + ['url', str_repeat('a', 501), 'The given web address is too long. The given web address is not a valid URL.'], + ['url', 'javascript:alert(1)', 'The given web address is not a valid URL.'], + ['slogan', str_repeat('a', 501), 'The given slogan is too long.'], + ['color', '0082C9', 'The given color is invalid.'], + ['color', '#0082Z9', 'The given color is invalid.'], + ['color', 'Nextcloud', 'The given color is invalid.'], + ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL.'], + ['imprintUrl', '0082C9', 'The given legal notice address is not a valid URL.'], + ['imprintUrl', 'javascript:foo', 'The given legal notice address is not a valid URL.'], + ['privacyUrl', '#0082Z9', 'The given privacy policy address is not a valid URL.'], ]; } diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php index c8ef147dc94bc..463bf55f728f6 100644 --- a/apps/theming/tests/ThemingDefaultsTest.php +++ b/apps/theming/tests/ThemingDefaultsTest.php @@ -34,6 +34,7 @@ */ namespace OCA\Theming\Tests; +use OC\Validator\Validator; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; @@ -46,6 +47,7 @@ use OCP\IL10N; use OCP\INavigationManager; use OCP\IURLGenerator; +use OCP\Validator\IValidator; use Test\TestCase; class ThemingDefaultsTest extends TestCase { @@ -98,7 +100,8 @@ protected function setUp(): void { $this->util, $this->imageManager, $this->appManager, - $this->navigationManager + $this->navigationManager, + new Validator() ); } diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 44a113f1238e9..8b8dfbde85e66 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1023,7 +1023,6 @@ get_class($res) === 'OpenSSLAsymmetricKey' - is_object($res) @@ -1963,10 +1962,6 @@ - - LDAP_OPT_PROTOCOL_VERSION - LDAP_OPT_REFERRALS - @@ -2105,15 +2100,9 @@ private function detectGroupMemberAssoc() { private function getAttributeValuesFromEntry($result, $attribute, &$known) { - + $port $port - LDAP_OPT_NETWORK_TIMEOUT - LDAP_OPT_NETWORK_TIMEOUT - LDAP_OPT_PROTOCOL_VERSION - LDAP_OPT_PROTOCOL_VERSION - LDAP_OPT_REFERRALS - LDAP_OPT_REFERRALS !isset($item['cn']) && !is_array($item['cn']) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 86efab3ad52b2..bc32bd46384e4 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -582,6 +582,14 @@ 'OCP\\User\\Events\\UserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/UserLoggedOutEvent.php', 'OCP\\User\\GetQuotaEvent' => $baseDir . '/lib/public/User/GetQuotaEvent.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', + 'OCP\\Validator\\Constraints\\Constraint' => $baseDir . '/lib/public/Validator/Constraints/Constraint.php', + 'OCP\\Validator\\Constraints\\CssColor' => $baseDir . '/lib/public/Validator/Constraints/CssColor.php', + 'OCP\\Validator\\Constraints\\Email' => $baseDir . '/lib/public/Validator/Constraints/Email.php', + 'OCP\\Validator\\Constraints\\Length' => $baseDir . '/lib/public/Validator/Constraints/Length.php', + 'OCP\\Validator\\Constraints\\NotBlank' => $baseDir . '/lib/public/Validator/Constraints/NotBlank.php', + 'OCP\\Validator\\Constraints\\Url' => $baseDir . '/lib/public/Validator/Constraints/Url.php', + 'OCP\\Validator\\IValidator' => $baseDir . '/lib/public/Validator/IValidator.php', + 'OCP\\Validator\\Violation' => $baseDir . '/lib/public/Validator/Violation.php', 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', 'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', 'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php', @@ -1503,6 +1511,13 @@ 'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', 'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php', 'OC\\User\\User' => $baseDir . '/lib/private/User/User.php', + 'OC\\Validator\\CssColorValidator' => $baseDir . '/lib/private/Validator/CssColorValidator.php', + 'OC\\Validator\\EmailValidator' => $baseDir . '/lib/private/Validator/EmailValidator.php', + 'OC\\Validator\\IConstraintValidator' => $baseDir . '/lib/private/Validator/IConstraintValidator.php', + 'OC\\Validator\\LengthValidator' => $baseDir . '/lib/private/Validator/LengthValidator.php', + 'OC\\Validator\\NotBlankValidator' => $baseDir . '/lib/private/Validator/NotBlankValidator.php', + 'OC\\Validator\\UrlValidator' => $baseDir . '/lib/private/Validator/UrlValidator.php', + 'OC\\Validator\\Validator' => $baseDir . '/lib/private/Validator/Validator.php', 'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php', 'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php', 'OC_DB' => $baseDir . '/lib/private/legacy/OC_DB.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index ea37771d63c37..035a506412624 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -611,6 +611,14 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Events\\UserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedOutEvent.php', 'OCP\\User\\GetQuotaEvent' => __DIR__ . '/../../..' . '/lib/public/User/GetQuotaEvent.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', + 'OCP\\Validator\\Constraints\\Constraint' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Constraint.php', + 'OCP\\Validator\\Constraints\\CssColor' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/CssColor.php', + 'OCP\\Validator\\Constraints\\Email' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Email.php', + 'OCP\\Validator\\Constraints\\Length' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Length.php', + 'OCP\\Validator\\Constraints\\NotBlank' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/NotBlank.php', + 'OCP\\Validator\\Constraints\\Url' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Url.php', + 'OCP\\Validator\\IValidator' => __DIR__ . '/../../..' . '/lib/public/Validator/IValidator.php', + 'OCP\\Validator\\Violation' => __DIR__ . '/../../..' . '/lib/public/Validator/Violation.php', 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', 'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php', 'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php', @@ -1532,6 +1540,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', 'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php', 'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php', + 'OC\\Validator\\CssColorValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/CssColorValidator.php', + 'OC\\Validator\\EmailValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/EmailValidator.php', + 'OC\\Validator\\IConstraintValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/IConstraintValidator.php', + 'OC\\Validator\\LengthValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/LengthValidator.php', + 'OC\\Validator\\NotBlankValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/NotBlankValidator.php', + 'OC\\Validator\\UrlValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/UrlValidator.php', + 'OC\\Validator\\Validator' => __DIR__ . '/../../..' . '/lib/private/Validator/Validator.php', 'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php', 'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php', 'OC_DB' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_DB.php', diff --git a/lib/private/Server.php b/lib/private/Server.php index 38720ab71c013..ab31c8cc15822 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -256,6 +256,8 @@ use OCA\Files_External\Service\UserGlobalStoragesService; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\BackendService; +use OCP\Validator\IValidator as IValidationValidator; +use OC\Validator\Validator as ValidationValidator; /** * Class Server @@ -1192,7 +1194,8 @@ public function __construct($webRoot, \OC\Config $config) { $this->get(ITempManager::class) ), $c->get(IAppManager::class), - $c->get(INavigationManager::class) + $c->get(INavigationManager::class), + $c->get(IValidationValidator::class) ); } return new \OC_Defaults(); @@ -1400,6 +1403,10 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class); + $this->registerService(IValidationValidator::class, function (ContainerInterface $c): IValidationValidator { + return new ValidationValidator(); + }); + $this->connectDispatcher(); } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index f8e5a9cfa2f19..52f66df1e39c1 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -59,25 +59,21 @@ use OCP\Defaults; use OCP\IGroup; use OCP\IL10N; +use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; +use OCP\Validator\Constraints\Url; +use OCP\Validator\IValidator; use Psr\Log\LoggerInterface; use OCP\Util; class Setup { - /** @var SystemConfig */ - protected $config; - /** @var IniGetWrapper */ - protected $iniWrapper; - /** @var IL10N */ - protected $l10n; - /** @var Defaults */ - protected $defaults; - /** @var LoggerInterface */ - protected $logger; - /** @var ISecureRandom */ - protected $random; - /** @var Installer */ - protected $installer; + protected SystemConfig $config; + protected IniGetWrapper $iniWrapper; + protected IL10N $l10n; + protected Defaults $defaults; + protected LoggerInterface $logger; + protected ISecureRandom $random; + protected Installer $installer; public function __construct( SystemConfig $config, @@ -476,7 +472,9 @@ private static function findWebRoot(SystemConfig $config): string { if ($webRoot === '') { throw new InvalidArgumentException('overwrite.cli.url is empty'); } - if (!Util::isValidUrl($webRoot)) { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + if (count($validator->validate($webRoot, [new Url()])) > 0) { throw new InvalidArgumentException('invalid value for overwrite.cli.url'); } $webRoot = rtrim((parse_url($webRoot, PHP_URL_PATH) ?? ''), '/'); @@ -505,11 +503,11 @@ public static function updateHtaccess() { $setupHelper = new \OC\Setup( $config, \OC::$server->get(IniGetWrapper::class), - \OC::$server->getL10N('lib'), - \OC::$server->query(Defaults::class), + \OC::$server->get(IFactory::class)->get('lib'), + \OC::$server->get(Defaults::class), \OC::$server->get(LoggerInterface::class), - \OC::$server->getSecureRandom(), - \OC::$server->query(Installer::class) + \OC::$server->get(ISecureRandom::class), + \OC::$server->get(Installer::class) ); $htaccessContent = file_get_contents($setupHelper->pathToHtaccess()); diff --git a/lib/private/Validator/CssColorValidator.php b/lib/private/Validator/CssColorValidator.php new file mode 100644 index 0000000000000..c2da9e9a788d7 --- /dev/null +++ b/lib/private/Validator/CssColorValidator.php @@ -0,0 +1,80 @@ + + * @copyright Mathieu Santostefano + * + * @license AGPL-3.0-or-later and MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Validator; + +use OCP\Validator\Constraints\Constraint; +use OCP\Validator\Constraints\CssColor; +use OCP\Validator\Violation; + +class CssColorValidator implements IConstraintValidator { + private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i'; + private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i'; + private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i'; + private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors + private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors + private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i'; + // List comes from https://drafts.csswg.org/css-color/#css-system-colors + private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i'; + private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i'; + private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i'; + private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i'; + private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + + private const COLOR_PATTERNS = [ + CssColor::HEX_LONG => self::PATTERN_HEX_LONG, + CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA, + CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT, + CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA, + CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS, + CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS, + CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS, + CssColor::KEYWORDS => self::PATTERN_KEYWORDS, + CssColor::RGB => self::PATTERN_RGB, + CssColor::RGBA => self::PATTERN_RGBA, + CssColor::HSL => self::PATTERN_HSL, + CssColor::HSLA => self::PATTERN_HSLA, + ]; + + public function validate($value, Constraint $constraint): array { + if (!$constraint instanceof CssColor) { + throw new \RuntimeException('Invalid constraint'); + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The CssColorValidator can only validate scalar values or object convertible to string.'); + } + + foreach ($constraint->getFormats() as $regex) { + if (preg_match(self::COLOR_PATTERNS[$regex], (string)$value)) { + return []; + } + } + + return [ + (new Violation($constraint->getMessage()))->addParameter('{{ value }}', (string)$value), + ]; + } +} diff --git a/lib/private/Validator/EmailValidator.php b/lib/private/Validator/EmailValidator.php new file mode 100644 index 0000000000000..56996689c7880 --- /dev/null +++ b/lib/private/Validator/EmailValidator.php @@ -0,0 +1,39 @@ +isValid($value, new NoRFCWarningsValidation())) { + return [ + (new Violation($constraint->getMessage()))->addParameter('{{ value }}', $value) + ]; + } + + return []; + } +} diff --git a/lib/private/Validator/IConstraintValidator.php b/lib/private/Validator/IConstraintValidator.php new file mode 100644 index 0000000000000..1d7ae79ddcbcb --- /dev/null +++ b/lib/private/Validator/IConstraintValidator.php @@ -0,0 +1,14 @@ + + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Validator; + +use OCP\Validator\Constraints\Constraint; +use OCP\Validator\Constraints\Length; +use OCP\Validator\Violation; + +class LengthValidator implements IConstraintValidator { + public function validate($value, Constraint $constraint): array { + if (!$constraint instanceof Length) { + throw new \RuntimeException('Invalid constraint'); + } + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The LengthValidator can only validate scalar values or object convertible to string.'); + } + + $stringValue = (string)$value; + $length = mb_strlen($stringValue); + + if ($constraint->getExact() !== null && $constraint->getExact() !== $length) { + return [ + (new Violation($constraint->getExactMessage())) + ->addParameter('{{ limit }}', (string)$constraint->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + + if ($constraint->getMin() !== null && $constraint->getMin() > $length) { + return [ + (new Violation($constraint->getMinMessage())) + ->addParameter('{{ limit }}', (string)$constraint->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + if ($constraint->getMax() !== null && $constraint->getMax() < $length) { + return [ + (new Violation($constraint->getMaxMessage())) + ->addParameter('{{ limit }}', (string)$constraint->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + + return []; + } +} diff --git a/lib/private/Validator/NotBlankValidator.php b/lib/private/Validator/NotBlankValidator.php new file mode 100644 index 0000000000000..056935ca18620 --- /dev/null +++ b/lib/private/Validator/NotBlankValidator.php @@ -0,0 +1,26 @@ +allowNull() && null === $value) { + return []; + } + + if (false === $value || (empty($value) && '0' != $value)) { + return [ + (new Violation($constraint->getMessage())) + ]; + } + return []; + } +} diff --git a/lib/private/Validator/UrlValidator.php b/lib/private/Validator/UrlValidator.php new file mode 100644 index 0000000000000..1de48a37a5b72 --- /dev/null +++ b/lib/private/Validator/UrlValidator.php @@ -0,0 +1,53 @@ +isRelativeUrl() ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; + $pattern = sprintf($pattern, implode('|', $constraint->getProtocols())); + + if (!preg_match($pattern, $stringValue)) { + return [(new Violation($constraint->getMessage()))->addParameter('{{ value }}', $stringValue)]; + } + return []; + } +} diff --git a/lib/private/Validator/Validator.php b/lib/private/Validator/Validator.php new file mode 100644 index 0000000000000..f98674f315a57 --- /dev/null +++ b/lib/private/Validator/Validator.php @@ -0,0 +1,32 @@ +validatedBy(); + /** @var IConstraintValidator $validator */ + $validator = new $validatorClass(); + $violations = array_merge($violations, $validator->validate($value, $constraint)); + } + return $violations; + } + + public function isValid($value, array $constraints): bool { + foreach ($constraints as $constraint) { + $validatorClass = $constraint->validatedBy(); + /** @var IConstraintValidator $validator */ + $validator = new $validatorClass(); + if (count($validator->validate($value, $constraint)) > 0) { + return false; + } + } + return true; + } +} diff --git a/lib/public/Util.php b/lib/public/Util.php index 31ef2fc654a13..cd6f5f34a698a 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -602,36 +602,4 @@ public static function shortenMultibyteString(string $subject, int $dataLength, } return $temp; } - - /** - * Checks whether the given URL is valid. This function should be - * used instead of filter_var($url, FILTER_VALIDATE_URL) since it - * handles idn urls too. - * - * @return bool true if the url is valid, false otherwise. - * @since 24.0.0 - */ - public static function isValidUrl(string $url, array $protocols = ['http', 'https']): bool { - // from https://github.com/symfony/validator/blob/14337bdf9e2e0b2e3385c9e90f13325f0c95a4f9/Constraints/UrlValidator.php#L24 - // Author: Bernhard Schussek - $pattern = '~^ - (%s):// # protocol - (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth - ( - ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name - | # or - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address - | # or - \[ - (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) - \] # an IPv6 address - ) - (:[0-9]+)? # a port (optional) - (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path - (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) - (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) - $~ixu'; - $pattern = sprintf($pattern, implode('|', $protocols)); - return preg_match($pattern, $url) !== false; - } } diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php new file mode 100644 index 0000000000000..14485695c8799 --- /dev/null +++ b/lib/public/Validator/Constraints/Constraint.php @@ -0,0 +1,29 @@ +l10n = \OC::$server->get(IFactory::class)->get('core'); + } + + /** + * @return class-string + */ + public function validatedBy(): string { + return str_replace('Constraints\\', '', str_replace('OCP', 'OC', static::class)) . 'Validator'; + } +} diff --git a/lib/public/Validator/Constraints/CssColor.php b/lib/public/Validator/Constraints/CssColor.php new file mode 100644 index 0000000000000..6d1641a938255 --- /dev/null +++ b/lib/public/Validator/Constraints/CssColor.php @@ -0,0 +1,74 @@ + + * @copyright Mathieu Santostefano + * + * @license AGPL-3.0-or-later AND MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Validator\Constraints; + +class CssColor extends Constraint { + public const HEX_LONG = 'hex_long'; + public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha'; + public const HEX_SHORT = 'hex_short'; + public const HEX_SHORT_WITH_ALPHA = 'hex_short_with_alpha'; + public const BASIC_NAMED_COLORS = 'basic_named_colors'; + public const EXTENDED_NAMED_COLORS = 'extended_named_colors'; + public const SYSTEM_COLORS = 'system_colors'; + public const KEYWORDS = 'keywords'; + public const RGB = 'rgb'; + public const RGBA = 'rgba'; + public const HSL = 'hsl'; + public const HSLA = 'hsla'; + private string $message; + + private array $formats; + + /** + * @param array{formats?: string[], message?: string} $options + */ + public function __construct(array $options = []) { + parent::__construct(); + $this->message = $options['message'] ?? $this->l10n->t('"{{ value }}" is not a valid email address'); + $this->formats = $options['formats'] ?? [ + self::HEX_LONG, + self::HEX_LONG_WITH_ALPHA, + self::HEX_SHORT, + self::HEX_SHORT_WITH_ALPHA, + self::BASIC_NAMED_COLORS, + self::EXTENDED_NAMED_COLORS, + self::SYSTEM_COLORS, + self::KEYWORDS, + self::RGB, + self::RGBA, + self::HSL, + self::HSLA, + ]; + } + + public function getMessage(): string { + return $this->message; + } + + /** + * @return string[] + */ + public function getFormats(): array { + return $this->formats; + } +} diff --git a/lib/public/Validator/Constraints/Email.php b/lib/public/Validator/Constraints/Email.php new file mode 100644 index 0000000000000..7da55bc07fe3a --- /dev/null +++ b/lib/public/Validator/Constraints/Email.php @@ -0,0 +1,19 @@ +message = $message === null ? $this->l10n->t('"{{ value }}" is not a valid email address') : $message; + } + + public function getMessage(): string { + return $this->message; + } +} diff --git a/lib/public/Validator/Constraints/Length.php b/lib/public/Validator/Constraints/Length.php new file mode 100644 index 0000000000000..082c847850bb3 --- /dev/null +++ b/lib/public/Validator/Constraints/Length.php @@ -0,0 +1,87 @@ + + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Validator\Constraints; + +/** + * Length constrains for strings + * + * ```php + * $name = ... + * $validator = ... + * $validator->validate($name, [new Length([ + * 'min' => 2, + * 'max' => 200, + * 'minMessage' => "Your first name must be at least {{ limit }} characters long", + * 'maxMessage' => "Your first name must be at most {{ limit }} characters long", + * ])]); + * ``` + */ +class Length extends Constraint { + private ?int $min; + private ?int $max; + private ?int $exact; + private string $minMessage; + private string $maxMessage; + private string $exactMessage; + + /** + * @psalm-param array{min?: ?int, max?: ?int, exact?: ?int, minMessage?: ?string, maxMessage?: ?string, exactMessage?: ?string} $options + * @param array $options An array of options. Either min, max or exact needs to be defined. + */ + public function __construct(array $options) { + parent::__construct(); + + $this->min = $options['min'] ?? null; + $this->max = $options['max'] ?? null; + $this->exact = $options['exact'] ?? null; + + $this->minMessage = $options['minMessage'] ?? $this->l10n->t('"This value is too short. It should be at least {{ limit }} characters long.'); + $this->maxMessage = $options['maxMessage'] ?? $this->l10n->t('"This value is too long. It should be at most {{ limit }} characters long.'); + $this->exactMessage = $options['exactMessage'] ?? $this->l10n->t('"This value is incorrect. It should be exactly {{ limit }} characters long.'); + + assert($this->min !== null || $this->max !== null || $this->exact !== null); + } + + public function getMin(): ?int { + return $this->min; + } + + public function getMax(): ?int { + return $this->max; + } + + public function getExact(): ?int { + return $this->exact; + } + + public function getMinMessage(): string { + return $this->minMessage; + } + + public function getMaxMessage(): string { + return $this->maxMessage; + } + + public function getExactMessage(): string { + return $this->exactMessage; + } +} diff --git a/lib/public/Validator/Constraints/NotBlank.php b/lib/public/Validator/Constraints/NotBlank.php new file mode 100644 index 0000000000000..3b39a6a771d7e --- /dev/null +++ b/lib/public/Validator/Constraints/NotBlank.php @@ -0,0 +1,26 @@ +allowNull = $allowNull; + $this->message = $message === null ? $this->l10n->t('The value is blank') : $message; + } + + public function allowNull(): bool { + return $this->allowNull; + } + + public function getMessage(): string { + return $this->message; + } +} diff --git a/lib/public/Validator/Constraints/Url.php b/lib/public/Validator/Constraints/Url.php new file mode 100644 index 0000000000000..6f18ea4b63b50 --- /dev/null +++ b/lib/public/Validator/Constraints/Url.php @@ -0,0 +1,33 @@ +protocols = $protocols; + $this->message = $message === null ? $this->l10n->t('"{{ value }}" is not an url') : $message; + $this->relativeUrl = $relativeUrl; + } + + public function getProtocols(): array { + return $this->protocols; + } + + public function isRelativeUrl(): bool { + return $this->relativeUrl; + } + + public function getMessage(): string { + return $this->message; + } +} diff --git a/lib/public/Validator/IValidator.php b/lib/public/Validator/IValidator.php new file mode 100644 index 0000000000000..16fb6dcea6fd5 --- /dev/null +++ b/lib/public/Validator/IValidator.php @@ -0,0 +1,26 @@ +message = $message; + $this->parameters = []; + } + + /** + * Returns the violation message. This can be directly displayed to the + * user, if wanted. + */ + public function getMessage(): string { + $message = $this->message; + foreach ($this->parameters as $value => $representation) { + $message = str_replace($representation, $value, $message); + } + return $message; + } + + /** + * Inject a parameter inside the violation message. + * + * This allows to inject dynamic information in the violation message. + * + * ```php + * $violation = new Violation('This value should be less than {{ max }}.'); + * $violation->addParameter('{{ max }}', 100); + * assert($violation->getMessage() === 'This value should be less than 100.') + * ``` + */ + public function addParameter(string $representation, string $value): self { + $this->parameters[] = [ + $representation => $value, + ]; + return $this; + } +} diff --git a/tests/lib/Validator/ValidatorTest.php b/tests/lib/Validator/ValidatorTest.php new file mode 100644 index 0000000000000..8474890810fee --- /dev/null +++ b/tests/lib/Validator/ValidatorTest.php @@ -0,0 +1,123 @@ + + * Copyright (c) 2014 Vincent Petry + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Validator; + +use OCP\Validator\Constraints\Email; +use OCP\Validator\Constraints\Length; +use OCP\Validator\Constraints\NotBlank; +use OCP\Validator\Constraints\Url; +use OCP\Validator\IValidator; +use Test\TestCase; + +class ValidatorTest extends TestCase { + public function testEmailConstraint() { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate('carl@example.org', [ + new NotBlank(), + new Email(), + ]); + + $this->assertEmpty($violations); + } + + public function testNotBlankConstraintInvalid() { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate('', [ + new NotBlank(), + ]); + + $this->assertEquals(1, count($violations)); + $this->assertEquals('The value is blank', $violations[0]->getMessage()); + } + + /** + * @dataProvider urlProviderValid + */ + public function testUrl($url, $relativeUrl, $protocols) { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate($url, [ + new Url($relativeUrl, $protocols), + ]); + + $this->assertEmpty($violations); + } + + /** + * @dataProvider urlProviderInvalid + */ + public function testUrlInvalid($url, $relativeUrl, $protocols) { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate($url, [ + new Url($relativeUrl, $protocols), + ]); + + $this->assertEquals(1, count($violations)); + } + /** + * @dataProvider lengthProviderValid + */ + public function testLengthValid($value, $options) { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate($value, [ + new Length($options) + ]); + + $this->assertEmpty($violations); + } + + public function lengthProviderValid(): array { + return [ + ['helloworld', ['max' => 300, 'min' => 2]], + ['helloworld', ['exact' => 10]], + ]; + } + + /** + * @dataProvider lengthProviderInvalid + */ + public function testLengthInvalid($value, $options) { + /** @var IValidator $validator */ + $validator = \OC::$server->get(IValidator::class); + $violations = $validator->validate($value, [ + new Length($options) + ]); + + $this->assertEquals(1, count($violations)); + } + + public function lengthProviderInvalid(): array { + return [ + ['helloworld', ['max' => 2]], + ['helloworld', ['min' => 300]], + ['helloworld', ['exact' => 300]], + ]; + } + + public function urlProviderValid(): array { + return [ + ['https://hello.world', false, ['https']], + ['http://hello.world', false, ['http']], + ['http://โŒ˜.ws/', false, ['http']], + ['http://โžก.ws/ไจน', false, ['http']], + ]; + } + + public function urlProviderInvalid(): array { + return [ + ['example.com/legal', false, ['http', 'https']], # missing scheme + ['https:///legal', false, ['https']], # missing host + ]; + } +} From 9003fd12afc4aa244605f6bcf696a4fde450dbbb Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 9 Mar 2022 19:45:02 +0100 Subject: [PATCH 03/10] Add missing copyright Signed-off-by: Carl Schwan --- .../lib/Controller/SettingsController.php | 22 +++++++++---------- lib/private/Validator/EmailValidator.php | 19 ++++++++++++++++ .../Validator/IConstraintValidator.php | 19 ++++++++++++++++ lib/private/Validator/NotBlankValidator.php | 19 ++++++++++++++++ lib/private/Validator/UrlValidator.php | 19 ++++++++++++++++ lib/private/Validator/Validator.php | 19 ++++++++++++++++ .../Validator/Constraints/Constraint.php | 19 ++++++++++++++++ lib/public/Validator/Constraints/Email.php | 19 ++++++++++++++++ lib/public/Validator/Constraints/NotBlank.php | 19 ++++++++++++++++ lib/public/Validator/Constraints/Url.php | 19 ++++++++++++++++ lib/public/Validator/IValidator.php | 19 ++++++++++++++++ lib/public/Validator/Violation.php | 19 ++++++++++++++++ 12 files changed, 220 insertions(+), 11 deletions(-) diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php index 1da0d2ecf5dbe..3fd29618c15ca 100644 --- a/apps/oauth2/lib/Controller/SettingsController.php +++ b/apps/oauth2/lib/Controller/SettingsController.php @@ -39,17 +39,15 @@ use OCP\IL10N; use OCP\IRequest; use OCP\Security\ISecureRandom; -use OCP\Util; +use OCP\Validator\Constraints\Url; +use OCP\Validator\IValidator; class SettingsController extends Controller { - /** @var ClientMapper */ - private $clientMapper; - /** @var ISecureRandom */ - private $secureRandom; - /** @var AccessTokenMapper */ - private $accessTokenMapper; - /** @var IL10N */ - private $l; + private ClientMapper $clientMapper; + private ISecureRandom $secureRandom; + private AccessTokenMapper $accessTokenMapper; + private IL10N $l; + private IValidator $validator; public const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; @@ -58,18 +56,20 @@ public function __construct(string $appName, ClientMapper $clientMapper, ISecureRandom $secureRandom, AccessTokenMapper $accessTokenMapper, - IL10N $l + IL10N $l, + IValidator $validator ) { parent::__construct($appName, $request); $this->secureRandom = $secureRandom; $this->clientMapper = $clientMapper; $this->accessTokenMapper = $accessTokenMapper; $this->l = $l; + $this->validator = $validator; } public function addClient(string $name, string $redirectUri): JSONResponse { - if (!Util::isValidUrl($redirectUri)) { + if (count($this->validator->validate($redirectUri, [new Url()])) > 0) { return new JSONResponse(['message' => $this->l->t('Your redirect URL needs to be a full URL for example: https://yourdomain.com/path')], Http::STATUS_BAD_REQUEST); } diff --git a/lib/private/Validator/EmailValidator.php b/lib/private/Validator/EmailValidator.php index 56996689c7880..9305bdb925178 100644 --- a/lib/private/Validator/EmailValidator.php +++ b/lib/private/Validator/EmailValidator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OC\Validator; diff --git a/lib/private/Validator/IConstraintValidator.php b/lib/private/Validator/IConstraintValidator.php index 1d7ae79ddcbcb..71d91822e42df 100644 --- a/lib/private/Validator/IConstraintValidator.php +++ b/lib/private/Validator/IConstraintValidator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OC\Validator; diff --git a/lib/private/Validator/NotBlankValidator.php b/lib/private/Validator/NotBlankValidator.php index 056935ca18620..2c5219e055784 100644 --- a/lib/private/Validator/NotBlankValidator.php +++ b/lib/private/Validator/NotBlankValidator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OC\Validator; diff --git a/lib/private/Validator/UrlValidator.php b/lib/private/Validator/UrlValidator.php index 1de48a37a5b72..e9b5b591846fc 100644 --- a/lib/private/Validator/UrlValidator.php +++ b/lib/private/Validator/UrlValidator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OC\Validator; diff --git a/lib/private/Validator/Validator.php b/lib/private/Validator/Validator.php index f98674f315a57..3ace0b37a460a 100644 --- a/lib/private/Validator/Validator.php +++ b/lib/private/Validator/Validator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OC\Validator; diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php index 14485695c8799..9525c4f4b1bf0 100644 --- a/lib/public/Validator/Constraints/Constraint.php +++ b/lib/public/Validator/Constraints/Constraint.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator\Constraints; diff --git a/lib/public/Validator/Constraints/Email.php b/lib/public/Validator/Constraints/Email.php index 7da55bc07fe3a..0ab9dccbbfe34 100644 --- a/lib/public/Validator/Constraints/Email.php +++ b/lib/public/Validator/Constraints/Email.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator\Constraints; diff --git a/lib/public/Validator/Constraints/NotBlank.php b/lib/public/Validator/Constraints/NotBlank.php index 3b39a6a771d7e..a9d504566028f 100644 --- a/lib/public/Validator/Constraints/NotBlank.php +++ b/lib/public/Validator/Constraints/NotBlank.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator\Constraints; diff --git a/lib/public/Validator/Constraints/Url.php b/lib/public/Validator/Constraints/Url.php index 6f18ea4b63b50..261463b595e99 100644 --- a/lib/public/Validator/Constraints/Url.php +++ b/lib/public/Validator/Constraints/Url.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator\Constraints; diff --git a/lib/public/Validator/IValidator.php b/lib/public/Validator/IValidator.php index 16fb6dcea6fd5..9099d35f69483 100644 --- a/lib/public/Validator/IValidator.php +++ b/lib/public/Validator/IValidator.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator; diff --git a/lib/public/Validator/Violation.php b/lib/public/Validator/Violation.php index 5b326ebffeded..632810100aff1 100644 --- a/lib/public/Validator/Violation.php +++ b/lib/public/Validator/Violation.php @@ -1,4 +1,23 @@ + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ namespace OCP\Validator; From 72294f4299fb47c0a2f181b561993f32827f9c5f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Mar 2022 10:57:05 +0100 Subject: [PATCH 04/10] Apply suggestions from review Signed-off-by: Carl Schwan --- apps/theming/lib/Controller/ThemingController.php | 5 ++--- lib/private/Setup.php | 2 +- lib/private/Validator/EmailValidator.php | 4 ++-- lib/public/Validator/Constraints/Constraint.php | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 2f0e6cd6200ae..2fb8e6c122e26 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -81,6 +81,7 @@ class ThemingController extends Controller { /** * ThemingController constructor. + * @string $appName */ public function __construct( $appName, @@ -174,9 +175,7 @@ public function updateStylesheet($setting, $value) { if (count($violations) > 0) { return new DataResponse([ 'data' => [ - 'message' => implode('. ', array_map(function (Violation $violation): string { - return $violation->getMessage(); - }, $violations)) . '.', + 'message' => implode('. ', array_map(fn (Violation $violation) => $violation->getMessage(), $violations)) . '.', ], 'status' => 'error' ], Http::STATUS_BAD_REQUEST); diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 52f66df1e39c1..e3795194cfb98 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -474,7 +474,7 @@ private static function findWebRoot(SystemConfig $config): string { } /** @var IValidator $validator */ $validator = \OC::$server->get(IValidator::class); - if (count($validator->validate($webRoot, [new Url()])) > 0) { + if ($validator->isValid($webRoot, [new Url()])) { throw new InvalidArgumentException('invalid value for overwrite.cli.url'); } $webRoot = rtrim((parse_url($webRoot, PHP_URL_PATH) ?? ''), '/'); diff --git a/lib/private/Validator/EmailValidator.php b/lib/private/Validator/EmailValidator.php index 9305bdb925178..8e73b379055ac 100644 --- a/lib/private/Validator/EmailValidator.php +++ b/lib/private/Validator/EmailValidator.php @@ -33,7 +33,7 @@ public function validate($value, Constraint $constraint): array { throw new \RuntimeException('Email validator called with a wrong constraint'); } - if ($value === null || '' == $value) { + if ($value === null || $value == '') { return []; } @@ -42,7 +42,7 @@ public function validate($value, Constraint $constraint): array { } $value = (string) $value; - if ('' === $value) { + if ($value === '') { return []; } diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php index 9525c4f4b1bf0..9972eeea26649 100644 --- a/lib/public/Validator/Constraints/Constraint.php +++ b/lib/public/Validator/Constraints/Constraint.php @@ -26,7 +26,7 @@ use OCP\L10N\IFactory; /** - * Abstract class for validatoin constraint. + * Abstract class for validation constraint. * * For the moment, you must not extends this class inside your Nextcloud application. * Instead use the already existing public constraints or contribute new constraints From 27104b0199d144e51942e617ff22e9bfd8db16d2 Mon Sep 17 00:00:00 2001 From: szaimen Date: Thu, 10 Mar 2022 12:51:17 +0100 Subject: [PATCH 05/10] add validator to setup check Signed-off-by: szaimen --- .../lib/Controller/CheckSetupController.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php index 5225cd04f0922..dad95e7ec9c79 100644 --- a/apps/settings/lib/Controller/CheckSetupController.php +++ b/apps/settings/lib/Controller/CheckSetupController.php @@ -84,6 +84,9 @@ use OCP\Lock\ILockingProvider; use OCP\Notification\IManager; use OCP\Security\ISecureRandom; +use OCP\Validator\Constraints\Url; +use OCP\Validator\Constraints\NotBlank; +use OCP\Validator\IValidator; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -125,6 +128,7 @@ class CheckSetupController extends Controller { private $appManager; /** @var IServerContainer */ private $serverContainer; + private IValidator $validator; public function __construct($AppName, IRequest $request, @@ -145,7 +149,8 @@ public function __construct($AppName, ITempManager $tempManager, IManager $manager, IAppManager $appManager, - IServerContainer $serverContainer + IServerContainer $serverContainer, + IValidator $validator ) { parent::__construct($AppName, $request); $this->config = $config; @@ -166,6 +171,7 @@ public function __construct($AppName, $this->manager = $manager; $this->appManager = $appManager; $this->serverContainer = $serverContainer; + $this->validator = $validator; } /** @@ -618,7 +624,10 @@ protected function getSuggestedOverwriteCliURL(): string { $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT; // Check correctness by checking if it is a valid URL - if (filter_var($currentOverwriteCliUrl, FILTER_VALIDATE_URL)) { + if ($this->validator->isValid($currentOverwriteCliUrl, [ + new NotBlank(), + new Url(), + ])) { $suggestedOverwriteCliUrl = ''; } From 5751e9db9b882b5f3a919c0590ca0a6fff608151 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Mar 2022 11:01:31 +0100 Subject: [PATCH 06/10] Merge constraint and validator This simplifies the code a bit and make it possible for apps to add their own validators Signed-off-by: Carl Schwan --- apps/theming/tests/ThemingDefaultsTest.php | 1 - lib/composer/composer/autoload_classmap.php | 8 +- lib/composer/composer/autoload_static.php | 8 +- lib/private/Server.php | 18 ++--- lib/private/Validator/CssColorValidator.php | 80 ------------------- lib/private/Validator/EmailValidator.php | 58 -------------- lib/private/Validator/LengthValidator.php | 68 ---------------- lib/private/Validator/NotBlankValidator.php | 45 ----------- lib/private/Validator/UrlValidator.php | 72 ----------------- lib/private/Validator/Validator.php | 10 +-- .../Validator/Constraints/Constraint.php | 16 +--- lib/public/Validator/Constraints/CssColor.php | 76 +++++++++++++++--- lib/public/Validator/Constraints/Email.php | 28 +++++++ lib/public/Validator/Constraints/Length.php | 39 +++++++++ lib/public/Validator/Constraints/NotBlank.php | 15 ++++ lib/public/Validator/Constraints/Url.php | 42 ++++++++++ .../Validator/IConstraintValidator.php | 7 +- lib/public/Validator/IValidator.php | 4 +- 18 files changed, 206 insertions(+), 389 deletions(-) delete mode 100644 lib/private/Validator/CssColorValidator.php delete mode 100644 lib/private/Validator/EmailValidator.php delete mode 100644 lib/private/Validator/LengthValidator.php delete mode 100644 lib/private/Validator/NotBlankValidator.php delete mode 100644 lib/private/Validator/UrlValidator.php rename lib/{private => public}/Validator/IConstraintValidator.php (86%) diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php index 463bf55f728f6..a89399ce4bc3c 100644 --- a/apps/theming/tests/ThemingDefaultsTest.php +++ b/apps/theming/tests/ThemingDefaultsTest.php @@ -47,7 +47,6 @@ use OCP\IL10N; use OCP\INavigationManager; use OCP\IURLGenerator; -use OCP\Validator\IValidator; use Test\TestCase; class ThemingDefaultsTest extends TestCase { diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index bc32bd46384e4..a42557573f772 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -588,6 +588,7 @@ 'OCP\\Validator\\Constraints\\Length' => $baseDir . '/lib/public/Validator/Constraints/Length.php', 'OCP\\Validator\\Constraints\\NotBlank' => $baseDir . '/lib/public/Validator/Constraints/NotBlank.php', 'OCP\\Validator\\Constraints\\Url' => $baseDir . '/lib/public/Validator/Constraints/Url.php', + 'OCP\\Validator\\IConstraintValidator' => $baseDir . '/lib/public/Validator/IConstraintValidator.php', 'OCP\\Validator\\IValidator' => $baseDir . '/lib/public/Validator/IValidator.php', 'OCP\\Validator\\Violation' => $baseDir . '/lib/public/Validator/Violation.php', 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', @@ -1511,13 +1512,6 @@ 'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php', 'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php', 'OC\\User\\User' => $baseDir . '/lib/private/User/User.php', - 'OC\\Validator\\CssColorValidator' => $baseDir . '/lib/private/Validator/CssColorValidator.php', - 'OC\\Validator\\EmailValidator' => $baseDir . '/lib/private/Validator/EmailValidator.php', - 'OC\\Validator\\IConstraintValidator' => $baseDir . '/lib/private/Validator/IConstraintValidator.php', - 'OC\\Validator\\LengthValidator' => $baseDir . '/lib/private/Validator/LengthValidator.php', - 'OC\\Validator\\NotBlankValidator' => $baseDir . '/lib/private/Validator/NotBlankValidator.php', - 'OC\\Validator\\UrlValidator' => $baseDir . '/lib/private/Validator/UrlValidator.php', - 'OC\\Validator\\Validator' => $baseDir . '/lib/private/Validator/Validator.php', 'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php', 'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php', 'OC_DB' => $baseDir . '/lib/private/legacy/OC_DB.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 035a506412624..9bc2935e83b98 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -617,6 +617,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Validator\\Constraints\\Length' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Length.php', 'OCP\\Validator\\Constraints\\NotBlank' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/NotBlank.php', 'OCP\\Validator\\Constraints\\Url' => __DIR__ . '/../../..' . '/lib/public/Validator/Constraints/Url.php', + 'OCP\\Validator\\IConstraintValidator' => __DIR__ . '/../../..' . '/lib/public/Validator/IConstraintValidator.php', 'OCP\\Validator\\IValidator' => __DIR__ . '/../../..' . '/lib/public/Validator/IValidator.php', 'OCP\\Validator\\Violation' => __DIR__ . '/../../..' . '/lib/public/Validator/Violation.php', 'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php', @@ -1540,13 +1541,6 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php', 'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php', 'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php', - 'OC\\Validator\\CssColorValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/CssColorValidator.php', - 'OC\\Validator\\EmailValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/EmailValidator.php', - 'OC\\Validator\\IConstraintValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/IConstraintValidator.php', - 'OC\\Validator\\LengthValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/LengthValidator.php', - 'OC\\Validator\\NotBlankValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/NotBlankValidator.php', - 'OC\\Validator\\UrlValidator' => __DIR__ . '/../../..' . '/lib/private/Validator/UrlValidator.php', - 'OC\\Validator\\Validator' => __DIR__ . '/../../..' . '/lib/private/Validator/Validator.php', 'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php', 'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php', 'OC_DB' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_DB.php', diff --git a/lib/private/Server.php b/lib/private/Server.php index ab31c8cc15822..76d568599aa25 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -109,8 +109,8 @@ use OC\IntegrityCheck\Helpers\AppLocator; use OC\IntegrityCheck\Helpers\EnvironmentHelper; use OC\IntegrityCheck\Helpers\FileAccessHelper; -use OC\LDAP\NullLDAPProviderFactory; use OC\KnownUser\KnownUserService; +use OC\LDAP\NullLDAPProviderFactory; use OC\Lock\DBLockingProvider; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; @@ -145,10 +145,14 @@ use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; use OC\Tagging\TagMapper; use OC\Template\JSCombiner; +use OC\Validator\Validator as ValidationValidator; +use OCA\Files_External\Service\BackendService; +use OCA\Files_External\Service\GlobalStoragesService; +use OCA\Files_External\Service\UserGlobalStoragesService; +use OCA\Files_External\Service\UserStoragesService; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; -use OCA\WorkflowEngine\Service\Logger; use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\Authentication\LoginCredentials\IStore; @@ -235,29 +239,21 @@ use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\User\Events\BeforePasswordUpdatedEvent; -use OCP\User\Events\BeforeUserCreatedEvent; -use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\BeforeUserLoggedInEvent; use OCP\User\Events\BeforeUserLoggedInWithCookieEvent; use OCP\User\Events\BeforeUserLoggedOutEvent; use OCP\User\Events\PasswordUpdatedEvent; use OCP\User\Events\PostLoginEvent; use OCP\User\Events\UserChangedEvent; -use OCP\User\Events\UserDeletedEvent; use OCP\User\Events\UserLoggedInEvent; use OCP\User\Events\UserLoggedInWithCookieEvent; use OCP\User\Events\UserLoggedOutEvent; +use OCP\Validator\IValidator as IValidationValidator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; -use OCA\Files_External\Service\UserStoragesService; -use OCA\Files_External\Service\UserGlobalStoragesService; -use OCA\Files_External\Service\GlobalStoragesService; -use OCA\Files_External\Service\BackendService; -use OCP\Validator\IValidator as IValidationValidator; -use OC\Validator\Validator as ValidationValidator; /** * Class Server diff --git a/lib/private/Validator/CssColorValidator.php b/lib/private/Validator/CssColorValidator.php deleted file mode 100644 index c2da9e9a788d7..0000000000000 --- a/lib/private/Validator/CssColorValidator.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @copyright Mathieu Santostefano - * - * @license AGPL-3.0-or-later and MIT - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OC\Validator; - -use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Constraints\CssColor; -use OCP\Validator\Violation; - -class CssColorValidator implements IConstraintValidator { - private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i'; - private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i'; - private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i'; - private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i'; - // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors - private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i'; - // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors - private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i'; - // List comes from https://drafts.csswg.org/css-color/#css-system-colors - private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i'; - private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i'; - private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i'; - private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; - private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i'; - private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; - - private const COLOR_PATTERNS = [ - CssColor::HEX_LONG => self::PATTERN_HEX_LONG, - CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA, - CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT, - CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA, - CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS, - CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS, - CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS, - CssColor::KEYWORDS => self::PATTERN_KEYWORDS, - CssColor::RGB => self::PATTERN_RGB, - CssColor::RGBA => self::PATTERN_RGBA, - CssColor::HSL => self::PATTERN_HSL, - CssColor::HSLA => self::PATTERN_HSLA, - ]; - - public function validate($value, Constraint $constraint): array { - if (!$constraint instanceof CssColor) { - throw new \RuntimeException('Invalid constraint'); - } - - if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - throw new \RuntimeException('The CssColorValidator can only validate scalar values or object convertible to string.'); - } - - foreach ($constraint->getFormats() as $regex) { - if (preg_match(self::COLOR_PATTERNS[$regex], (string)$value)) { - return []; - } - } - - return [ - (new Violation($constraint->getMessage()))->addParameter('{{ value }}', (string)$value), - ]; - } -} diff --git a/lib/private/Validator/EmailValidator.php b/lib/private/Validator/EmailValidator.php deleted file mode 100644 index 8e73b379055ac..0000000000000 --- a/lib/private/Validator/EmailValidator.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OC\Validator; - -use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator; -use Egulias\EmailValidator\Validation\NoRFCWarningsValidation; -use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Constraints\Email; -use OCP\Validator\Violation; - -class EmailValidator implements IConstraintValidator { - public function validate($value, Constraint $constraint): array { - if (!$constraint instanceof Email) { - throw new \RuntimeException('Email validator called with a wrong constraint'); - } - - if ($value === null || $value == '') { - return []; - } - - if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - throw new \RuntimeException('The EmailValidator can only validate scalar values or object convertible to string.'); - } - - $value = (string) $value; - if ($value === '') { - return []; - } - - $internalValidator = new EguliasEmailValidator(); - if (!$internalValidator->isValid($value, new NoRFCWarningsValidation())) { - return [ - (new Violation($constraint->getMessage()))->addParameter('{{ value }}', $value) - ]; - } - - return []; - } -} diff --git a/lib/private/Validator/LengthValidator.php b/lib/private/Validator/LengthValidator.php deleted file mode 100644 index 8534b3da4c0bb..0000000000000 --- a/lib/private/Validator/LengthValidator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OC\Validator; - -use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Constraints\Length; -use OCP\Validator\Violation; - -class LengthValidator implements IConstraintValidator { - public function validate($value, Constraint $constraint): array { - if (!$constraint instanceof Length) { - throw new \RuntimeException('Invalid constraint'); - } - if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - throw new \RuntimeException('The LengthValidator can only validate scalar values or object convertible to string.'); - } - - $stringValue = (string)$value; - $length = mb_strlen($stringValue); - - if ($constraint->getExact() !== null && $constraint->getExact() !== $length) { - return [ - (new Violation($constraint->getExactMessage())) - ->addParameter('{{ limit }}', (string)$constraint->getMax()) - ->addParameter('{{ value }}', $stringValue) - ->addParameter('{{ stringLength }}', (string)$length), - ]; - } - - if ($constraint->getMin() !== null && $constraint->getMin() > $length) { - return [ - (new Violation($constraint->getMinMessage())) - ->addParameter('{{ limit }}', (string)$constraint->getMax()) - ->addParameter('{{ value }}', $stringValue) - ->addParameter('{{ stringLength }}', (string)$length), - ]; - } - if ($constraint->getMax() !== null && $constraint->getMax() < $length) { - return [ - (new Violation($constraint->getMaxMessage())) - ->addParameter('{{ limit }}', (string)$constraint->getMax()) - ->addParameter('{{ value }}', $stringValue) - ->addParameter('{{ stringLength }}', (string)$length), - ]; - } - - return []; - } -} diff --git a/lib/private/Validator/NotBlankValidator.php b/lib/private/Validator/NotBlankValidator.php deleted file mode 100644 index 2c5219e055784..0000000000000 --- a/lib/private/Validator/NotBlankValidator.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OC\Validator; - -use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Constraints\NotBlank; -use OCP\Validator\Violation; - -class NotBlankValidator implements IConstraintValidator { - public function validate($value, Constraint $constraint): array { - if (!$constraint instanceof NotBlank) { - throw new \RuntimeException(); - } - - if ($constraint->allowNull() && null === $value) { - return []; - } - - if (false === $value || (empty($value) && '0' != $value)) { - return [ - (new Violation($constraint->getMessage())) - ]; - } - return []; - } -} diff --git a/lib/private/Validator/UrlValidator.php b/lib/private/Validator/UrlValidator.php deleted file mode 100644 index e9b5b591846fc..0000000000000 --- a/lib/private/Validator/UrlValidator.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OC\Validator; - -use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Constraints\Url; -use OCP\Validator\Violation; - -class UrlValidator implements IConstraintValidator { - public const PATTERN = '~^ - (%s):// # protocol - (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth - ( - (?: - (?:xn--[a-z0-9-]++\.)*+xn--[a-z0-9-]++ # a domain name using punycode - | - (?:[\pL\pN\pS\pM\-\_]++\.)+[\pL\pN\pM]++ # a multi-level domain name - | - [a-z0-9\-\_]++ # a single-level domain name - )\.? - | # or - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address - | # or - \[ - (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) - \] # an IPv6 address - ) - (:[0-9]+)? # a port (optional) - (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path - (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) - (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) - $~ixu'; - - public function validate($value, Constraint $constraint): array { - if (!$constraint instanceof Url) { - throw new \RuntimeException('Url validator called with a wrong constraint'); - } - - if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - throw new \RuntimeException('The UrlValidator can only validate scalar values or object convertible to string.'); - } - - $stringValue = (string)$value; - - $pattern = $constraint->isRelativeUrl() ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; - $pattern = sprintf($pattern, implode('|', $constraint->getProtocols())); - - if (!preg_match($pattern, $stringValue)) { - return [(new Violation($constraint->getMessage()))->addParameter('{{ value }}', $stringValue)]; - } - return []; - } -} diff --git a/lib/private/Validator/Validator.php b/lib/private/Validator/Validator.php index 3ace0b37a460a..d9b78727a8b7c 100644 --- a/lib/private/Validator/Validator.php +++ b/lib/private/Validator/Validator.php @@ -29,20 +29,14 @@ public function validate($value, array $constraints): array { /** @var Violation[] $violations */ $violations = []; foreach ($constraints as $constraint) { - $validatorClass = $constraint->validatedBy(); - /** @var IConstraintValidator $validator */ - $validator = new $validatorClass(); - $violations = array_merge($violations, $validator->validate($value, $constraint)); + $violations = array_merge($violations, $constraint->validate($value)); } return $violations; } public function isValid($value, array $constraints): bool { foreach ($constraints as $constraint) { - $validatorClass = $constraint->validatedBy(); - /** @var IConstraintValidator $validator */ - $validator = new $validatorClass(); - if (count($validator->validate($value, $constraint)) > 0) { + if (count($constraint->validate($value)) > 0) { return false; } } diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php index 9972eeea26649..71f2d3f692ecd 100644 --- a/lib/public/Validator/Constraints/Constraint.php +++ b/lib/public/Validator/Constraints/Constraint.php @@ -21,28 +21,18 @@ namespace OCP\Validator\Constraints; -use OC\Validator\IConstraintValidator; use OCP\IL10N; use OCP\L10N\IFactory; +use OCP\Validator\IConstraintValidator; +use OCP\Validator\Violation; /** * Abstract class for validation constraint. - * - * For the moment, you must not extends this class inside your Nextcloud application. - * Instead use the already existing public constraints or contribute new constraints - * to Nextcloud core. */ -abstract class Constraint { +abstract class Constraint implements IConstraintValidator{ protected IL10N $l10n; public function __construct() { $this->l10n = \OC::$server->get(IFactory::class)->get('core'); } - - /** - * @return class-string - */ - public function validatedBy(): string { - return str_replace('Constraints\\', '', str_replace('OCP', 'OC', static::class)) . 'Validator'; - } } diff --git a/lib/public/Validator/Constraints/CssColor.php b/lib/public/Validator/Constraints/CssColor.php index 6d1641a938255..22b46f05e8c78 100644 --- a/lib/public/Validator/Constraints/CssColor.php +++ b/lib/public/Validator/Constraints/CssColor.php @@ -22,6 +22,8 @@ namespace OCP\Validator\Constraints; +use OCP\Validator\Violation; + class CssColor extends Constraint { public const HEX_LONG = 'hex_long'; public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha'; @@ -37,6 +39,37 @@ class CssColor extends Constraint { public const HSLA = 'hsla'; private string $message; + private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i'; + private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i'; + private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i'; + private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors + private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors + private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i'; + // List comes from https://drafts.csswg.org/css-color/#css-system-colors + private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i'; + private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i'; + private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i'; + private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i'; + private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + + private const COLOR_PATTERNS = [ + CssColor::HEX_LONG => self::PATTERN_HEX_LONG, + CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA, + CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT, + CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA, + CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS, + CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS, + CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS, + CssColor::KEYWORDS => self::PATTERN_KEYWORDS, + CssColor::RGB => self::PATTERN_RGB, + CssColor::RGBA => self::PATTERN_RGBA, + CssColor::HSL => self::PATTERN_HSL, + CssColor::HSLA => self::PATTERN_HSLA, + ]; + private array $formats; /** @@ -46,19 +79,19 @@ public function __construct(array $options = []) { parent::__construct(); $this->message = $options['message'] ?? $this->l10n->t('"{{ value }}" is not a valid email address'); $this->formats = $options['formats'] ?? [ - self::HEX_LONG, - self::HEX_LONG_WITH_ALPHA, - self::HEX_SHORT, - self::HEX_SHORT_WITH_ALPHA, - self::BASIC_NAMED_COLORS, - self::EXTENDED_NAMED_COLORS, - self::SYSTEM_COLORS, - self::KEYWORDS, - self::RGB, - self::RGBA, - self::HSL, - self::HSLA, - ]; + self::HEX_LONG, + self::HEX_LONG_WITH_ALPHA, + self::HEX_SHORT, + self::HEX_SHORT_WITH_ALPHA, + self::BASIC_NAMED_COLORS, + self::EXTENDED_NAMED_COLORS, + self::SYSTEM_COLORS, + self::KEYWORDS, + self::RGB, + self::RGBA, + self::HSL, + self::HSLA, + ]; } public function getMessage(): string { @@ -71,4 +104,21 @@ public function getMessage(): string { public function getFormats(): array { return $this->formats; } + + + public function validate($value): array { + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The CssColorValidator can only validate scalar values or object convertible to string.'); + } + + foreach ($this->getFormats() as $regex) { + if (preg_match(self::COLOR_PATTERNS[$regex], (string)$value)) { + return []; + } + } + + return [ + (new Violation($this->getMessage()))->addParameter('{{ value }}', (string)$value), + ]; + } } diff --git a/lib/public/Validator/Constraints/Email.php b/lib/public/Validator/Constraints/Email.php index 0ab9dccbbfe34..ab518e22b5055 100644 --- a/lib/public/Validator/Constraints/Email.php +++ b/lib/public/Validator/Constraints/Email.php @@ -21,6 +21,10 @@ namespace OCP\Validator\Constraints; +use Egulias\EmailValidator\Validation\NoRFCWarningsValidation; +use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator; +use OCP\Validator\Violation; + class Email extends Constraint { private string $message; /** @@ -35,4 +39,28 @@ public function __construct(?string $message = null) { public function getMessage(): string { return $this->message; } + + public function validate($value): array { + if ($value === null || $value == '') { + return []; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The EmailValidator can only validate scalar values or object convertible to string.'); + } + + $value = (string) $value; + if ($value === '') { + return []; + } + + $internalValidator = new EguliasEmailValidator(); + if (!$internalValidator->isValid($value, new NoRFCWarningsValidation())) { + return [ + (new Violation($this->getMessage()))->addParameter('{{ value }}', $value) + ]; + } + + return []; + } } diff --git a/lib/public/Validator/Constraints/Length.php b/lib/public/Validator/Constraints/Length.php index 082c847850bb3..49594d3808131 100644 --- a/lib/public/Validator/Constraints/Length.php +++ b/lib/public/Validator/Constraints/Length.php @@ -21,6 +21,8 @@ namespace OCP\Validator\Constraints; +use OCP\Validator\Violation; + /** * Length constrains for strings * @@ -84,4 +86,41 @@ public function getMaxMessage(): string { public function getExactMessage(): string { return $this->exactMessage; } + + public function validate($value): array { + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The LengthValidator can only validate scalar values or object convertible to string.'); + } + + $stringValue = (string)$value; + $length = mb_strlen($stringValue); + + if ($this->getExact() !== null && $this->getExact() !== $length) { + return [ + (new Violation($this->getExactMessage())) + ->addParameter('{{ limit }}', (string)$this->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + + if ($this->getMin() !== null && $this->getMin() > $length) { + return [ + (new Violation($this->getMinMessage())) + ->addParameter('{{ limit }}', (string)$this->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + if ($this->getMax() !== null && $this->getMax() < $length) { + return [ + (new Violation($this->getMaxMessage())) + ->addParameter('{{ limit }}', (string)$this->getMax()) + ->addParameter('{{ value }}', $stringValue) + ->addParameter('{{ stringLength }}', (string)$length), + ]; + } + + return []; + } } diff --git a/lib/public/Validator/Constraints/NotBlank.php b/lib/public/Validator/Constraints/NotBlank.php index a9d504566028f..b6e62fe2eef88 100644 --- a/lib/public/Validator/Constraints/NotBlank.php +++ b/lib/public/Validator/Constraints/NotBlank.php @@ -21,6 +21,8 @@ namespace OCP\Validator\Constraints; +use OCP\Validator\Violation; + class NotBlank extends Constraint { private string $message; private bool $allowNull; @@ -42,4 +44,17 @@ public function allowNull(): bool { public function getMessage(): string { return $this->message; } + + public function validate($value): array { + if ($this->allowNull() && null === $value) { + return []; + } + + if (false === $value || (empty($value) && '0' != $value)) { + return [ + (new Violation($this->getMessage())) + ]; + } + return []; + } } diff --git a/lib/public/Validator/Constraints/Url.php b/lib/public/Validator/Constraints/Url.php index 261463b595e99..2ea22e3f225b4 100644 --- a/lib/public/Validator/Constraints/Url.php +++ b/lib/public/Validator/Constraints/Url.php @@ -21,12 +21,38 @@ namespace OCP\Validator\Constraints; +use OCP\Validator\Violation; + class Url extends Constraint { /** @var string[] */ private array $protocols; private bool $relativeUrl; private string $message; + public const PATTERN = '~^ + (%s):// # protocol + (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth + ( + (?: + (?:xn--[a-z0-9-]++\.)*+xn--[a-z0-9-]++ # a domain name using punycode + | + (?:[\pL\pN\pS\pM\-\_]++\.)+[\pL\pN\pM]++ # a multi-level domain name + | + [a-z0-9\-\_]++ # a single-level domain name + )\.? + | # or + \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address + | # or + \[ + (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) + \] # an IPv6 address + ) + (:[0-9]+)? # a port (optional) + (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path + (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) + (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) + $~ixu'; + /** * @param string|null $message Overwrite the default translated error message * to use when the constraint is not fulfilled. @@ -49,4 +75,20 @@ public function isRelativeUrl(): bool { public function getMessage(): string { return $this->message; } + + public function validate($value): array { + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new \RuntimeException('The UrlValidator can only validate scalar values or object convertible to string.'); + } + + $stringValue = (string)$value; + + $pattern = $this->isRelativeUrl() ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; + $pattern = sprintf($pattern, implode('|', $this->getProtocols())); + + if (!preg_match($pattern, $stringValue)) { + return [(new Violation($this->getMessage()))->addParameter('{{ value }}', $stringValue)]; + } + return []; + } } diff --git a/lib/private/Validator/IConstraintValidator.php b/lib/public/Validator/IConstraintValidator.php similarity index 86% rename from lib/private/Validator/IConstraintValidator.php rename to lib/public/Validator/IConstraintValidator.php index 71d91822e42df..c0f8b90028698 100644 --- a/lib/private/Validator/IConstraintValidator.php +++ b/lib/public/Validator/IConstraintValidator.php @@ -19,15 +19,14 @@ * */ -namespace OC\Validator; +namespace OCP\Validator; use OCP\Validator\Constraints\Constraint; -use OCP\Validator\Violation; interface IConstraintValidator { /** - * @param mixed The value + * @param mixed The value to validate * @return Violation[] An array of violations */ - public function validate($value, Constraint $constraint): array; + public function validate($value): array; } diff --git a/lib/public/Validator/IValidator.php b/lib/public/Validator/IValidator.php index 9099d35f69483..4805cd9d8f796 100644 --- a/lib/public/Validator/IValidator.php +++ b/lib/public/Validator/IValidator.php @@ -28,7 +28,7 @@ interface IValidator { * Validate a value according to one or more constraints. * * @param mixed $value The value to validate - * @param Constraint[] $constraints The validator constraints for the value + * @param IConstraintValidator[] $constraints The validator constraints for the value * @return Violation[] An array of constraints violations. Empty if the value * is conforming to every constrains. */ @@ -38,7 +38,7 @@ public function validate($value, array $constraints): array; * Validate a value according to one or more constraints. This * * @param mixed $value The value to validate - * @param Constraint[] $constraints The validator constraints for the value + * @param IConstraintValidator[] $constraints The validator constraints for the value * @return bool Whether the value is valid */ public function isValid($value, array $constraints): bool; From 21769881b3b5e98794c6bf0265ae86ef42dce8bc Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Mar 2022 11:03:12 +0100 Subject: [PATCH 07/10] fixup! Merge constraint and validator --- apps/theming/lib/Controller/ThemingController.php | 2 +- apps/theming/tests/Controller/ThemingControllerTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 2fb8e6c122e26..795e87d3703c6 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -81,7 +81,7 @@ class ThemingController extends Controller { /** * ThemingController constructor. - * @string $appName + * @param string $appName */ public function __construct( $appName, diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index 41499bf619773..34fe930db2c5a 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -51,7 +51,6 @@ use OCP\IRequest; use OCP\ITempManager; use OCP\IURLGenerator; -use OCP\Validator\IValidator; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; From 0bcae13b4d9144aae7f2bf631d4e3a92733b506a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Mar 2022 11:21:48 +0100 Subject: [PATCH 08/10] fixup! Merge constraint and validator --- build/psalm-baseline-ocp.xml | 5 +++ .../Validator/Constraints/Constraint.php | 3 +- lib/public/Validator/Constraints/CssColor.php | 38 ++++++++++--------- lib/public/Validator/IConstraintValidator.php | 2 - lib/public/Validator/IValidator.php | 2 - 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/build/psalm-baseline-ocp.xml b/build/psalm-baseline-ocp.xml index 87a994ea720f8..570eb02d5de9b 100644 --- a/build/psalm-baseline-ocp.xml +++ b/build/psalm-baseline-ocp.xml @@ -16,6 +16,11 @@ \OC + + + \OC + + $this->request->server diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php index 71f2d3f692ecd..0d99457e090c8 100644 --- a/lib/public/Validator/Constraints/Constraint.php +++ b/lib/public/Validator/Constraints/Constraint.php @@ -24,12 +24,11 @@ use OCP\IL10N; use OCP\L10N\IFactory; use OCP\Validator\IConstraintValidator; -use OCP\Validator\Violation; /** * Abstract class for validation constraint. */ -abstract class Constraint implements IConstraintValidator{ +abstract class Constraint implements IConstraintValidator { protected IL10N $l10n; public function __construct() { diff --git a/lib/public/Validator/Constraints/CssColor.php b/lib/public/Validator/Constraints/CssColor.php index 22b46f05e8c78..d2024a8c5b9a0 100644 --- a/lib/public/Validator/Constraints/CssColor.php +++ b/lib/public/Validator/Constraints/CssColor.php @@ -24,6 +24,9 @@ use OCP\Validator\Violation; +/** + * Constraint that validate that a value is a CSS3 compatible color. + */ class CssColor extends Constraint { public const HEX_LONG = 'hex_long'; public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha'; @@ -73,25 +76,26 @@ class CssColor extends Constraint { private array $formats; /** - * @param array{formats?: string[], message?: string} $options + * @param string|null $message The violation message displayed to the user + * @param array|null $formats The list of allowed color formats, by default all */ - public function __construct(array $options = []) { + public function __construct(?string $message, ?array $formats) { parent::__construct(); - $this->message = $options['message'] ?? $this->l10n->t('"{{ value }}" is not a valid email address'); - $this->formats = $options['formats'] ?? [ - self::HEX_LONG, - self::HEX_LONG_WITH_ALPHA, - self::HEX_SHORT, - self::HEX_SHORT_WITH_ALPHA, - self::BASIC_NAMED_COLORS, - self::EXTENDED_NAMED_COLORS, - self::SYSTEM_COLORS, - self::KEYWORDS, - self::RGB, - self::RGBA, - self::HSL, - self::HSLA, - ]; + $this->message = $message ?? $this->l10n->t('"{{ value }}" is not a valid email address'); + $this->formats = $formats ?? [ + self::HEX_LONG, + self::HEX_LONG_WITH_ALPHA, + self::HEX_SHORT, + self::HEX_SHORT_WITH_ALPHA, + self::BASIC_NAMED_COLORS, + self::EXTENDED_NAMED_COLORS, + self::SYSTEM_COLORS, + self::KEYWORDS, + self::RGB, + self::RGBA, + self::HSL, + self::HSLA, + ]; } public function getMessage(): string { diff --git a/lib/public/Validator/IConstraintValidator.php b/lib/public/Validator/IConstraintValidator.php index c0f8b90028698..73d2b89491cf2 100644 --- a/lib/public/Validator/IConstraintValidator.php +++ b/lib/public/Validator/IConstraintValidator.php @@ -21,8 +21,6 @@ namespace OCP\Validator; -use OCP\Validator\Constraints\Constraint; - interface IConstraintValidator { /** * @param mixed The value to validate diff --git a/lib/public/Validator/IValidator.php b/lib/public/Validator/IValidator.php index 4805cd9d8f796..9e76ff909a4c0 100644 --- a/lib/public/Validator/IValidator.php +++ b/lib/public/Validator/IValidator.php @@ -21,8 +21,6 @@ namespace OCP\Validator; -use OCP\Validator\Constraints\Constraint; - interface IValidator { /** * Validate a value according to one or more constraints. From ec67ee4f873916cbe67a3dc11c24894134829506 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Mar 2022 11:44:46 +0100 Subject: [PATCH 09/10] fixup! Merge constraint and validator --- .../lib/Controller/ThemingController.php | 2 +- build/psalm-baseline.xml | 28 ++++++++----------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 795e87d3703c6..0d189aec29788 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -168,7 +168,7 @@ public function updateStylesheet($setting, $value) { break; case 'color': $violations = $this->validator->validate($value, [ - new CssColor(['message' => $this->l10n->t('The given color is invalid')]), + new CssColor($this->l10n->t('The given color is invalid')), ]); break; } diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 8b8dfbde85e66..24ba57f3d92ca 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1023,6 +1023,7 @@ get_class($res) === 'OpenSSLAsymmetricKey' + is_object($res) @@ -1962,6 +1963,10 @@ + + LDAP_OPT_PROTOCOL_VERSION + LDAP_OPT_REFERRALS + @@ -2025,15 +2030,6 @@ - - $isUnmapped - - - $result - - - bool - isset($qb) @@ -2100,9 +2096,15 @@ private function detectGroupMemberAssoc() { private function getAttributeValuesFromEntry($result, $attribute, &$known) { - + $port $port + LDAP_OPT_NETWORK_TIMEOUT + LDAP_OPT_NETWORK_TIMEOUT + LDAP_OPT_PROTOCOL_VERSION + LDAP_OPT_PROTOCOL_VERSION + LDAP_OPT_REFERRALS + LDAP_OPT_REFERRALS !isset($item['cn']) && !is_array($item['cn']) @@ -3795,12 +3797,6 @@ isAdmin - - - $sortMode - self::SORT_NONE - - string|resource From df30b14973a517ba2ed250684275acb33b5b08dd Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 14 Mar 2022 11:46:10 +0100 Subject: [PATCH 10/10] fixup! Merge constraint and validator --- lib/public/Validator/Constraints/CssColor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/Validator/Constraints/CssColor.php b/lib/public/Validator/Constraints/CssColor.php index d2024a8c5b9a0..1a7645bbee9c1 100644 --- a/lib/public/Validator/Constraints/CssColor.php +++ b/lib/public/Validator/Constraints/CssColor.php @@ -79,7 +79,7 @@ class CssColor extends Constraint { * @param string|null $message The violation message displayed to the user * @param array|null $formats The list of allowed color formats, by default all */ - public function __construct(?string $message, ?array $formats) { + public function __construct(?string $message = null, ?array $formats = null) { parent::__construct(); $this->message = $message ?? $this->l10n->t('"{{ value }}" is not a valid email address'); $this->formats = $formats ?? [