diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php
index 2850012862988..e32781c590b5b 100644
--- a/apps/oauth2/lib/Controller/SettingsController.php
+++ b/apps/oauth2/lib/Controller/SettingsController.php
@@ -81,7 +81,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 1638f5bd95799..f0f9f258037dd 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
@@ -142,7 +143,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;
@@ -150,7 +151,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;
@@ -158,7 +159,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;
@@ -199,14 +200,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);
- }
-
/**
* @return DataResponse
* @throws NotPermittedException
diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php
index ee0ad85cd06c0..3a3682b14e8c9 100644
--- a/apps/theming/lib/ThemingDefaults.php
+++ b/apps/theming/lib/ThemingDefaults.php
@@ -48,6 +48,7 @@
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IURLGenerator;
+use OCP\Util as OCPUtil;
class ThemingDefaults extends \OC_Defaults {
@@ -157,7 +158,7 @@ public function getBaseUrl() {
}
public function getSlogan(?string $lang = null) {
- return \OCP\Util::sanitizeHTML($this->config->getAppValue('theming', 'slogan', parent::getSlogan($lang)));
+ return OCPUtil::sanitizeHTML($this->config->getAppValue('theming', 'slogan', parent::getSlogan($lang)));
}
public function getImprintUrl() {
@@ -203,7 +204,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 a4873e63aa980..83a6f84c00bdf 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -474,7 +474,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 5165846707a66..c54b3982845e8 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -513,4 +513,36 @@ public static function needUpgrade() {
}
return self::$needUpgradeCache;
}
+
+ /**
+ * 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 23.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;
+ }
}