diff --git a/src/Input/UrlInputSource.php b/src/Input/UrlInputSource.php index e3a49364..fb81f20c 100644 --- a/src/Input/UrlInputSource.php +++ b/src/Input/UrlInputSource.php @@ -30,15 +30,7 @@ public function __construct(string $url) /** * Validates that a URL is safe to fetch. * - * Rejects URLs with: - * - non-HTTPS schemes, - * - embedded user credentials, - * - loopback hostnames (localhost, *.localhost), - * - literal loopback, link-local, or private-network IP addresses. - * - * Note: DNS resolution is not performed — a hostname that resolves to a - * private IP will not be caught here. - * + * Rejects URLs with non-HTTPS schemes. * @param string $url URL to validate. * @throws MindeeSourceException Throws if the URL is invalid or unsafe. */ @@ -52,55 +44,6 @@ private function validateUrl(string $url): void if (!isset($parsed['scheme']) || strtolower($parsed['scheme']) !== 'https') { throw new MindeeSourceException('URL must be HTTPS', ErrorCode::USER_INPUT_ERROR); } - - if (!empty($parsed['user']) || !empty($parsed['pass'])) { - throw new MindeeSourceException( - 'URL must not embed user credentials', - ErrorCode::USER_INPUT_ERROR - ); - } - - $host = strtolower($parsed['host'] ?? ''); - if ($host === '') { - throw new MindeeSourceException('URL is missing a host', ErrorCode::USER_INPUT_ERROR); - } - - // Strip IPv6 brackets - $host = preg_replace('/^\[|]$/', '', $host); - - if ( - $host === 'localhost' - || str_ends_with((string) $host, '.localhost') - || $host === 'ip6-localhost' - || $host === 'ip6-loopback' - ) { - throw new MindeeSourceException( - 'URL host is a loopback address', - ErrorCode::USER_INPUT_ERROR - ); - } - - if ( - $host === '::1' - || preg_match('/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', (string) $host) - ) { - throw new MindeeSourceException( - 'URL host is a loopback or private address', - ErrorCode::USER_INPUT_ERROR - ); - } - - if ( - preg_match('/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', (string) $host) - || preg_match('/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/', (string) $host) - || preg_match('/^192\.168\.\d{1,3}\.\d{1,3}$/', (string) $host) - || preg_match('/^169\.254\.\d{1,3}\.\d{1,3}$/', (string) $host) - ) { - throw new MindeeSourceException( - 'URL host is a loopback or private address', - ErrorCode::USER_INPUT_ERROR - ); - } } /** diff --git a/tests/Input/UrlInputSourceTest.php b/tests/Input/UrlInputSourceTest.php index 85fb53db..b72c3827 100644 --- a/tests/Input/UrlInputSourceTest.php +++ b/tests/Input/UrlInputSourceTest.php @@ -27,7 +27,7 @@ protected function tearDown(): void putenv('MINDEE_API_KEY=' . $this->oldKey); } - public function testInputFromHttpShouldNotThrow(): void + public function testInputFromHttpsShouldNotThrow(): void { $inputDoc = new UrlInputSource("https://example.com/invoice.pdf"); self::assertInstanceOf(UrlInputSource::class, $inputDoc); @@ -39,69 +39,6 @@ public function testInputFromHttpShouldThrow(): void new UrlInputSource(url: "http://example.com/invoice.pdf"); } - public function testRejectsEmbeddedCredentials(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL must not embed user credentials'); - new UrlInputSource('https://user:pass@example.com/invoice.pdf'); - } - - public function testRejectsLocalhostHostname(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback address'); - new UrlInputSource('https://localhost/invoice.pdf'); - } - - public function testRejectsDotLocalhostHostname(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback address'); - new UrlInputSource('https://foo.localhost/invoice.pdf'); - } - - public function testRejectsLoopbackIpv4(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://127.0.0.1/invoice.pdf'); - } - - public function testRejectsLoopbackIpv6(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://[::1]/invoice.pdf'); - } - - public function testRejectsPrivateRfc1918Class10(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://10.0.0.1/invoice.pdf'); - } - - public function testRejectsPrivateRfc1918Class172(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://172.16.0.1/invoice.pdf'); - } - - public function testRejectsPrivateRfc1918Class192(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://192.168.1.1/invoice.pdf'); - } - - public function testRejectsLinkLocalAddress(): void - { - $this->expectException(MindeeSourceException::class); - $this->expectExceptionMessage('URL host is a loopback or private address'); - new UrlInputSource('https://169.254.0.1/invoice.pdf'); - } - public function testDownloadFileFails(): void { $dummyAddress = "addressthatdoesntworkforcipurposes";