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

Class ExternalUrlValidator

lib/Security/ExternalUrlValidator.php:9–95  ·  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 if (isset($parts['user']) || isset($parts['pass'])) {
21 return 'Credentials in external URLs are not allowed';
22 }
23
24 $scheme = strtolower((string)$parts['scheme']);
25 if (!in_array($scheme, ['http', 'https'], true)) {
26 return 'External URL scheme is not allowed';
27 }
28
29 $host = strtolower(rtrim((string)$parts['host'], '.'));
30 if (str_starts_with($host, '[') && str_ends_with($host, ']')) {
31 $host = substr($host, 1, -1);
32 }
33 if ($host === '' || $host === 'localhost' || str_ends_with($host, '.localhost')) {
34 return 'External URL host is not allowed';
35 }
36
37 $addresses = self::resolveHost($host);
38 if ($addresses === []) {
39 return 'External URL host could not be resolved';
40 }
41
42 foreach ($addresses as $address) {
43 if (!self::isPublicIp($address)) {
44 return 'External URL resolves to a private or reserved address';
45 }
46 }
47
48 return null;
49 }
50
51 public static function isAllowed(string $url): bool {
52 return self::validate($url) === null;
53 }
54
55 /**
56 * @return string[]
57 */
58 private static function resolveHost(string $host): array {
59 if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
60 return [$host];
61 }
62
63 $addresses = [];
64 $ipv4 = @gethostbynamel($host);
65 if (is_array($ipv4)) {
66 $addresses = array_merge($addresses, $ipv4);

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected