diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php
index 6bc1505dd..66c78a69c 100644
--- a/src/Composer/Util/RemoteFilesystem.php
+++ b/src/Composer/Util/RemoteFilesystem.php
@@ -175,6 +175,24 @@ class RemoteFilesystem
return $value;
}
+ /**
+ * @param array $headers array of returned headers like from getLastHeaders()
+ * @return string|null
+ */
+ public function findStatusMessage(array $headers)
+ {
+ $value = null;
+ foreach ($headers as $header) {
+ if (preg_match('{^HTTP/\S+ \d+}i', $header)) {
+ // In case of redirects, http_response_headers contains the headers of all responses
+ // so we can not return directly and need to keep iterating
+ $value = $header;
+ }
+ }
+
+ return $value;
+ }
+
/**
* Get file content or copy action.
*
@@ -299,6 +317,20 @@ class RemoteFilesystem
try {
$result = file_get_contents($fileUrl, false, $ctx);
+ if (!empty($http_response_header[0])) {
+ $statusCode = $this->findStatusCode($http_response_header);
+ if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) {
+ $warning = null;
+ if ($this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
+ $data = json_decode($result, true);
+ if (!empty($data['warning'])) {
+ $warning = $data['warning'];
+ }
+ }
+ $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning);
+ }
+ }
+
$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
@@ -558,29 +590,6 @@ class RemoteFilesystem
// but you do not send an appropriate certificate
throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode);
}
- // intentional fallthrough to the next case as the notificationCode
- // isn't always consistent and we should inspect the messageCode for 401s
-
- case STREAM_NOTIFY_AUTH_REQUIRED:
- if (401 === $messageCode) {
- // Bail if the caller is going to handle authentication failures itself.
- if (!$this->retryAuthFailure) {
- break;
- }
-
- $this->promptAuthAndRetry($messageCode);
- }
- break;
-
- case STREAM_NOTIFY_AUTH_RESULT:
- if (403 === $messageCode) {
- // Bail if the caller is going to handle authentication failures itself.
- if (!$this->retryAuthFailure) {
- break;
- }
-
- $this->promptAuthAndRetry($messageCode, $message);
- }
break;
case STREAM_NOTIFY_FILE_SIZE_IS:
@@ -603,7 +612,7 @@ class RemoteFilesystem
}
}
- protected function promptAuthAndRetry($httpStatus, $reason = null)
+ protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null)
{
if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
$message = "\n".'Could not fetch '.$this->fileUrl.', please create a GitHub OAuth token '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit');
@@ -674,6 +683,9 @@ class RemoteFilesystem
}
$this->io->overwriteError('');
+ if ($warning) {
+ $this->io->writeError(' '.$warning.'');
+ }
$this->io->writeError(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):');
$username = $this->io->ask(' Username: ');
$password = $this->io->askAndHideAnswer(' Password: ');