Sanitize URLs in getRepoName and centralize the Url sanitization process
parent
c41df325d8
commit
1e68555e0a
|
@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Composer\Util\Git as GitUtil;
|
use Composer\Util\Git as GitUtil;
|
||||||
|
use Composer\Util\Url;
|
||||||
use Composer\Util\Platform;
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Cache;
|
use Composer\Cache;
|
||||||
|
@ -434,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
|
$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)
|
protected function updateOriginUrl($path, $url)
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
public function getRepoName()
|
public function getRepoName()
|
||||||
{
|
{
|
||||||
return 'platform repo ('.$this->lookup.')';
|
return 'artifact repo ('.$this->lookup.')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRepoConfig()
|
public function getRepoConfig()
|
||||||
|
|
|
@ -34,6 +34,7 @@ use Composer\Semver\Constraint\Constraint;
|
||||||
use Composer\Semver\Constraint\EmptyConstraint;
|
use Composer\Semver\Constraint\EmptyConstraint;
|
||||||
use Composer\Util\Http\Response;
|
use Composer\Util\Http\Response;
|
||||||
use Composer\Util\MetadataMinifier;
|
use Composer\Util\MetadataMinifier;
|
||||||
|
use Composer\Util\Url;
|
||||||
use React\Promise\Util as PromiseUtil;
|
use React\Promise\Util as PromiseUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,7 +130,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
public function getRepoName()
|
public function getRepoName()
|
||||||
{
|
{
|
||||||
return 'composer repo ('.$this->url.')';
|
return 'composer repo ('.Url::sanitize($this->url).')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRepoConfig()
|
public function getRepoConfig()
|
||||||
|
|
|
@ -41,7 +41,7 @@ class CompositeRepository extends BaseRepository
|
||||||
|
|
||||||
public function getRepoName()
|
public function getRepoName()
|
||||||
{
|
{
|
||||||
return 'composite repo ('.count($this->repositories).' repos)';
|
return 'composite repo ('.implode(', ', array_map(function ($repo) { return $repo->getRepoName(); }, $this->repositories)).')';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Package\Version\VersionGuesser;
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
use Composer\Util\Platform;
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
use Composer\Util\Url;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This repository allows installing local packages that are not necessarily under their own VCS.
|
* 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()
|
public function getRepoName()
|
||||||
{
|
{
|
||||||
return 'path repo ('.$this->repoConfig['url'].')';
|
return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRepoConfig()
|
public function getRepoConfig()
|
||||||
|
|
|
@ -22,6 +22,7 @@ use Composer\Package\Loader\LoaderInterface;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\HttpDownloader;
|
use Composer\Util\HttpDownloader;
|
||||||
|
use Composer\Util\Url;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
||||||
$driverType = $driverClass;
|
$driverType = $driverClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'vcs repo ('.$driverType.' '.$this->url.')';
|
return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRepoConfig()
|
public function getRepoConfig()
|
||||||
|
|
|
@ -255,15 +255,4 @@ class AuthHelper
|
||||||
|
|
||||||
return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,27 +362,16 @@ class Git
|
||||||
return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
|
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)
|
private function throwException($message, $url)
|
||||||
{
|
{
|
||||||
// git might delete a directory when it fails and php will not know
|
// git might delete a directory when it fails and php will not know
|
||||||
clearstatcache();
|
clearstatcache();
|
||||||
|
|
||||||
if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -72,23 +72,12 @@ class Hg
|
||||||
$this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url);
|
$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)
|
private function throwException($message, $url)
|
||||||
{
|
{
|
||||||
if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ class CurlDownloader
|
||||||
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
|
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
|
||||||
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
|
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
|
||||||
if ($attributes['redirects'] === 0) {
|
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));
|
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
|
||||||
|
@ -254,12 +254,12 @@ class CurlDownloader
|
||||||
$contents = stream_get_contents($job['bodyHandle']);
|
$contents = stream_get_contents($job['bodyHandle']);
|
||||||
}
|
}
|
||||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
|
$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 {
|
} else {
|
||||||
rewind($job['bodyHandle']);
|
rewind($job['bodyHandle']);
|
||||||
$contents = stream_get_contents($job['bodyHandle']);
|
$contents = stream_get_contents($job['bodyHandle']);
|
||||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
|
$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']);
|
fclose($job['bodyHandle']);
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ class CurlDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($targetUrl)) {
|
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;
|
return $targetUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,7 +246,7 @@ class RemoteFilesystem
|
||||||
|
|
||||||
$actualContextOptions = stream_context_get_options($ctx);
|
$actualContextOptions = stream_context_get_options($ctx);
|
||||||
$usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
|
$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);
|
unset($origFileUrl, $actualContextOptions);
|
||||||
|
|
||||||
// Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
// 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->redirects++;
|
||||||
|
|
||||||
$this->io->writeError('', true, IOInterface::DEBUG);
|
$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;
|
$additionalOptions['redirects'] = $this->redirects;
|
||||||
|
|
||||||
|
|
|
@ -102,4 +102,21 @@ class Url
|
||||||
|
|
||||||
return $origin;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue