src/Composer/Util/RemoteFilesystem.php
protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
    {
        if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
            $originUrl = 'github.com';
        }

        // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
        // is the host without the path, so we look for the registered gitlab-domains matching the host here
        if (
            $this->config
            && is_array($this->config->get('gitlab-domains'))
            && false === strpos($originUrl, '/')
            && !in_array($originUrl, $this->config->get('gitlab-domains'))
        ) {
            foreach ($this->config->get('gitlab-domains') as $gitlabDomain) {
                if (0 === strpos($gitlabDomain, $originUrl)) {
                    $originUrl = $gitlabDomain;
                    break;
                }
            }
            unset($gitlabDomain);
        }

        $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $this->retryAuthFailure = true;
        $this->lastHeaders = array();
        $this->redirects = 1; // The first request counts.

        // capture username/password from URL if there is one
        if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) {
            $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];

            unset($tempAdditionalOptions['retry-auth-failure']);
        }

        $isRedirect = false;
        if (isset($tempAdditionalOptions['redirects'])) {
            $this->redirects = $tempAdditionalOptions['redirects'];
            $isRedirect = true;

            unset($tempAdditionalOptions['redirects']);
        }

        $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
        unset($tempAdditionalOptions);

        $origFileUrl = $fileUrl;

        if (isset($options['github-token'])) {
            // only add the access_token if it is actually a github URL (in case we were redirected to S3)
            if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) {
                $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
            }
            unset($options['github-token']);
        }

        if (isset($options['gitlab-token'])) {
            $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
            unset($options['gitlab-token']);
        }

        if (isset($options['http'])) {
            $options['http']['ignore_errors'] = true;
        }

        if ($this->degradedMode && substr($fileUrl, 0, 26) === 'http://repo.packagist.org/') {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));

        $actualContextOptions = stream_context_get_options($ctx);
        $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $actualContextOptions);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) {
            $this->config->prohibitUrlByConfig($fileUrl, $this->io);
        }

        if ($this->progress && !$isRedirect) {
            $this->io->writeError("Downloading (<comment>connecting...</comment>)", false);
        }

        $errorMessage = '';
        $errorCode = 0;
        $result = false;
        set_error_handler(function ($code, $msg) use (&$errorMessage) {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
        });
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);

            if (!empty($http_response_header[0])) {
                $statusCode = $this->findStatusCode($http_response_header);
                if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    self::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), null, $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null;
            if ($contentLength && Platform::strlen($result) < $contentLength) {
                // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
                $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
                $e->setResponse($result);
                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG);

                throw $e;
            }

            if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) {
                // Emulate fingerprint validation on PHP < 5.6
                $params = stream_context_get_params($ctx);
                $expectedPeerFingerprint = $options['ssl']['peer_fingerprint'];
                $peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']);

                // Constant time compare??!
                if ($expectedPeerFingerprint !== $peerFingerprint) {
                    throw new TransportException('Peer fingerprint did not match');
                }
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && !empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($result);
            }
            $result = false;
        }
        if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
        }
        restore_error_handler();
        if (isset($e) && !$this->retry) {
            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        $statusCode = null;
        $contentType = null;
        $locationHeader = null;
        if (!empty($http_response_header[0])) {
            $statusCode = $this->findStatusCode($http_response_header);
            $contentType = $this->findHeaderValue($http_response_header, 'content-type');
            $locationHeader = $this->findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr($locationHeader, -4) !== '.zip')
            && $contentType && preg_match('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // handle 3xx redirects, 304 Not Modified is excluded
        $hasFollowedRedirect = false;
        if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
            $hasFollowedRedirect = true;
            $result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
        }

        // fail 4xx and 5xx responses and capture the response
        if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
            if (!$this->retry) {
                if ($this->progress && !$this->retry && !$isRedirect) {
                    $this->io->overwriteError("Downloading (<error>failed</error>)", false);
                }

                $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode);
                $e->setHeaders($http_response_header);
                $e->setResponse($result);
                $e->setStatusCode($statusCode);
                throw $e;
            }
            $result = false;
        }

        if ($this->progress && !$this->retry && !$isRedirect) {
            $this->io->overwriteError("Downloading (".($result === false ? '<error>failed</error>' : '<comment>100%</comment>').")", false);
        }

        // decode gzip
        if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
            $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                try {
                    if (PHP_VERSION_ID >= 50400) {
                        $result = zlib_decode($result);
                    } else {
                        // work around issue with gzuncompress & co that do not work with all gzip checksums
                        $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
                    }

                    if (!$result) {
                        throw new TransportException('Failed to decode zlib stream');
                    }
                } catch (\Exception $e) {
                    if ($this->degradedMode) {
                        throw $e;
                    }

                    $this->degradedMode = true;
                    $this->io->writeError(array(
                        '',
                        '<error>Failed to decode response: '.$e->getMessage().'</error>',
                        '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                    ));

                    return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
                }
            }
        }

        // handle copy command if download was successful
        if (false !== $result && null !== $fileName && !$isRedirect) {
            if ('' === $result) {
                throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
            }

            $errorMessage = '';
            set_error_handler(function ($code, $msg) use (&$errorMessage) {
                if ($errorMessage) {
                    $errorMessage .= "\n";
                }
                $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);
            });
            $result = (bool) file_put_contents($fileName, $result);
            restore_error_handler();
            if (false === $result) {
                throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage);
            }
        }

        // Handle SSL cert match issues
        if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) {
            // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6
            // The procedure to handle sAN for older PHP's is:
            //
            // 1. Open socket to remote server and fetch certificate (disabling peer
            //    validation because PHP errors without giving up the certificate.)
            //
            // 2. Verifying the domain in the URL against the names in the sAN field.
            //    If there is a match record the authority [host/port], certificate
            //    common name, and certificate fingerprint.
            //
            // 3. Retry the original request but changing the CN_match parameter to
            //    the common name extracted from the certificate in step 2.
            //
            // 4. To prevent any attempt at being hoodwinked by switching the
            //    certificate between steps 2 and 3 the fingerprint of the certificate
            //    presented in step 3 is compared against the one recorded in step 2.
            if (CaBundle::isOpensslParseSafe()) {
                $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options);

                if ($certDetails) {
                    $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;

                    $this->retry = true;
                }
            } else {
                $this->io->writeError('');
                $this->io->writeError(sprintf(
                    '<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
                    PHP_VERSION
                ));
            }
        }

        if ($this->retry) {
            $this->retry = false;

            $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);

            if ($this->storeAuth && $this->config) {
                $authHelper = new AuthHelper($this->io, $this->config);
                $authHelper->storeAuth($this->originUrl, $this->storeAuth);
                $this->storeAuth = false;
            }

            return $result;
        }

        if (false === $result) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
            if (!empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
            }

            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        if (!empty($http_response_header[0])) {
            $this->lastHeaders = $http_response_header;
        }

        return $result;
    }
protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
    {
        if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
            $originUrl = 'github.com';
        }

        // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
        // is the host without the path, so we look for the registered gitlab-domains matching the host here
        if (
            $this->config
            && is_array($this->config->get('gitlab-domains'))
            && false === strpos($originUrl, '/')
            && !in_array($originUrl, $this->config->get('gitlab-domains'))
        ) {
            foreach ($this->config->get('gitlab-domains') as $gitlabDomain) {
                if (0 === strpos($gitlabDomain, $originUrl)) {
                    $originUrl = $gitlabDomain;
                    break;
                }
            }
            unset($gitlabDomain);
        }

        $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $this->retryAuthFailure = true;
        $this->lastHeaders = array();
        $this->redirects = 1; // The first request counts.

        // capture username/password from URL if there is one
        if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) {
            $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];

            unset($tempAdditionalOptions['retry-auth-failure']);
        }

        $isRedirect = false;
        if (isset($tempAdditionalOptions['redirects'])) {
            $this->redirects = $tempAdditionalOptions['redirects'];
            $isRedirect = true;

            unset($tempAdditionalOptions['redirects']);
        }

        $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
        unset($tempAdditionalOptions);

        $origFileUrl = $fileUrl;

        if (isset($options['github-token'])) {
            // only add the access_token if it is actually a github URL (in case we were redirected to S3)
            if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) {
                $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
            }
            unset($options['github-token']);
        }

        if (isset($options['gitlab-token'])) {
            $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
            unset($options['gitlab-token']);
        }

        if (isset($options['http'])) {
            $options['http']['ignore_errors'] = true;
        }

        if ($this->degradedMode && substr($fileUrl, 0, 26) === 'http://repo.packagist.org/') {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));

        $actualContextOptions = stream_context_get_options($ctx);
        $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $actualContextOptions);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) {
            $this->config->prohibitUrlByConfig($fileUrl, $this->io);
        }

        if ($this->progress && !$isRedirect) {
            $this->io->writeError("Downloading (<comment>connecting...</comment>)", false);
        }

        $errorMessage = '';
        $errorCode = 0;
        $result = false;
        set_error_handler(function ($code, $msg) use (&$errorMessage) {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
        });
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);

            if (!empty($http_response_header[0])) {
                $statusCode = $this->findStatusCode($http_response_header);
                if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    self::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), null, $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null;
            if ($contentLength && Platform::strlen($result) < $contentLength) {
                // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
                $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
                $e->setResponse($result);
                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG);

                throw $e;
            }

            if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) {
                // Emulate fingerprint validation on PHP < 5.6
                $params = stream_context_get_params($ctx);
                $expectedPeerFingerprint = $options['ssl']['peer_fingerprint'];
                $peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']);

                // Constant time compare??!
                if ($expectedPeerFingerprint !== $peerFingerprint) {
                    throw new TransportException('Peer fingerprint did not match');
                }
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && !empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($result);
            }
            $result = false;
        }
        if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
        }
        restore_error_handler();
        if (isset($e) && !$this->retry) {
            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        $statusCode = null;
        $contentType = null;
        $locationHeader = null;
        if (!empty($http_response_header[0])) {
            $statusCode = $this->findStatusCode($http_response_header);
            $contentType = $this->findHeaderValue($http_response_header, 'content-type');
            $locationHeader = $this->findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr($locationHeader, -4) !== '.zip')
            && $contentType && preg_match('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // handle 3xx redirects, 304 Not Modified is excluded
        $hasFollowedRedirect = false;
        if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
            $hasFollowedRedirect = true;
            $result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
        }

        // fail 4xx and 5xx responses and capture the response
        if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
            if (!$this->retry) {
                if ($this->progress && !$this->retry && !$isRedirect) {
                    $this->io->overwriteError("Downloading (<error>failed</error>)", false);
                }

                $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode);
                $e->setHeaders($http_response_header);
                $e->setResponse($result);
                $e->setStatusCode($statusCode);
                throw $e;
            }
            $result = false;
        }

        if ($this->progress && !$this->retry && !$isRedirect) {
            $this->io->overwriteError("Downloading (".($result === false ? '<error>failed</error>' : '<comment>100%</comment>').")", false);
        }

        // decode gzip
        if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
            $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                try {
                    if (PHP_VERSION_ID >= 50400) {
                        $result = zlib_decode($result);
                    } else {
                        // work around issue with gzuncompress & co that do not work with all gzip checksums
                        $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
                    }

                    if (!$result) {
                        throw new TransportException('Failed to decode zlib stream');
                    }
                } catch (\Exception $e) {
                    if ($this->degradedMode) {
                        throw $e;
                    }

                    $this->degradedMode = true;
                    $this->io->writeError(array(
                        '',
                        '<error>Failed to decode response: '.$e->getMessage().'</error>',
                        '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                    ));

                    return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
                }
            }
        }

        // handle copy command if download was successful
        if (false !== $result && null !== $fileName && !$isRedirect) {
            if ('' === $result) {
                throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
            }

            $errorMessage = '';
            set_error_handler(function ($code, $msg) use (&$errorMessage) {
                if ($errorMessage) {
                    $errorMessage .= "\n";
                }
                $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);
            });
            $result = (bool) file_put_contents($fileName, $result);
            restore_error_handler();
            if (false === $result) {
                throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage);
            }
        }

        // Handle SSL cert match issues
        if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) {
            // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6
            // The procedure to handle sAN for older PHP's is:
            //
            // 1. Open socket to remote server and fetch certificate (disabling peer
            //    validation because PHP errors without giving up the certificate.)
            //
            // 2. Verifying the domain in the URL against the names in the sAN field.
            //    If there is a match record the authority [host/port], certificate
            //    common name, and certificate fingerprint.
            //
            // 3. Retry the original request but changing the CN_match parameter to
            //    the common name extracted from the certificate in step 2.
            //
            // 4. To prevent any attempt at being hoodwinked by switching the
            //    certificate between steps 2 and 3 the fingerprint of the certificate
            //    presented in step 3 is compared against the one recorded in step 2.
            if (CaBundle::isOpensslParseSafe()) {
                $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options);

                if ($certDetails) {
                    $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;

                    $this->retry = true;
                }
            } else {
                $this->io->writeError('');
                $this->io->writeError(sprintf(
                    '<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
                    PHP_VERSION
                ));
            }
        }

        if ($this->retry) {
            $this->retry = false;

            $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);

            if ($this->storeAuth && $this->config) {
                $authHelper = new AuthHelper($this->io, $this->config);
                $authHelper->storeAuth($this->originUrl, $this->storeAuth);
                $this->storeAuth = false;
            }

            return $result;
        }

        if (false === $result) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
            if (!empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
            }

            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        if (!empty($http_response_header[0])) {
            $this->lastHeaders = $http_response_header;
        }

        return $result;
    }
protected function get ( $ originUrl , $ fileUrl , $ additionalOptions = array ( ) , $ fileName = null , $ progress = true ) { if ( strpos ( $ originUrl , '.github.com' ) === ( strlen ( $ originUrl ) - 11 ) ) { $ originUrl = 'github.com' ; } // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl // is the host without the path, so we look for the registered gitlab-domains matching the host here if ( $ this -> config && is_array ( $ this -> config -> get ( 'gitlab-domains' ) ) && false === strpos ( $ originUrl , '/' ) && ! in_array ( $ originUrl , $ this -> config -> get ( 'gitlab-domains' ) ) ) { foreach ( $ this -> config -> get ( 'gitlab-domains' ) as $ gitlabDomain ) { if ( 0 === strpos ( $ gitlabDomain , $ originUrl ) ) { $ originUrl = $ gitlabDomain ; break ; } } unset ( $ gitlabDomain ) ; } $ this -> scheme = parse_url ( $ fileUrl , PHP_URL_SCHEME ) ; $ this -> bytesMax = 0 ; $ this -> originUrl = $ originUrl ; $ this -> fileUrl = $ fileUrl ; $ this -> fileName = $ fileName ; $ this -> progress = $ progress ; $ this -> lastProgress = null ; $ this -> retryAuthFailure = true ; $ this -> lastHeaders = array ( ) ; $ this -> redirects = 1 ; // The first request counts. // capture username/password from URL if there is one if ( preg_match ( '{^https?://([^:/]+):([^@/]+)@([^/]+)}i' , $ fileUrl , $ match ) ) { $ this -> io -> setAuthentication ( $ originUrl , rawurldecode ( $ match [ 1 ] ) , rawurldecode ( $ match [ 2 ] ) ) ; } $ tempAdditionalOptions = $ additionalOptions ; if ( isset ( $ tempAdditionalOptions [ 'retry-auth-failure' ] ) ) { $ this -> retryAuthFailure = ( bool ) $ tempAdditionalOptions [ 'retry-auth-failure' ] ; unset ( $ tempAdditionalOptions [ 'retry-auth-failure' ] ) ; } $ isRedirect = false ; if ( isset ( $ tempAdditionalOptions [ 'redirects' ] ) ) { $ this -> redirects = $ tempAdditionalOptions [ 'redirects' ] ; $ isRedirect = true ; unset ( $ tempAdditionalOptions [ 'redirects' ] ) ; } $ options = $ this -> getOptionsForUrl ( $ originUrl , $ tempAdditionalOptions ) ; unset ( $ tempAdditionalOptions ) ; $ origFileUrl = $ fileUrl ; if ( isset ( $ options [ 'github-token' ] ) ) { // only add the access_token if it is actually a github URL (in case we were redirected to S3) if ( preg_match ( '{^https?://([a-z0-9-]+\.)*github\.com/}' , $ fileUrl ) ) { $ fileUrl .= ( false === strpos ( $ fileUrl , '?' ) ? '?' : '&' ) . 'access_token=' . $ options [ 'github-token' ] ; } unset ( $ options [ 'github-token' ] ) ; } if ( isset ( $ options [ 'gitlab-token' ] ) ) { $ fileUrl .= ( false === strpos ( $ fileUrl , '?' ) ? '?' : '&' ) . 'access_token=' . $ options [ 'gitlab-token' ] ; unset ( $ options [ 'gitlab-token' ] ) ; } if ( isset ( $ options [ 'http' ] ) ) { $ options [ 'http' ] [ 'ignore_errors' ] = true ; } if ( $ this -> degradedMode && substr ( $ fileUrl , 0 , 26 ) === 'http://repo.packagist.org/' ) { // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol $ fileUrl = 'http://' . gethostbyname ( 'repo.packagist.org' ) . substr ( $ fileUrl , 20 ) ; $ degradedPackagist = true ; } $ ctx = StreamContextFactory :: getContext ( $ fileUrl , $ options , array ( 'notification' => array ( $ this , 'callbackGet' ) ) ) ; $ actualContextOptions = stream_context_get_options ( $ ctx ) ; $ usingProxy = ! empty ( $ actualContextOptions [ 'http' ] [ 'proxy' ] ) ? ' using proxy ' . $ actualContextOptions [ 'http' ] [ 'proxy' ] : '' ; $ this -> io -> writeError ( ( substr ( $ origFileUrl , 0 , 4 ) === 'http' ? 'Downloading ' : 'Reading ' ) . $ origFileUrl . $ usingProxy , true , IOInterface :: DEBUG ) ; unset ( $ origFileUrl , $ actualContextOptions ) ; // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 if ( ( ! preg_match ( '{^http://(repo\.)?packagist\.org/p/}' , $ fileUrl ) || ( false === strpos ( $ fileUrl , '$' ) && false === strpos ( $ fileUrl , '%24' ) ) ) && empty ( $ degradedPackagist ) && $ this -> config ) { $ this -> config -> prohibitUrlByConfig ( $ fileUrl , $ this -> io ) ; } if ( $ this -> progress && ! $ isRedirect ) { $ this -> io -> writeError ( "Downloading (<comment>connecting...</comment>)" , false ) ; } $ errorMessage = '' ; $ errorCode = 0 ; $ result = false ; set_error_handler ( function ( $ code , $ msg ) use ( & $ errorMessage ) { if ( $ errorMessage ) { $ errorMessage .= "\n" ; } $ errorMessage .= preg_replace ( '{^file_get_contents\(.*?\): }' , '' , $ msg ) ; } ) ; try { $ result = $ this -> getRemoteContents ( $ originUrl , $ fileUrl , $ ctx , $ http_response_header ) ; if ( ! empty ( $ http_response_header [ 0 ] ) ) { $ statusCode = $ this -> findStatusCode ( $ http_response_header ) ; if ( $ statusCode >= 400 && $ this -> findHeaderValue ( $ http_response_header , 'content-type' ) === 'application/json' ) { self :: outputWarnings ( $ this -> io , $ originUrl , json_decode ( $ result , true ) ) ; } if ( in_array ( $ statusCode , array ( 401 , 403 ) ) && $ this -> retryAuthFailure ) { $ this -> promptAuthAndRetry ( $ statusCode , $ this -> findStatusMessage ( $ http_response_header ) , null , $ http_response_header ) ; } } $ contentLength = ! empty ( $ http_response_header [ 0 ] ) ? $ this -> findHeaderValue ( $ http_response_header , 'content-length' ) : null ; if ( $ contentLength && Platform :: strlen ( $ result ) < $ contentLength ) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP $ e = new TransportException ( 'Content-Length mismatch, received ' . Platform :: strlen ( $ result ) . ' bytes out of the expected ' . $ contentLength ) ; $ e -> setHeaders ( $ http_response_header ) ; $ e -> setStatusCode ( $ this -> findStatusCode ( $ http_response_header ) ) ; $ e -> setResponse ( $ result ) ; $ this -> io -> writeError ( 'Content-Length mismatch, received ' . Platform :: strlen ( $ result ) . ' out of ' . $ contentLength . ' bytes: (' . base64_encode ( $ result ) . ')' , true , IOInterface :: DEBUG ) ; throw $ e ; } if ( PHP_VERSION_ID < 50600 && ! empty ( $ options [ 'ssl' ] [ 'peer_fingerprint' ] ) ) { // Emulate fingerprint validation on PHP < 5.6 $ params = stream_context_get_params ( $ ctx ) ; $ expectedPeerFingerprint = $ options [ 'ssl' ] [ 'peer_fingerprint' ] ; $ peerFingerprint = TlsHelper :: getCertificateFingerprint ( $ params [ 'options' ] [ 'ssl' ] [ 'peer_certificate' ] ) ; // Constant time compare??! if ( $ expectedPeerFingerprint !== $ peerFingerprint ) { throw new TransportException ( 'Peer fingerprint did not match' ) ; } } } catch ( \ Exception $ e ) { if ( $ e instanceof TransportException && ! empty ( $ http_response_header [ 0 ] ) ) { $ e -> setHeaders ( $ http_response_header ) ; $ e -> setStatusCode ( $ this -> findStatusCode ( $ http_response_header ) ) ; } if ( $ e instanceof TransportException && $ result !== false ) { $ e -> setResponse ( $ result ) ; } $ result = false ; } if ( $ errorMessage && ! filter_var ( ini_get ( 'allow_url_fopen' ) , FILTER_VALIDATE_BOOLEAN ) ) { $ errorMessage = 'allow_url_fopen must be enabled in php.ini (' . $ errorMessage . ')' ; } restore_error_handler ( ) ; if ( isset ( $ e ) && ! $ this -> retry ) { if ( ! $ this -> degradedMode && false !== strpos ( $ e -> getMessage ( ) , 'Operation timed out' ) ) { $ this -> degradedMode = true ; $ this -> io -> writeError ( '' ) ; $ this -> io -> writeError ( array ( '<error>' . $ e -> getMessage ( ) . '</error>' , '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>' , ) ) ; return $ this -> get ( $ this -> originUrl , $ this -> fileUrl , $ additionalOptions , $ this -> fileName , $ this -> progress ) ; } throw $ e ; } $ statusCode = null ; $ contentType = null ; $ locationHeader = null ; if ( ! empty ( $ http_response_header [ 0 ] ) ) { $ statusCode = $ this -> findStatusCode ( $ http_response_header ) ; $ contentType = $ this -> findHeaderValue ( $ http_response_header , 'content-type' ) ; $ locationHeader = $ this -> findHeaderValue ( $ http_response_header , 'location' ) ; } // check for bitbucket login page asking to authenticate if ( $ originUrl === 'bitbucket.org' && ! $ this -> isPublicBitBucketDownload ( $ fileUrl ) && substr ( $ fileUrl , - 4 ) === '.zip' && ( ! $ locationHeader || substr ( $ locationHeader , - 4 ) !== '.zip' ) && $ contentType && preg_match ( '{^text/html\b}i' , $ contentType ) ) { $ result = false ; if ( $ this -> retryAuthFailure ) { $ this -> promptAuthAndRetry ( 401 ) ; } } // check for gitlab 404 when downloading archives if ( $ statusCode === 404 && $ this -> config && in_array ( $ originUrl , $ this -> config -> get ( 'gitlab-domains' ) , true ) && false !== strpos ( $ fileUrl , 'archive.zip' ) ) { $ result = false ; if ( $ this -> retryAuthFailure ) { $ this -> promptAuthAndRetry ( 401 ) ; } } // handle 3xx redirects, 304 Not Modified is excluded $ hasFollowedRedirect = false ; if ( $ statusCode >= 300 && $ statusCode <= 399 && $ statusCode !== 304 && $ this -> redirects < $ this -> maxRedirects ) { $ hasFollowedRedirect = true ; $ result = $ this -> handleRedirect ( $ http_response_header , $ additionalOptions , $ result ) ; } // fail 4xx and 5xx responses and capture the response if ( $ statusCode && $ statusCode >= 400 && $ statusCode <= 599 ) { if ( ! $ this -> retry ) { if ( $ this -> progress && ! $ this -> retry && ! $ isRedirect ) { $ this -> io -> overwriteError ( "Downloading (<error>failed</error>)" , false ) ; } $ e = new TransportException ( 'The "' . $ this -> fileUrl . '" file could not be downloaded (' . $ http_response_header [ 0 ] . ')' , $ statusCode ) ; $ e -> setHeaders ( $ http_response_header ) ; $ e -> setResponse ( $ result ) ; $ e -> setStatusCode ( $ statusCode ) ; throw $ e ; } $ result = false ; } if ( $ this -> progress && ! $ this -> retry && ! $ isRedirect ) { $ this -> io -> overwriteError ( "Downloading (" . ( $ result === false ? '<error>failed</error>' : '<comment>100%</comment>' ) . ")" , false ) ; } // decode gzip if ( $ result && extension_loaded ( 'zlib' ) && substr ( $ fileUrl , 0 , 4 ) === 'http' && ! $ hasFollowedRedirect ) { $ contentEncoding = $ this -> findHeaderValue ( $ http_response_header , 'content-encoding' ) ; $ decode = $ contentEncoding && 'gzip' === strtolower ( $ contentEncoding ) ; if ( $ decode ) { try { if ( PHP_VERSION_ID >= 50400 ) { $ result = zlib_decode ( $ result ) ; } else { // work around issue with gzuncompress & co that do not work with all gzip checksums $ result = file_get_contents ( 'compress.zlib://data:application/octet-stream;base64,' . base64_encode ( $ result ) ) ; } if ( ! $ result ) { throw new TransportException ( 'Failed to decode zlib stream' ) ; } } catch ( \ Exception $ e ) { if ( $ this -> degradedMode ) { throw $ e ; } $ this -> degradedMode = true ; $ this -> io -> writeError ( array ( '' , '<error>Failed to decode response: ' . $ e -> getMessage ( ) . '</error>' , '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>' , ) ) ; return $ this -> get ( $ this -> originUrl , $ this -> fileUrl , $ additionalOptions , $ this -> fileName , $ this -> progress ) ; } } } // handle copy command if download was successful if ( false !== $ result && null !== $ fileName && ! $ isRedirect ) { if ( '' === $ result ) { throw new TransportException ( '"' . $ this -> fileUrl . '" appears broken, and returned an empty 200 response' ) ; } $ errorMessage = '' ; set_error_handler ( function ( $ code , $ msg ) use ( & $ errorMessage ) { if ( $ errorMessage ) { $ errorMessage .= "\n" ; } $ errorMessage .= preg_replace ( '{^file_put_contents\(.*?\): }' , '' , $ msg ) ; } ) ; $ result = ( bool ) file_put_contents ( $ fileName , $ result ) ; restore_error_handler ( ) ; if ( false === $ result ) { throw new TransportException ( 'The "' . $ this -> fileUrl . '" file could not be written to ' . $ fileName . ': ' . $ errorMessage ) ; } } // Handle SSL cert match issues if ( false === $ result && false !== strpos ( $ errorMessage , 'Peer certificate' ) && PHP_VERSION_ID < 50600 ) { // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6 // The procedure to handle sAN for older PHP's is: // // 1. Open socket to remote server and fetch certificate (disabling peer //    validation because PHP errors without giving up the certificate.) // // 2. Verifying the domain in the URL against the names in the sAN field. //    If there is a match record the authority [host/port], certificate //    common name, and certificate fingerprint. // // 3. Retry the original request but changing the CN_match parameter to //    the common name extracted from the certificate in step 2. // // 4. To prevent any attempt at being hoodwinked by switching the //    certificate between steps 2 and 3 the fingerprint of the certificate //    presented in step 3 is compared against the one recorded in step 2. if ( CaBundle :: isOpensslParseSafe ( ) ) { $ certDetails = $ this -> getCertificateCnAndFp ( $ this -> fileUrl , $ options ) ; if ( $ certDetails ) { $ this -> peerCertificateMap [ $ this -> getUrlAuthority ( $ this -> fileUrl ) ] = $ certDetails ; $ this -> retry = true ; } } else { $ this -> io -> writeError ( '' ) ; $ this -> io -> writeError ( sprintf ( '<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>' , PHP_VERSION ) ) ; } } if ( $ this -> retry ) { $ this -> retry = false ; $ result = $ this -> get ( $ this -> originUrl , $ this -> fileUrl , $ additionalOptions , $ this -> fileName , $ this -> progress ) ; if ( $ this -> storeAuth && $ this -> config ) { $ authHelper = new AuthHelper ( $ this -> io , $ this -> config ) ; $ authHelper -> storeAuth ( $ this -> originUrl , $ this -> storeAuth ) ; $ this -> storeAuth = false ; } return $ result ; } if ( false === $ result ) { $ e = new TransportException ( 'The "' . $ this -> fileUrl . '" file could not be downloaded: ' . $ errorMessage , $ errorCode ) ; if ( ! empty ( $ http_response_header [ 0 ] ) ) { $ e -> setHeaders ( $ http_response_header ) ; } if ( ! $ this -> degradedMode && false !== strpos ( $ e -> getMessage ( ) , 'Operation timed out' ) ) { $ this -> degradedMode = true ; $ this -> io -> writeError ( '' ) ; $ this -> io -> writeError ( array ( '<error>' . $ e -> getMessage ( ) . '</error>' , '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>' , ) ) ; return $ this -> get ( $ this -> originUrl , $ this -> fileUrl , $ additionalOptions , $ this -> fileName , $ this -> progress ) ; } throw $ e ; } if ( ! empty ( $ http_response_header [ 0 ] ) ) { $ this -> lastHeaders = $ http_response_header ; } return $ result ; }
Get file content or copy action.

@param string $originUrl         The origin URL
@param string $fileUrl           The file URL
@param array  $additionalOptions context options
@param string $fileName          the local filename
@param bool   $progress          Display the progression

@throws TransportException|\Exception
@throws TransportException            When the file could not be downloaded

@return bool|string
Get file content or copy action .