mirror of
git://f0xx.org/ac/ac-ms-url-shortener
synced 2026-07-29 01:38:35 +03:00
61 lines
2.2 KiB
PHP
61 lines
2.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
final class UrlNormalizer {
|
|
private const MAX_LEN = 2048;
|
|
|
|
public static function normalize(string $raw): string {
|
|
$url = trim($raw);
|
|
if ($url === '') {
|
|
throw new InvalidArgumentException('empty url');
|
|
}
|
|
if (strlen($url) > self::MAX_LEN) {
|
|
throw new InvalidArgumentException('url too long');
|
|
}
|
|
$parts = parse_url($url);
|
|
if ($parts === false || !isset($parts['scheme'], $parts['host'])) {
|
|
throw new InvalidArgumentException('malformed url');
|
|
}
|
|
$scheme = strtolower((string) $parts['scheme']);
|
|
if ($scheme !== 'http' && $scheme !== 'https') {
|
|
throw new InvalidArgumentException('unsupported scheme');
|
|
}
|
|
$host = strtolower((string) $parts['host']);
|
|
self::assertHostAllowed($host);
|
|
|
|
$port = isset($parts['port']) ? ':' . (int) $parts['port'] : '';
|
|
$path = $parts['path'] ?? '';
|
|
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
|
|
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
|
|
|
|
return $scheme . '://' . $host . $port . $path . $query . $fragment;
|
|
}
|
|
|
|
public static function originHash(string $normalized): string {
|
|
return hash('sha256', $normalized);
|
|
}
|
|
|
|
private static function assertHostAllowed(string $host): void {
|
|
if ($host === 'localhost' || str_ends_with($host, '.local') || str_ends_with($host, '.internal')) {
|
|
throw new InvalidArgumentException('blocked host');
|
|
}
|
|
if ($host === 'metadata.google.internal' || $host === 'metadata') {
|
|
throw new InvalidArgumentException('blocked host');
|
|
}
|
|
if (filter_var($host, FILTER_VALIDATE_IP)) {
|
|
self::assertIpAllowed($host);
|
|
return;
|
|
}
|
|
$resolved = @gethostbyname($host);
|
|
if ($resolved !== $host && filter_var($resolved, FILTER_VALIDATE_IP)) {
|
|
self::assertIpAllowed($resolved);
|
|
}
|
|
}
|
|
|
|
private static function assertIpAllowed(string $ip): void {
|
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
throw new InvalidArgumentException('blocked host');
|
|
}
|
|
}
|
|
}
|