mode = $mode; $response = $this->fetch( $url ); if ( $response['status'] > 0 ) { return $mode; } } $this->mode = self::DIRECT; return null; } /** * Fetches one page. Redirects are not followed; the caller decides what they mean. * * @param string $url Page URL. * @return array{status: int, headers: array, body: string, error: string} */ public function fetch( string $url ): array { // The scan token and the loopback route are only for the site itself. An address on // another host (for example a sitemap listed in robots.txt) is fetched like any other page. if ( ! self::is_own( $url ) ) { return $this->http->request( 'GET', $url, array( 'timeout' => self::TIMEOUT, 'headers' => array( 'Cache-Control' => 'no-cache' ), ) ); } $args = array( 'method' => 'GET', 'timeout' => self::TIMEOUT, 'headers' => array( ScanToken::HEADER => ScanToken::current(), 'Cache-Control' => 'no-cache', ), ); if ( self::LOOPBACK !== $this->mode ) { return $this->http->request( 'GET', $url, $args ); } $parts = wp_parse_url( $url ); if ( ! is_array( $parts ) || ! isset( $parts['host'], $parts['scheme'] ) ) { return $this->http->request( 'GET', $url, $args ); } $port = $parts['port'] ?? ( 'https' === $parts['scheme'] ? 443 : 80 ); $resolve = static function ( $handle ) use ( $parts, $port ): void { // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt -- Requests exposes the handle only here. curl_setopt( $handle, CURLOPT_RESOLVE, array( $parts['host'] . ':' . $port . ':127.0.0.1' ) ); }; $args['sslverify'] = false; add_action( 'http_api_curl', $resolve ); try { return $this->http->request( 'GET', $url, $args ); } finally { remove_action( 'http_api_curl', $resolve ); } } /** * Whether an address is on the site's own host. * * @param string $url Address. */ private static function is_own( string $url ): bool { $own = wp_parse_url( home_url(), PHP_URL_HOST ); $host = wp_parse_url( $url, PHP_URL_HOST ); return is_string( $own ) && is_string( $host ) && strtolower( $own ) === strtolower( $host ); } /** * Current mode. */ public function mode(): string { return $this->mode; } }