MCPcopy Create free account
hub / github.com/Rello/analytics / ExternalUrlValidator

Class ExternalUrlValidator

lib/Security/ExternalUrlValidator.php:9–86  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

7namespace OCA\Analytics\Security;
8
9class ExternalUrlValidator {
10 public static function validate(string $url): ?string {
11 $url = trim($url);
12 if ($url === '') {
13 return 'External URL is empty';
14 }
15
16 $parts = parse_url($url);
17 if (!is_array($parts) || !isset($parts['scheme'], $parts['host'])) {
18 return 'External URL is invalid';
19 }
20
21 $scheme = strtolower((string)$parts['scheme']);
22 if (!in_array($scheme, ['http', 'https'], true)) {
23 return 'External URL scheme is not allowed';
24 }
25
26 $host = strtolower(rtrim((string)$parts['host'], '.'));
27 if ($host === '' || $host === 'localhost' || str_ends_with($host, '.localhost')) {
28 return 'External URL host is not allowed';
29 }
30
31 $addresses = self::resolveHost($host);
32 if ($addresses === []) {
33 return 'External URL host could not be resolved';
34 }
35
36 foreach ($addresses as $address) {
37 if (!self::isPublicIp($address)) {
38 return 'External URL resolves to a private or reserved address';
39 }
40 }
41
42 return null;
43 }
44
45 public static function isAllowed(string $url): bool {
46 return self::validate($url) === null;
47 }
48
49 /**
50 * @return string[]
51 */
52 private static function resolveHost(string $host): array {
53 if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
54 return [$host];
55 }
56
57 $addresses = [];
58 $ipv4 = gethostbynamel($host);
59 if (is_array($ipv4)) {
60 $addresses = array_merge($addresses, $ipv4);
61 }
62
63 $ipv6 = dns_get_record($host, DNS_AAAA);
64 if (is_array($ipv6)) {
65 foreach ($ipv6 as $record) {
66 if (isset($record['ipv6'])) {

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected