$i = strrpos($result, '/'); $result = $i ? substr($result, 0, $i) : ''; $path = substr_replace($path, '/', 0, 4); } else { $i = strpos($path, '/', 1) ?: \strlen($path); $result .= substr($path, 0, $i); $path = substr($path, $i); } } return $result; } /** * Merges and encodes a query array with a query string. * * @throws InvalidArgumentException When an invalid query-string value is passed */ private static function mergeQueryString(?string $queryString, array $queryArray, bool $replace): ?string { if (!$queryArray) { return $queryString; } $query = []; if (null !== $queryString) { foreach (explode('&', $queryString) as $v) { if ('' !== $v) { $k = urldecode(explode('=', $v, 2)[0]); $query[$k] = (isset($query[$k]) ? $query[$k].'&' : '').$v; } } } if ($replace) { foreach ($queryArray as $k => $v) { if (null === $v) { unset($query[$k]); } } } $queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986); $queryArray = []; if ($queryString) { if (str_contains($queryString, '%')) { // https://tools.ietf.org/html/rfc3986#section-2.3 + some chars not encoded by browsers $queryString = strtr($queryString, [ '%21' => '!', '%24' => '$', '%28' => '(', '%29' => ')', '%2A' => '*', '%2F' => '/', '%3A' => ':', '%3B' => ';', '%40' => '@', '%5B' => '[', '%5D' => ']', ]); } foreach (explode('&', $queryString) as $v) { $queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v; } } return implode('&', $replace ? array_replace($query, $queryArray) : ($query + $queryArray)); } /** * Loads proxy configuration from the same environment variables as curl when no proxy is explicitly set. */ private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array { if (null === $proxy = self::getProxyUrl($proxy, $url)) { return null; } $proxy = (parse_url($proxy) ?: []) + ['scheme' => 'http']; if (!isset($proxy['host'])) { throw new TransportException('Invalid HTTP proxy: host is missing.'); } if ('http' === $proxy['scheme']) { $proxyUrl = 'tcp://'.$proxy['host'].':'.($proxy['port'] ?? '80'); } elseif ('https' === $proxy['scheme']) { $proxyUrl = 'ssl://'.$proxy['host'].':'.($proxy['port'] ?? '443'); } else { throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme'])); } $noProxy = $noProxy ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? ''; $noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : []; return [ 'url' => $proxyUrl, 'auth' => isset($proxy['user']) ? 'Basic '.base64_encode(rawurldecode($proxy['user']).':'.rawurldecode($proxy['pass'] ?? '')) : null, 'no_proxy' => $noProxy, ]; } private static function getProxyUrl(?string $proxy, array $url): ?string { if (null !== $proxy) { return $proxy; } // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; if ('https:' === $url['scheme']) { $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy; } return $proxy; } private static function shouldBuffer(array $headers): bool { if (null === $contentType = $headers['content-type'][0] ?? null) { return false; } if (false !== $i = strpos($contentType, ';')) { $contentType = substr($contentType, 0, $i); } return $contentType && preg_match('#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i', $contentType); } }