tmp = (string) tempnam(sys_get_temp_dir(), 'lit-fetch'); $this->errorLog = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); $this->previousErrorLog = ini_set('error_log', $this->errorLog); Functions\when('wp_parse_url')->alias('parse_url'); Functions\when('wp_http_validate_url')->returnArg(); Functions\when('wp_remote_retrieve_response_code')->alias(static fn (array $r): int => $r['response']['code']); } protected function tearDown(): void { ini_set('error_log', (string) $this->previousErrorLog); foreach ([$this->tmp, $this->errorLog] as $file) { if (is_file($file)) { unlink($file); } } parent::tearDown(); } /** @param list $addresses What every host name resolves to (no DNS in unit tests). */ private function fetcher(array $addresses = ['93.184.216.34']): RemoteFileFetcher { return new RemoteFileFetcher(static fn (string $host): array => $addresses); } public function testOnlyPublicHttpUrlsAreAllowed(): void { self::assertTrue($this->fetcher()->allows('https://fonts.example.com/inter.woff2')); self::assertTrue($this->fetcher()->allows('HTTP://fonts.example.com/inter.woff2')); self::assertFalse($this->fetcher()->allows('ftp://fonts.example.com/inter.woff2')); self::assertFalse($this->fetcher()->allows('file:///etc/passwd')); self::assertFalse($this->fetcher()->allows('http://169.254.169.254/latest/meta-data/')); self::assertFalse($this->fetcher(['10.0.0.5'])->allows('https://intern.example.com/x.woff2')); self::assertFalse($this->fetcher([])->allows('https://nirgends.example.com/x.woff2')); Functions\when('wp_http_validate_url')->justReturn(false); self::assertFalse($this->fetcher()->allows('https://fonts.example.com/inter.woff2')); } public function testTheBodyIsStreamedIntoTheTempFileAndCutOffOneByteAboveTheLimit(): void { Functions\expect('wp_safe_remote_get')->once()->with( 'https://fonts.example.com/inter.woff2', Mockery::on(function (array $args): bool { file_put_contents($args['filename'], 'wOF2DATA'); return $args['filename'] === $this->tmp && $args['stream'] === true && $args['timeout'] === 30 && $args['limit_response_size'] === 2048 + 1; }) )->andReturn(['response' => ['code' => 200], 'headers' => ['content-type' => 'font/woff2']]); $response = $this->fetcher()->fetch('https://fonts.example.com/inter.woff2', $this->tmp, 2048, 'font upload'); self::assertSame(['code' => 200], $response['response'] ?? null); self::assertSame('wOF2DATA', file_get_contents($this->tmp)); } public function testAFailedRequestIsNullAndOnlyTheLogNamesTheReasonAndTheContext(): void { Functions\when('wp_safe_remote_get')->justReturn(new WP_Error('http_request_failed', 'Connection refused')); $response = $this->fetcher()->fetch( 'https://nutzer:geheim@fonts.example.com:8443/inter.woff2?token=abc', $this->tmp, 2048, 'font upload' ); $log = (string) file_get_contents($this->errorLog); self::assertNull($response); self::assertStringContainsString('font upload from https://fonts.example.com:8443/inter.woff2', $log); self::assertStringContainsString('Connection refused', $log); self::assertStringNotContainsString('geheim', $log); self::assertStringNotContainsString('token=abc', $log); } public function testAnHttpErrorStatusIsNull(): void { Functions\when('wp_safe_remote_get')->justReturn(['response' => ['code' => 404], 'headers' => []]); self::assertNull($this->fetcher()->fetch('https://fonts.example.com/fehlt.woff2', $this->tmp, 2048, 'x')); self::assertStringContainsString('HTTP status 404', (string) file_get_contents($this->errorLog)); } public function testTheIpv4AndRedirectHooksLiveOnlyDuringTheRequest(): void { $listener = null; Actions\expectAdded('http_api_curl')->once(); Actions\expectAdded('requests-requests.before_redirect')->once() ->whenHappen(static function (callable $callback) use (&$listener): void { $listener = $callback; }); Functions\when('wp_safe_remote_get')->justReturn(['response' => ['code' => 200], 'headers' => []]); $this->fetcher()->fetch('https://fonts.example.com/inter.woff2', $this->tmp, 2048, 'x'); self::assertFalse(has_action('http_api_curl')); self::assertFalse(has_action('requests-requests.before_redirect')); self::assertIsCallable($listener); $listener('https://cdn.example.com/inter.woff2'); // public: no exception $this->expectException(\WpOrg\Requests\Exception::class); $listener('http://127.0.0.1/inter.woff2'); } }