1
0
Fork 0

Merge branch '1.7'

pull/7552/head
Jordi Boggiano 2018-08-10 08:58:12 +02:00
commit db13cc4960
6 changed files with 105 additions and 63 deletions

10
composer.lock generated
View File

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.1.1", "version": "1.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0",
"reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -60,7 +60,7 @@
"ssl", "ssl",
"tls" "tls"
], ],
"time": "2018-03-29T19:57:20+00:00" "time": "2018-08-08T08:57:40+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",

View File

@ -69,6 +69,13 @@ class JsonFormatter
$l = strlen($match[1]); $l = strlen($match[1]);
if ($l % 2) { if ($l % 2) {
$code = hexdec($match[2]);
// 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped
// see https://github.com/composer/composer/issues/7510
if (0xD800 <= $code && 0xDFFF >= $code) {
return $match[0];
}
return str_repeat('\\', $l - 1) . mb_convert_encoding( return str_repeat('\\', $l - 1) . mb_convert_encoding(
pack('H*', $match[2]), pack('H*', $match[2]),
'UTF-8', 'UTF-8',

View File

@ -378,12 +378,7 @@ class GitHubDriver extends VcsDriver
return $this->attemptCloneFallback(); return $this->attemptCloneFallback();
} }
$rateLimited = false; $rateLimited = $githubUtil->isRateLimited($e->getHeaders());
foreach ($e->getHeaders() as $header) {
if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
$rateLimited = true;
}
}
if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->hasAuthentication($this->originUrl)) {
if (!$this->io->isInteractive()) { if (!$this->io->isInteractive()) {
@ -397,7 +392,7 @@ class GitHubDriver extends VcsDriver
} }
if ($rateLimited) { if ($rateLimited) {
$rateLimit = $this->getRateLimit($e->getHeaders()); $rateLimit = $githubUtil->getRateLimit($e->getHeaders());
$this->io->writeError(sprintf( $this->io->writeError(sprintf(
'<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>', '<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>',
$rateLimit['limit'], $rateLimit['limit'],
@ -413,39 +408,6 @@ class GitHubDriver extends VcsDriver
} }
} }
/**
* Extract ratelimit from response.
*
* @param array $headers Headers from Composer\Downloader\TransportException.
*
* @return array Associative array with the keys limit and reset.
*/
protected function getRateLimit(array $headers)
{
$rateLimit = array(
'limit' => '?',
'reset' => '?',
);
foreach ($headers as $header) {
$header = trim($header);
if (false === strpos($header, 'X-RateLimit-')) {
continue;
}
list($type, $value) = explode(':', $header, 2);
switch ($type) {
case 'X-RateLimit-Limit':
$rateLimit['limit'] = (int) trim($value);
break;
case 'X-RateLimit-Reset':
$rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
break;
}
}
return $rateLimit;
}
/** /**
* Fetch root identifier from GitHub * Fetch root identifier from GitHub
* *

View File

@ -126,4 +126,55 @@ class GitHub
return true; return true;
} }
/**
* Extract ratelimit from response.
*
* @param array $headers Headers from Composer\Downloader\TransportException.
*
* @return array Associative array with the keys limit and reset.
*/
public function getRateLimit(array $headers)
{
$rateLimit = array(
'limit' => '?',
'reset' => '?',
);
foreach ($headers as $header) {
$header = trim($header);
if (false === strpos($header, 'X-RateLimit-')) {
continue;
}
list($type, $value) = explode(':', $header, 2);
switch ($type) {
case 'X-RateLimit-Limit':
$rateLimit['limit'] = (int) trim($value);
break;
case 'X-RateLimit-Reset':
$rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
break;
}
}
return $rateLimit;
}
/**
* Finds whether a request failed due to rate limiting
*
* @param array $headers Headers from Composer\Downloader\TransportException.
*
* @return bool
*/
public function isRateLimited(array $headers)
{
foreach ($headers as $header) {
if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
return true;
}
}
return false;
}
} }

View File

@ -327,7 +327,7 @@ class RemoteFilesystem
$warning = $data['warning']; $warning = $data['warning'];
} }
} }
$this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning); $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning, $http_response_header);
} }
} }
@ -639,11 +639,35 @@ class RemoteFilesystem
} }
} }
protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null) protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array())
{ {
if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { 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');
$gitHubUtil = new GitHub($this->io, $this->config, null); $gitHubUtil = new GitHub($this->io, $this->config, null);
$message = "\n";
$rateLimited = $gitHubUtil->isRateLimited($headers);
if ($rateLimited) {
$rateLimit = $gitHubUtil->getRateLimit($headers);
if ($this->io->hasAuthentication($this->originUrl)) {
$message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
} else {
$message = 'Create a GitHub OAuth token to go over the API rate limit.';
}
$message = sprintf(
'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.',
$rateLimit['limit'],
$rateLimit['reset']
)."\n";
} else {
$message .= 'Could not fetch '.$this->fileUrl.', please ';
if ($this->io->hasAuthentication($this->originUrl)) {
$message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
} else {
$message .= 'create a GitHub OAuth token to access private repos';
}
}
if (!$gitHubUtil->authorizeOAuth($this->originUrl) if (!$gitHubUtil->authorizeOAuth($this->originUrl)
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
) { ) {

View File

@ -18,33 +18,31 @@ use PHPUnit\Framework\TestCase;
class JsonFormatterTest extends TestCase class JsonFormatterTest extends TestCase
{ {
/** /**
* Test if \u0119 (196+153) will get correctly formatted * Test if \u0119 will get correctly formatted (unescaped)
* See ticket #2613 * https://github.com/composer/composer/issues/2613
*/ */
public function testUnicodeWithPrependedSlash() public function testUnicodeWithPrependedSlash()
{ {
if (!extension_loaded('mbstring')) { if (!extension_loaded('mbstring')) {
$this->markTestSkipped('Test requires the mbstring extension'); $this->markTestSkipped('Test requires the mbstring extension');
} }
$backslash = chr(92);
$data = '"' . chr(92) . chr(92) . chr(92) . 'u0119"'; $data = '"' . $backslash . $backslash . $backslash . 'u0119"';
$encodedData = JsonFormatter::format($data, true, true); $expected = '"' . $backslash . $backslash . 'ę"';
$expected = '34+92+92+196+153+34'; $this->assertEquals($expected, JsonFormatter::format($data, true, true));
$this->assertEquals($expected, $this->getCharacterCodes($encodedData));
} }
/** /**
* Convert string to character codes split by a plus sign * Surrogate pairs are intentionally skipped and not unescaped
* @param string $string * https://github.com/composer/composer/issues/7510
* @return string
*/ */
protected function getCharacterCodes($string) public function testUtf16SurrogatePair()
{ {
$codes = array(); if (!extension_loaded('mbstring')) {
for ($i = 0; $i < strlen($string); $i++) { $this->markTestSkipped('Test requires the mbstring extension');
$codes[] = ord($string[$i]);
} }
return implode('+', $codes); $escaped = '"\ud83d\ude00"';
$this->assertEquals($escaped, JsonFormatter::format($escaped, true, true));
} }
} }