* Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; use Composer\Downloader\TransportException; use Composer\Util\NoProxyPattern; use Composer\Util\Url; /** * @internal * @author John Stevenson */ class ProxyManager { /** @var ?string */ private $error = null; /** @var array{http: ?string, https: ?string} */ private $fullProxy; /** @var array{http: ?string, https: ?string} */ private $safeProxy; /** @var array{http: array{options: mixed[]|null}, https: array{options: mixed[]|null}} */ private $streams; /** @var bool */ private $hasProxy; /** @var ?string */ private $info = null; /** @var ?string */ private $lastProxy = null; /** @var ?NoProxyPattern */ private $noProxyHandler = null; /** @var ?ProxyManager */ private static $instance = null; private function __construct() { $this->fullProxy = $this->safeProxy = array( 'http' => null, 'https' => null, ); $this->streams['http'] = $this->streams['https'] = array( 'options' => null, ); $this->hasProxy = false; $this->initProxyData(); } /** * @return ProxyManager */ public static function getInstance() { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Clears the persistent instance * * @return void */ public static function reset() { self::$instance = null; } /** * Returns a RequestProxy instance for the request url * * @param string $requestUrl * @return RequestProxy */ public function getProxyForRequest($requestUrl) { if ($this->error) { throw new TransportException('Unable to use a proxy: '.$this->error); } $scheme = parse_url($requestUrl, PHP_URL_SCHEME) ?: 'http'; $proxyUrl = ''; $options = array(); $formattedProxyUrl = ''; if ($this->hasProxy && in_array($scheme, array('http', 'https'), true) && $this->fullProxy[$scheme]) { if ($this->noProxy($requestUrl)) { $formattedProxyUrl = 'excluded by no_proxy'; } else { $proxyUrl = $this->fullProxy[$scheme]; $options = $this->streams[$scheme]['options']; ProxyHelper::setRequestFullUri($requestUrl, $options); $formattedProxyUrl = $this->safeProxy[$scheme]; } } return new RequestProxy($proxyUrl, $options, $formattedProxyUrl); } /** * Returns true if a proxy is being used * * @return bool If false any error will be in $message */ public function isProxying() { return $this->hasProxy; } /** * Returns proxy configuration info which can be shown to the user * * @return string|null Safe proxy URL or an error message if setting up proxy failed or null if no proxy was configured */ public function getFormattedProxy() { return $this->hasProxy ? $this->info : $this->error; } /** * Initializes proxy values from the environment * * @return void */ private function initProxyData() { try { list($httpProxy, $httpsProxy, $noProxy) = ProxyHelper::getProxyData(); } catch (\RuntimeException $e) { $this->error = $e->getMessage(); return; } $info = array(); if ($httpProxy) { $info[] = $this->setData($httpProxy, 'http'); } if ($httpsProxy) { $info[] = $this->setData($httpsProxy, 'https'); } if ($this->hasProxy) { $this->info = implode(', ', $info); if ($noProxy) { $this->noProxyHandler = new NoProxyPattern($noProxy); } } } /** * Sets initial data * * @param non-empty-string $url Proxy url * @param 'http'|'https' $scheme Environment variable scheme * * @return non-empty-string */ private function setData($url, $scheme) { $safeProxy = Url::sanitize($url); $this->fullProxy[$scheme] = $url; $this->safeProxy[$scheme] = $safeProxy; $this->streams[$scheme]['options'] = ProxyHelper::getContextOptions($url); $this->hasProxy = true; return sprintf('%s=%s', $scheme, $safeProxy); } /** * Returns true if a url matches no_proxy value * * @param string $requestUrl * @return bool */ private function noProxy($requestUrl) { if ($this->noProxyHandler) { if ($this->noProxyHandler->test($requestUrl)) { $this->lastProxy = 'excluded by no_proxy'; return true; } } return false; } }