예제 #1
0
 /**
  * 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
  */
 protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
 {
     if (strpos($originUrl, '.github.com') === strlen($originUrl) - 11) {
         $originUrl = 'github.com';
     }
     $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, urldecode($match[1]), urldecode($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);
     $userlandFollow = isset($options['http']['follow_location']) && !$options['http']['follow_location'];
     $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl, true, IOInterface::DEBUG);
     if (isset($options['github-token'])) {
         $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, 21) === 'http://packagist.org/') {
         // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
         $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20);
     }
     $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
     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 = file_get_contents($fileUrl, false, $ctx);
         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 && !ini_get('allow_url_fopen')) {
         $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(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;
     if (!empty($http_response_header[0])) {
         $statusCode = $this->findStatusCode($http_response_header);
     }
     // handle 3xx redirects for php<5.6, 304 Not Modified is excluded
     $hasFollowedRedirect = false;
     if ($userlandFollow && $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) {
             $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: <comment>100%</comment>");
     }
     // decode gzip
     if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
         $decode = 'gzip' === strtolower($this->findHeaderValue($http_response_header, 'content-encoding'));
         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 (TlsHelper::isOpensslParseSafe()) {
             $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options);
             if ($certDetails) {
                 $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;
                 $this->retry = true;
             }
         } else {
             $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(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;
 }
예제 #2
0
 public function runCommand($commandCallable, $url, $cwd, $initialClone = false)
 {
     if (preg_match('{^(http|git):}i', $url) && $this->config->get('secure-http')) {
         throw new TransportException("Your configuration does not allow connection to {$url}. See https://getcomposer.org/doc/06-config.md#secure-http for details.");
     }
     if ($initialClone) {
         $origCwd = $cwd;
         $cwd = null;
     }
     if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
         throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
     }
     if (!$initialClone) {
         // capture username/password from URL if there is one
         $this->process->execute('git remote -v', $output, $cwd);
         if (preg_match('{^(?:composer|origin)\\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) {
             $this->io->setAuthentication($match[3], urldecode($match[1]), urldecode($match[2]));
         }
     }
     $protocols = $this->config->get('github-protocols');
     if (!is_array($protocols)) {
         throw new \RuntimeException('Config value "github-protocols" must be an array, got ' . gettype($protocols));
     }
     // public github, autoswitch protocols
     if (preg_match('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
         $messages = array();
         foreach ($protocols as $protocol) {
             if ('ssh' === $protocol) {
                 $protoUrl = "git@" . $match[1] . ":" . $match[2];
             } else {
                 $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
             }
             if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) {
                 return;
             }
             $messages[] = '- ' . $protoUrl . "\n" . preg_replace('#^#m', '  ', $this->process->getErrorOutput());
             if ($initialClone) {
                 $this->filesystem->removeDirectory($origCwd);
             }
         }
         // failed to checkout, first check git accessibility
         $this->throwException('Failed to clone ' . self::sanitizeUrl($url) . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
     }
     // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
     $bypassSshForGitHub = preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url) && !in_array('ssh', $protocols, true);
     $command = call_user_func($commandCallable, $url);
     $auth = null;
     if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
         // private github repository without git access, try https with auth
         if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url, $match)) {
             if (!$this->io->hasAuthentication($match[1])) {
                 $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                 $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
                 if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                     $gitHubUtil->authorizeOAuthInteractively($match[1], $message);
                 }
             }
             if ($this->io->hasAuthentication($match[1])) {
                 $auth = $this->io->getAuthentication($match[1]);
                 $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
                 $command = call_user_func($commandCallable, $authUrl);
                 if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                     return;
                 }
             }
         } elseif ($this->isAuthenticationFailure($url, $match)) {
             // private non-github repo that failed to authenticate
             if (strpos($match[2], '@')) {
                 list($authParts, $match[2]) = explode('@', $match[2], 2);
             }
             $storeAuth = false;
             if ($this->io->hasAuthentication($match[2])) {
                 $auth = $this->io->getAuthentication($match[2]);
             } elseif ($this->io->isInteractive()) {
                 $defaultUsername = null;
                 if (isset($authParts) && $authParts) {
                     if (false !== strpos($authParts, ':')) {
                         list($defaultUsername, ) = explode(':', $authParts, 2);
                     } else {
                         $defaultUsername = $authParts;
                     }
                 }
                 $this->io->writeError('    Authentication required (<info>' . parse_url($url, PHP_URL_HOST) . '</info>):');
                 $auth = array('username' => $this->io->ask('      Username: '******'password' => $this->io->askAndHideAnswer('      Password: '******'store-auths');
             }
             if ($auth) {
                 $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3];
                 $command = call_user_func($commandCallable, $authUrl);
                 if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                     $this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
                     $authHelper = new AuthHelper($this->io, $this->config);
                     $authHelper->storeAuth($match[2], $storeAuth);
                     return;
                 }
             }
         }
         if ($initialClone) {
             $this->filesystem->removeDirectory($origCwd);
         }
         $this->throwException('Failed to execute ' . self::sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput(), $url);
     }
 }
예제 #3
0
 /**
  * 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 boolean $progress          Display the progression
  *
  * @throws TransportException|\Exception
  * @throws TransportException            When the file could not be downloaded
  *
  * @return bool|string
  */
 protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
 {
     if (strpos($originUrl, '.github.com') === strlen($originUrl) - 11) {
         $originUrl = 'github.com';
     }
     $this->bytesMax = 0;
     $this->originUrl = $originUrl;
     $this->fileUrl = $fileUrl;
     $this->fileName = $fileName;
     $this->progress = $progress;
     $this->lastProgress = null;
     $this->retryAuthFailure = true;
     $this->lastHeaders = array();
     // capture username/password from URL if there is one
     if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) {
         $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
     }
     if (isset($additionalOptions['retry-auth-failure'])) {
         $this->retryAuthFailure = (bool) $additionalOptions['retry-auth-failure'];
         unset($additionalOptions['retry-auth-failure']);
     }
     $options = $this->getOptionsForUrl($originUrl, $additionalOptions);
     if ($this->io->isDebug()) {
         $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
     }
     if (isset($options['github-token'])) {
         $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['github-token'];
         unset($options['github-token']);
     }
     if (isset($options['http'])) {
         $options['http']['ignore_errors'] = true;
     }
     if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') {
         // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
         $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20);
     }
     $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
     if ($this->progress) {
         $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 = file_get_contents($fileUrl, false, $ctx);
     } catch (\Exception $e) {
         if ($e instanceof TransportException && !empty($http_response_header[0])) {
             $e->setHeaders($http_response_header);
         }
         if ($e instanceof TransportException && $result !== false) {
             $e->setResponse($result);
         }
         $result = false;
     }
     if ($errorMessage && !ini_get('allow_url_fopen')) {
         $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(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;
     }
     // fail 4xx and 5xx responses and capture the response
     if (!empty($http_response_header[0]) && preg_match('{^HTTP/\\S+ ([45]\\d\\d)}i', $http_response_header[0], $match)) {
         $errorCode = $match[1];
         if (!$this->retry) {
             $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded (' . $http_response_header[0] . ')', $errorCode);
             $e->setHeaders($http_response_header);
             $e->setResponse($result);
             throw $e;
         }
         $result = false;
     }
     if ($this->progress && !$this->retry) {
         $this->io->overwriteError("    Downloading: <comment>100%</comment>");
     }
     // decode gzip
     if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') {
         $decode = false;
         foreach ($http_response_header as $header) {
             if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
                 $decode = true;
             } elseif (preg_match('{^HTTP/}i', $header)) {
                 // In case of redirects, http_response_headers contains the headers of all responses
                 // so we reset the flag when a new response is being parsed as we are only interested in the last response
                 $decode = false;
             }
         }
         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) {
         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);
         }
     }
     if ($this->retry) {
         $this->retry = false;
         $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
         $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(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;
 }
예제 #4
0
 protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
 {
     if (strpos($originUrl, '.github.com') === strlen($originUrl) - 11) {
         $originUrl = 'github.com';
     }
     $this->bytesMax = 0;
     $this->originUrl = $originUrl;
     $this->fileUrl = $fileUrl;
     $this->fileName = $fileName;
     $this->progress = $progress;
     $this->lastProgress = null;
     $this->retryAuthFailure = true;
     $this->lastHeaders = array();
     if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) {
         $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
     }
     if (isset($additionalOptions['retry-auth-failure'])) {
         $this->retryAuthFailure = (bool) $additionalOptions['retry-auth-failure'];
         unset($additionalOptions['retry-auth-failure']);
     }
     $options = $this->getOptionsForUrl($originUrl, $additionalOptions);
     if ($this->io->isDebug()) {
         $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
     }
     if (isset($options['github-token'])) {
         $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['github-token'];
         unset($options['github-token']);
     }
     if (isset($options['http'])) {
         $options['http']['ignore_errors'] = true;
     }
     $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
     if ($this->progress) {
         $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 = file_get_contents($fileUrl, false, $ctx);
     } catch (\Exception $e) {
         if ($e instanceof TransportException && !empty($http_response_header[0])) {
             $e->setHeaders($http_response_header);
         }
         if ($e instanceof TransportException && $result !== false) {
             $e->setResponse($result);
         }
         $result = false;
     }
     if ($errorMessage && !ini_get('allow_url_fopen')) {
         $errorMessage = 'allow_url_fopen must be enabled in php.ini (' . $errorMessage . ')';
     }
     restore_error_handler();
     if (isset($e) && !$this->retry) {
         throw $e;
     }
     if (!empty($http_response_header[0]) && preg_match('{^HTTP/\\S+ ([45]\\d\\d)}i', $http_response_header[0], $match)) {
         $errorCode = $match[1];
         if (!$this->retry) {
             $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded (' . $http_response_header[0] . ')', $errorCode);
             $e->setHeaders($http_response_header);
             $e->setResponse($result);
             throw $e;
         }
         $result = false;
     }
     if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') {
         $decode = false;
         foreach ($http_response_header as $header) {
             if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
                 $decode = true;
                 continue;
             } elseif (preg_match('{^HTTP/}i', $header)) {
                 $decode = false;
             }
         }
         if ($decode) {
             if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
                 $result = zlib_decode($result);
             } else {
                 $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,' . base64_encode($result));
             }
             if (!$result) {
                 throw new TransportException('Failed to decode zlib stream');
             }
         }
     }
     if ($this->progress && !$this->retry) {
         $this->io->overwriteError("    Downloading: <comment>100%</comment>");
     }
     if (false !== $result && null !== $fileName) {
         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);
         }
     }
     if ($this->retry) {
         $this->retry = false;
         $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
         $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);
         }
         throw $e;
     }
     if (!empty($http_response_header[0])) {
         $this->lastHeaders = $http_response_header;
     }
     return $result;
 }