$hosts */ public function __construct( private readonly array $hosts, private readonly bool $httpsOnly = true, private readonly bool $loopback = false, ) { } public function allows(string $url): bool { $scheme = strtolower((string) wp_parse_url($url, PHP_URL_SCHEME)); $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST)); if ($scheme !== 'https' && ($this->httpsOnly || $scheme !== 'http')) { return false; } return in_array($host, array_map('strtolower', $this->hosts), true); } public function send(string $method, string $url, array $headers = [], string $body = '', int $timeout = 60): array { if (!$this->allows($url)) { return [ 'status' => 0, 'body' => '', 'error' => sprintf( /* translators: %s: a host name */ __('Blocked a request to %s: Freshet Backup only connects to Dropbox and to this site.', 'freshet-backup'), (string) wp_parse_url($url, PHP_URL_HOST) ), ]; } $args = [ 'method' => $method, 'headers' => $headers, 'body' => $body, 'timeout' => $timeout, 'redirection' => 0, ]; if ($this->loopback) { // A loopback to the site itself, the same request core's own health checks make. $args['sslverify'] = apply_filters('https_local_ssl_verify', false); $args['limit_response_size'] = 65536; } // Redirects are followed here rather than by the HTTP API, so a hop to // another host is refused like any other URL (a loopback commonly // bounces http -> https or to the www. host first). for ($hop = 0; ; $hop++) { $response = wp_remote_request($url, $args); if (is_wp_error($response)) { return ['status' => 0, 'body' => '', 'error' => $response->get_error_message()]; } $status = (int) wp_remote_retrieve_response_code($response); $location = (string) wp_remote_retrieve_header($response, 'location'); if (!$this->loopback || $status < 300 || $status > 399 || $location === '' || $hop >= 3) { break; } $url = \WP_Http::make_absolute_url($location, $url); if (!$this->allows($url)) { break; } } return [ 'status' => $status, 'body' => (string) wp_remote_retrieve_body($response), 'error' => null, ]; } }