Pārlūkot izejas kodu

Merge branch '1.7'

Jordi Boggiano 6 gadi atpakaļ
vecāks
revīzija
db13cc4960

+ 5 - 5
composer.lock

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

+ 7 - 0
src/Composer/Json/JsonFormatter.php

@@ -69,6 +69,13 @@ class JsonFormatter
                         $l = strlen($match[1]);
 
                         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(
                                 pack('H*', $match[2]),
                                 'UTF-8',

+ 2 - 40
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -378,12 +378,7 @@ class GitHubDriver extends VcsDriver
                         return $this->attemptCloneFallback();
                     }
 
-                    $rateLimited = false;
-                    foreach ($e->getHeaders() as $header) {
-                        if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
-                            $rateLimited = true;
-                        }
-                    }
+                    $rateLimited = $githubUtil->isRateLimited($e->getHeaders());
 
                     if (!$this->io->hasAuthentication($this->originUrl)) {
                         if (!$this->io->isInteractive()) {
@@ -397,7 +392,7 @@ class GitHubDriver extends VcsDriver
                     }
 
                     if ($rateLimited) {
-                        $rateLimit = $this->getRateLimit($e->getHeaders());
+                        $rateLimit = $githubUtil->getRateLimit($e->getHeaders());
                         $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>',
                             $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
      *

+ 51 - 0
src/Composer/Util/GitHub.php

@@ -126,4 +126,55 @@ class GitHub
 
         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;
+    }
 }

+ 27 - 3
src/Composer/Util/RemoteFilesystem.php

@@ -327,7 +327,7 @@ class RemoteFilesystem
                             $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)) {
-            $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);
+            $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)
                 && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
             ) {

+ 13 - 15
tests/Composer/Test/Json/JsonFormatterTest.php

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