From bce871d9912b10ef2509241c7d0e119da0305f3e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:20:26 +0200 Subject: [PATCH] fix(domains): reject non-HTTP URL schemes --- bootstrap/helpers/domains.php | 50 +++++++++++++++++++++++++---- tests/Unit/IsValidDomainUrlTest.php | 7 ++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/bootstrap/helpers/domains.php b/bootstrap/helpers/domains.php index 1aa182510..f3c5359f7 100644 --- a/bootstrap/helpers/domains.php +++ b/bootstrap/helpers/domains.php @@ -6,12 +6,50 @@ function isValidDomainUrl(string $url): bool { - // PHP's FILTER_VALIDATE_URL rejects underscores in the host, but they are - // accepted by browsers, Let's Encrypt, and common Docker service naming - // (e.g. https://myapp_service.example.com). Validate against a copy with - // underscores replaced by hyphens (a valid host character) so such domains - // are not wrongly rejected; URLs without underscores are unaffected. - return filter_var(str_replace('_', '-', $url), FILTER_VALIDATE_URL) !== false; + $components = parse_url($url); + + if ($components === false) { + return false; + } + + $scheme = $components['scheme'] ?? ''; + $host = $components['host'] ?? ''; + + if (! in_array(strtolower($scheme), ['http', 'https'], true) || $host === '') { + return false; + } + + $urlToValidate = $scheme.'://'; + + if (isset($components['user'])) { + $urlToValidate .= $components['user']; + + if (isset($components['pass'])) { + $urlToValidate .= ':'.$components['pass']; + } + + $urlToValidate .= '@'; + } + + $urlToValidate .= str_replace('_', '-', $host); + + if (isset($components['port'])) { + $urlToValidate .= ':'.$components['port']; + } + + if (isset($components['path'])) { + $urlToValidate .= $components['path']; + } + + if (isset($components['query'])) { + $urlToValidate .= '?'.$components['query']; + } + + if (isset($components['fragment'])) { + $urlToValidate .= '#'.$components['fragment']; + } + + return filter_var($urlToValidate, FILTER_VALIDATE_URL) !== false; } function checkDomainUsage(ServiceApplication|Application|null $resource = null, ?string $domain = null) diff --git a/tests/Unit/IsValidDomainUrlTest.php b/tests/Unit/IsValidDomainUrlTest.php index 940eaecea..3f40646f7 100644 --- a/tests/Unit/IsValidDomainUrlTest.php +++ b/tests/Unit/IsValidDomainUrlTest.php @@ -18,5 +18,12 @@ it('rejects strings that are not valid URLs', function () { expect(isValidDomainUrl('not a url'))->toBeFalse(); expect(isValidDomainUrl('example.com'))->toBeFalse(); + expect(isValidDomainUrl('ht_tp://example.com'))->toBeFalse(); expect(isValidDomainUrl(''))->toBeFalse(); }); + +it('rejects URLs that do not use HTTP or HTTPS schemes', function () { + expect(isValidDomainUrl('ftp://example.com'))->toBeFalse(); + expect(isValidDomainUrl('javascript://example.com'))->toBeFalse(); + expect(isValidDomainUrl('data://example.com'))->toBeFalse(); +});