diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php
index 046e6d7704188..3fd29618c15ca 100644
--- a/apps/oauth2/lib/Controller/SettingsController.php
+++ b/apps/oauth2/lib/Controller/SettingsController.php
@@ -39,16 +39,15 @@
use OCP\IL10N;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
+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';
@@ -57,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 (filter_var($redirectUri, FILTER_VALIDATE_URL) === false) {
+ 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/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 = '';
}
diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php
index a735dfafc2ce8..0d189aec29788 100644
--- a/apps/theming/lib/Controller/ThemingController.php
+++ b/apps/theming/lib/Controller/ThemingController.php
@@ -54,6 +54,11 @@
use OCP\IRequest;
use OCP\ITempManager;
use OCP\IURLGenerator;
+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
@@ -63,39 +68,20 @@
* @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,
@@ -108,7 +94,8 @@ public function __construct(
SCSSCacher $scssCacher,
IURLGenerator $urlGenerator,
IAppManager $appManager,
- ImageManager $imageManager
+ ImageManager $imageManager,
+ IValidator $validator
) {
parent::__construct($appName, $request);
@@ -121,6 +108,7 @@ public function __construct(
$this->urlGenerator = $urlGenerator;
$this->appManager = $appManager;
$this->imageManager = $imageManager;
+ $this->validator = $validator;
}
/**
@@ -132,52 +120,62 @@ 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 (!$this->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 (!$this->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 (!$this->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($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(fn (Violation $violation) => $violation->getMessage(), $violations)) . '.',
],
'status' => 'error'
], Http::STATUS_BAD_REQUEST);
@@ -200,14 +198,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..e6377725c8510 100644
--- a/apps/theming/lib/ThemingDefaults.php
+++ b/apps/theming/lib/ThemingDefaults.php
@@ -49,6 +49,11 @@
use OCP\IL10N;
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 {
@@ -90,6 +95,7 @@ class ThemingDefaults extends \OC_Defaults {
private $AndroidClientUrl;
/** @var string */
private $FDroidClientUrl;
+ private IValidator $validator;
/**
* ThemingDefaults constructor.
@@ -109,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;
@@ -120,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();
@@ -208,9 +216,10 @@ public function getShortFooter() {
$legalLinks = '';
$divider = '';
foreach ($links as $link) {
- if ($link['url'] !== ''
- && filter_var($link['url'], FILTER_VALIDATE_URL)
- ) {
+ if ($this->validator->isValid($link['url'], [
+ new NotBlank(),
+ new Url(),
+ ])) {
$legalLinks .= $divider . '' . $link['text'] . '';
$divider = ' · ';
@@ -230,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..34fe930db2c5a 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;
@@ -107,7 +108,8 @@ protected function setUp(): void {
$this->scssCacher,
$this->urlGenerator,
$this->appManager,
- $this->imageManager
+ $this->imageManager,
+ new Validator()
);
parent::setUp();
@@ -139,7 +141,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 +172,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..a89399ce4bc3c 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;
@@ -98,7 +99,8 @@ protected function setUp(): void {
$this->util,
$this->imageManager,
$this->appManager,
- $this->navigationManager
+ $this->navigationManager,
+ new Validator()
);
}
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/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 44a113f1238e9..24ba57f3d92ca 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -2030,15 +2030,6 @@
-
- $isUnmapped
-
-
- $result
-
-
- bool
-
isset($qb)
@@ -3806,12 +3797,6 @@
isAdmin
-
-
- $sortMode
- self::SORT_NONE
-
-
string|resource
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 86efab3ad52b2..a42557573f772 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -582,6 +582,15 @@
'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\\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',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index ea37771d63c37..9bc2935e83b98 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -611,6 +611,15 @@ 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\\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',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayText' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayText.php',
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 38720ab71c013..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,27 +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;
/**
* Class Server
@@ -1192,7 +1190,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 +1399,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 177ede1e29254..e3795194cfb98 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -59,24 +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,
@@ -339,7 +336,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 +472,9 @@ private static function findWebRoot(SystemConfig $config): string {
if ($webRoot === '') {
throw new InvalidArgumentException('overwrite.cli.url is empty');
}
- if (!filter_var($webRoot, FILTER_VALIDATE_URL)) {
+ /** @var IValidator $validator */
+ $validator = \OC::$server->get(IValidator::class);
+ if ($validator->isValid($webRoot, [new Url()])) {
throw new InvalidArgumentException('invalid value for overwrite.cli.url');
}
$webRoot = rtrim((parse_url($webRoot, PHP_URL_PATH) ?? ''), '/');
@@ -504,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/Validator.php b/lib/private/Validator/Validator.php
new file mode 100644
index 0000000000000..d9b78727a8b7c
--- /dev/null
+++ b/lib/private/Validator/Validator.php
@@ -0,0 +1,45 @@
+
+ *
+ * @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\IValidator;
+use OCP\Validator\Violation;
+
+class Validator implements IValidator {
+ public function validate($value, array $constraints): array {
+ /** @var Violation[] $violations */
+ $violations = [];
+ foreach ($constraints as $constraint) {
+ $violations = array_merge($violations, $constraint->validate($value));
+ }
+ return $violations;
+ }
+
+ public function isValid($value, array $constraints): bool {
+ foreach ($constraints as $constraint) {
+ if (count($constraint->validate($value)) > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/lib/public/Validator/Constraints/Constraint.php b/lib/public/Validator/Constraints/Constraint.php
new file mode 100644
index 0000000000000..0d99457e090c8
--- /dev/null
+++ b/lib/public/Validator/Constraints/Constraint.php
@@ -0,0 +1,37 @@
+
+ *
+ * @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;
+
+use OCP\IL10N;
+use OCP\L10N\IFactory;
+use OCP\Validator\IConstraintValidator;
+
+/**
+ * Abstract class for validation constraint.
+ */
+abstract class Constraint implements IConstraintValidator {
+ protected IL10N $l10n;
+
+ public function __construct() {
+ $this->l10n = \OC::$server->get(IFactory::class)->get('core');
+ }
+}
diff --git a/lib/public/Validator/Constraints/CssColor.php b/lib/public/Validator/Constraints/CssColor.php
new file mode 100644
index 0000000000000..1a7645bbee9c1
--- /dev/null
+++ b/lib/public/Validator/Constraints/CssColor.php
@@ -0,0 +1,128 @@
+
+ * @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;
+
+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';
+ 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 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;
+
+ /**
+ * @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 = null, ?array $formats = null) {
+ parent::__construct();
+ $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 {
+ return $this->message;
+ }
+
+ /**
+ * @return 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
new file mode 100644
index 0000000000000..ab518e22b5055
--- /dev/null
+++ b/lib/public/Validator/Constraints/Email.php
@@ -0,0 +1,66 @@
+
+ *
+ * @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;
+
+use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
+use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator;
+use OCP\Validator\Violation;
+
+class Email extends Constraint {
+ private string $message;
+ /**
+ * @param string|null $message Overwrite the default translated error message
+ * to use when the constraint is not fulfilled.
+ */
+ public function __construct(?string $message = null) {
+ parent::__construct();
+ $this->message = $message === null ? $this->l10n->t('"{{ value }}" is not a valid email address') : $message;
+ }
+
+ 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
new file mode 100644
index 0000000000000..49594d3808131
--- /dev/null
+++ b/lib/public/Validator/Constraints/Length.php
@@ -0,0 +1,126 @@
+
+ *
+ * @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;
+
+use OCP\Validator\Violation;
+
+/**
+ * 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;
+ }
+
+ 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
new file mode 100644
index 0000000000000..b6e62fe2eef88
--- /dev/null
+++ b/lib/public/Validator/Constraints/NotBlank.php
@@ -0,0 +1,60 @@
+
+ *
+ * @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;
+
+use OCP\Validator\Violation;
+
+class NotBlank extends Constraint {
+ private string $message;
+ private bool $allowNull;
+
+ /**
+ * @param string|null $message Overwrite the default translated error message
+ * to use when the constraint is not fulfilled.
+ */
+ public function __construct(bool $allowNull = false, ?string $message = null) {
+ parent::__construct();
+ $this->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;
+ }
+
+ 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
new file mode 100644
index 0000000000000..2ea22e3f225b4
--- /dev/null
+++ b/lib/public/Validator/Constraints/Url.php
@@ -0,0 +1,94 @@
+
+ *
+ * @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;
+
+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.
+ */
+ public function __construct(bool $relativeUrl = false, array $protocols = ['http', 'https'], ?string $message = null) {
+ parent::__construct();
+ $this->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;
+ }
+
+ 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/public/Validator/IConstraintValidator.php b/lib/public/Validator/IConstraintValidator.php
new file mode 100644
index 0000000000000..73d2b89491cf2
--- /dev/null
+++ b/lib/public/Validator/IConstraintValidator.php
@@ -0,0 +1,30 @@
+
+ *
+ * @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;
+
+interface IConstraintValidator {
+ /**
+ * @param mixed The value to validate
+ * @return Violation[] An array of violations
+ */
+ public function validate($value): array;
+}
diff --git a/lib/public/Validator/IValidator.php b/lib/public/Validator/IValidator.php
new file mode 100644
index 0000000000000..9e76ff909a4c0
--- /dev/null
+++ b/lib/public/Validator/IValidator.php
@@ -0,0 +1,43 @@
+
+ *
+ * @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;
+
+interface IValidator {
+ /**
+ * Validate a value according to one or more constraints.
+ *
+ * @param mixed $value The value to validate
+ * @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.
+ */
+ 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 IConstraintValidator[] $constraints The validator constraints for the value
+ * @return bool Whether the value is valid
+ */
+ public function isValid($value, array $constraints): bool;
+}
diff --git a/lib/public/Validator/Violation.php b/lib/public/Validator/Violation.php
new file mode 100644
index 0000000000000..632810100aff1
--- /dev/null
+++ b/lib/public/Validator/Violation.php
@@ -0,0 +1,65 @@
+
+ *
+ * @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;
+
+/**
+ * This object represents a constraint violation when validating a value.
+ */
+class Violation {
+ private string $message;
+ private array $parameters;
+
+ public function __construct(string $message) {
+ $this->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
+ ];
+ }
+}