Browse Source

Sanitize URLs in getRepoName and centralize the Url sanitization process

Jordi Boggiano 5 years ago
parent
commit
1e68555e0a

+ 2 - 1
src/Composer/Downloader/GitDownloader.php

@@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
 use Composer\Package\PackageInterface;
 use Composer\Util\Filesystem;
 use Composer\Util\Git as GitUtil;
+use Composer\Util\Url;
 use Composer\Util\Platform;
 use Composer\Util\ProcessExecutor;
 use Composer\Cache;
@@ -434,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
             $this->io->writeError('    <warning>'.$reference.' is gone (history was rewritten?)</warning>');
         }
 
-        throw new \RuntimeException(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
+        throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
     }
 
     protected function updateOriginUrl($path, $url)

+ 1 - 1
src/Composer/Repository/ArtifactRepository.php

@@ -45,7 +45,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
 
     public function getRepoName()
     {
-        return 'platform repo ('.$this->lookup.')';
+        return 'artifact repo ('.$this->lookup.')';
     }
 
     public function getRepoConfig()

+ 2 - 1
src/Composer/Repository/ComposerRepository.php

@@ -34,6 +34,7 @@ use Composer\Semver\Constraint\Constraint;
 use Composer\Semver\Constraint\EmptyConstraint;
 use Composer\Util\Http\Response;
 use Composer\Util\MetadataMinifier;
+use Composer\Util\Url;
 use React\Promise\Util as PromiseUtil;
 
 /**
@@ -129,7 +130,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
 
     public function getRepoName()
     {
-        return 'composer repo ('.$this->url.')';
+        return 'composer repo ('.Url::sanitize($this->url).')';
     }
 
     public function getRepoConfig()

+ 1 - 1
src/Composer/Repository/CompositeRepository.php

@@ -41,7 +41,7 @@ class CompositeRepository extends BaseRepository
 
     public function getRepoName()
     {
-        return 'composite repo ('.count($this->repositories).' repos)';
+        return 'composite repo ('.implode(', ', array_map(function ($repo) { return $repo->getRepoName(); }, $this->repositories)).')';
     }
 
     /**

+ 2 - 1
src/Composer/Repository/PathRepository.php

@@ -20,6 +20,7 @@ use Composer\Package\Version\VersionGuesser;
 use Composer\Package\Version\VersionParser;
 use Composer\Util\Platform;
 use Composer\Util\ProcessExecutor;
+use Composer\Util\Url;
 
 /**
  * This repository allows installing local packages that are not necessarily under their own VCS.
@@ -113,7 +114,7 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
 
     public function getRepoName()
     {
-        return 'path repo ('.$this->repoConfig['url'].')';
+        return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
     }
 
     public function getRepoConfig()

+ 2 - 1
src/Composer/Repository/VcsRepository.php

@@ -22,6 +22,7 @@ use Composer\Package\Loader\LoaderInterface;
 use Composer\EventDispatcher\EventDispatcher;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\HttpDownloader;
+use Composer\Util\Url;
 use Composer\IO\IOInterface;
 use Composer\Config;
 
@@ -87,7 +88,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
             $driverType = $driverClass;
         }
 
-        return 'vcs repo ('.$driverType.' '.$this->url.')';
+        return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')';
     }
 
     public function getRepoConfig()

+ 0 - 11
src/Composer/Util/AuthHelper.php

@@ -255,15 +255,4 @@ class AuthHelper
 
         return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
     }
-
-    /**
-     * @param string $url
-     * @return string
-     */
-    public function stripCredentialsFromUrl($url)
-    {
-        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
-        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
-        return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
-    }
 }

+ 2 - 13
src/Composer/Util/Git.php

@@ -362,27 +362,16 @@ class Git
         return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
     }
 
-    public static function sanitizeUrl($message)
-    {
-        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
-            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
-                return '://***:***@';
-            }
-
-            return '://' . $m[1] . ':***@';
-        }, $message);
-    }
-
     private function throwException($message, $url)
     {
         // git might delete a directory when it fails and php will not know
         clearstatcache();
 
         if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
-            throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
+            throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
         }
 
-        throw new \RuntimeException(self::sanitizeUrl($message));
+        throw new \RuntimeException(Url::sanitize($message));
     }
 
     /**

+ 2 - 13
src/Composer/Util/Hg.php

@@ -72,23 +72,12 @@ class Hg
         $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url);
     }
 
-    public static function sanitizeUrl($message)
-    {
-        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
-            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
-                return '://***:***@';
-            }
-
-            return '://' . $m[1] . ':***@';
-        }, $message);
-    }
-
     private function throwException($message, $url)
     {
         if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
-            throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
+            throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
         }
 
-        throw new \RuntimeException(self::sanitizeUrl($message));
+        throw new \RuntimeException(Url::sanitize($message));
     }
 }

+ 4 - 4
src/Composer/Util/Http/CurlDownloader.php

@@ -195,7 +195,7 @@ class CurlDownloader
         $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
         $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
         if ($attributes['redirects'] === 0) {
-            $this->io->writeError('Downloading ' . $this->authHelper->stripCredentialsFromUrl($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
+            $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
         }
 
         $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
@@ -254,12 +254,12 @@ class CurlDownloader
                         $contents = stream_get_contents($job['bodyHandle']);
                     }
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
-                    $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
+                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG);
                 } else {
                     rewind($job['bodyHandle']);
                     $contents = stream_get_contents($job['bodyHandle']);
                     $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
-                    $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
+                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG);
                 }
                 fclose($job['bodyHandle']);
 
@@ -362,7 +362,7 @@ class CurlDownloader
         }
 
         if (!empty($targetUrl)) {
-            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
+            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);
 
             return $targetUrl;
         }

+ 2 - 2
src/Composer/Util/RemoteFilesystem.php

@@ -246,7 +246,7 @@ class RemoteFilesystem
 
         $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 ') . $this->authHelper->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
+        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . Url::sanitize($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
@@ -704,7 +704,7 @@ class RemoteFilesystem
             $this->redirects++;
 
             $this->io->writeError('', true, IOInterface::DEBUG);
-            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
+            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);
 
             $additionalOptions['redirects'] = $this->redirects;
 

+ 17 - 0
src/Composer/Util/Url.php

@@ -102,4 +102,21 @@ class Url
 
         return $origin;
     }
+
+    public static function sanitize($url)
+    {
+        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
+        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
+        $url = preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
+
+        $url = preg_replace_callback('{://(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@}i', function ($m) {
+            if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) {
+                return '://***:***@';
+            }
+
+            return '://'.$m['user'].':***@';
+        }, $url);
+
+        return $url;
+    }
 }

+ 21 - 0
tests/Composer/Test/Util/UrlTest.php

@@ -58,4 +58,25 @@ class UrlTest extends TestCase
             array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'),
         );
     }
+
+    /**
+     * @dataProvider sanitizeProvider
+     */
+    public function testSanitize($expected, $url)
+    {
+        $this->assertSame($expected, Url::sanitize($url));
+    }
+
+    public static function sanitizeProvider()
+    {
+        return array(
+            array('https://foo:***@example.org/', 'https://foo:bar@example.org/'),
+            array('https://foo@example.org/', 'https://foo@example.org/'),
+            array('https://example.org/', 'https://example.org/'),
+            array('http://***:***@example.org', 'http://10a8f08e8d7b7b9:foo@example.org'),
+            array('https://foo:***@example.org:123/', 'https://foo:bar@example.org:123/'),
+            array('https://example.org/foo/bar?access_token=***', 'https://example.org/foo/bar?access_token=abcdef'),
+            array('https://example.org/foo/bar?foo=bar&access_token=***', 'https://example.org/foo/bar?foo=bar&access_token=abcdef'),
+        );
+    }
 }