Merge branch '1.10'
commit
8b934a415f
|
@ -16,6 +16,7 @@ use Composer\Composer;
|
||||||
use Composer\Factory;
|
use Composer\Factory;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\SelfUpdate\Keys;
|
use Composer\SelfUpdate\Keys;
|
||||||
use Composer\SelfUpdate\Versions;
|
use Composer\SelfUpdate\Versions;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
@ -106,6 +107,11 @@ EOT
|
||||||
return $this->fetchKeys($io, $config);
|
return $this->fetchKeys($io, $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure composer.phar location is accessible
|
||||||
|
if (!file_exists($localFilename)) {
|
||||||
|
throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible');
|
||||||
|
}
|
||||||
|
|
||||||
// check if current dir is writable and if not try the cache dir from settings
|
// check if current dir is writable and if not try the cache dir from settings
|
||||||
$tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;
|
$tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;
|
||||||
|
|
||||||
|
@ -248,10 +254,8 @@ TAGSPUBKEY
|
||||||
$this->cleanBackups($rollbackDir);
|
$this->cleanBackups($rollbackDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
|
if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
|
||||||
@unlink($tempFilename);
|
@unlink($tempFilename);
|
||||||
$io->writeError('<error>The file is corrupted ('.$err->getMessage().').</error>');
|
|
||||||
$io->writeError('<error>Please re-run the self-update command to try again.</error>');
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -331,9 +335,7 @@ TAGSPUBKEY
|
||||||
|
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
$io->writeError(sprintf("Rolling back to version <info>%s</info>.", $rollbackVersion));
|
$io->writeError(sprintf("Rolling back to version <info>%s</info>.", $rollbackVersion));
|
||||||
if ($err = $this->setLocalPhar($localFilename, $oldFile)) {
|
if (!$this->setLocalPhar($localFilename, $oldFile)) {
|
||||||
$io->writeError('<error>The backup file was corrupted ('.$err->getMessage().').</error>');
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,37 +343,49 @@ TAGSPUBKEY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $localFilename
|
* Checks if the downloaded/rollback phar is valid then moves it
|
||||||
* @param string $newFilename
|
*
|
||||||
* @param string $backupTarget
|
* @param string $localFilename The composer.phar location
|
||||||
* @throws \Exception
|
* @param string $newFilename The downloaded or backup phar
|
||||||
* @return \UnexpectedValueException|\PharException|null
|
* @param string $backupTarget The filename to use for the backup
|
||||||
|
* @throws \FilesystemException If the file cannot be moved
|
||||||
|
* @return bool Whether the phar is valid and has been moved
|
||||||
*/
|
*/
|
||||||
protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
|
protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
|
||||||
{
|
{
|
||||||
|
$io = $this->getIO();
|
||||||
|
@chmod($newFilename, fileperms($localFilename));
|
||||||
|
|
||||||
|
// check phar validity
|
||||||
|
if (!$this->validatePhar($newFilename, $error)) {
|
||||||
|
$io->writeError('<error>The '.($backupTarget ? 'update' : 'backup').' file is corrupted ('.$error.')</error>');
|
||||||
|
|
||||||
|
if ($backupTarget) {
|
||||||
|
$io->writeError('<error>Please re-run the self-update command to try again.</error>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy current file into backups dir
|
||||||
|
if ($backupTarget) {
|
||||||
|
@copy($localFilename, $backupTarget);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@chmod($newFilename, fileperms($localFilename));
|
|
||||||
if (!ini_get('phar.readonly')) {
|
|
||||||
// test the phar validity
|
|
||||||
$phar = new \Phar($newFilename);
|
|
||||||
// free the variable to unlock the file
|
|
||||||
unset($phar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy current file into installations dir
|
|
||||||
if ($backupTarget && file_exists($localFilename)) {
|
|
||||||
@copy($localFilename, $backupTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
rename($newFilename, $localFilename);
|
rename($newFilename, $localFilename);
|
||||||
|
|
||||||
return null;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
|
// see if we can run this operation as an Admin on Windows
|
||||||
throw $e;
|
if (!is_writable(dirname($localFilename))
|
||||||
|
&& $io->isInteractive()
|
||||||
|
&& $this->isWindowsNonAdminUser($isCygwin)) {
|
||||||
|
return $this->tryAsWindowsAdmin($localFilename, $newFilename, $isCygwin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $e;
|
$action = 'Composer '.($backupTarget ? 'update' : 'rollback');
|
||||||
|
throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,4 +428,129 @@ TAGSPUBKEY
|
||||||
|
|
||||||
return $finder;
|
return $finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the downloaded/backup phar file
|
||||||
|
*
|
||||||
|
* @param string $pharFile The downloaded or backup phar
|
||||||
|
* @param null|string $error Set by method on failure
|
||||||
|
*
|
||||||
|
* Code taken from getcomposer.org/installer. Any changes should be made
|
||||||
|
* there and replicated here
|
||||||
|
*
|
||||||
|
* @return bool If the operation succeeded
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function validatePhar($pharFile, &$error)
|
||||||
|
{
|
||||||
|
if (ini_get('phar.readonly')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test the phar validity
|
||||||
|
$phar = new \Phar($pharFile);
|
||||||
|
// Free the variable to unlock the file
|
||||||
|
unset($phar);
|
||||||
|
$result = true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
$error = $e->getMessage();
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this is a non-admin Windows user account
|
||||||
|
*
|
||||||
|
* @param null|bool $isCygwin Set by method
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isWindowsNonAdminUser(&$isCygwin)
|
||||||
|
{
|
||||||
|
$isCygwin = preg_match('/cygwin/i', php_uname());
|
||||||
|
|
||||||
|
if (!$isCygwin && !Platform::isWindows()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fltmc.exe manages filter drivers and errors without admin privileges
|
||||||
|
$command = sprintf('%sfltmc.exe filters', $isCygwin ? 'cmd.exe /c ' : '');
|
||||||
|
exec($command, $output, $exitCode);
|
||||||
|
|
||||||
|
return $exitCode !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes a UAC prompt to update composer.phar as an admin
|
||||||
|
*
|
||||||
|
* Uses a .vbs script to elevate and run the cmd.exe move command.
|
||||||
|
*
|
||||||
|
* @param string $localFilename The composer.phar location
|
||||||
|
* @param string $newFilename The downloaded or backup phar
|
||||||
|
* @param bool $isCygwin Whether we are running on Cygwin
|
||||||
|
* @return bool Whether composer.phar has been updated
|
||||||
|
*/
|
||||||
|
protected function tryAsWindowsAdmin($localFilename, $newFilename, $isCygwin)
|
||||||
|
{
|
||||||
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
$io->writeError('<error>Unable to write "'.$localFilename.'". Access is denied.</error>');
|
||||||
|
$helpMessage = 'Please run the self-update command as an Administrator.';
|
||||||
|
$question = 'Complete this operation with Administrator privileges [<comment>Y,n</comment>]? ';
|
||||||
|
|
||||||
|
if (!$io->askConfirmation($question, false)) {
|
||||||
|
$io->writeError('<warning>Operation cancelled. '.$helpMessage.'</warning>');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpFile = tempnam(sys_get_temp_dir(), '');
|
||||||
|
$script = $tmpFile.'.vbs';
|
||||||
|
rename($tmpFile, $script);
|
||||||
|
|
||||||
|
$checksum = hash_file('sha256', $newFilename);
|
||||||
|
|
||||||
|
// format the file names for cmd.exe
|
||||||
|
if ($isCygwin) {
|
||||||
|
$source = exec(sprintf("cygpath -w '%s'", $newFilename));
|
||||||
|
$destination = exec(sprintf("cygpath -w '%s'", $localFilename));
|
||||||
|
} else {
|
||||||
|
// cmd's internal move is fussy about backslashes
|
||||||
|
$source = str_replace('/', '\\', $newFilename);
|
||||||
|
$destination = str_replace('/', '\\', $localFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
$vbs = <<<EOT
|
||||||
|
Set UAC = CreateObject("Shell.Application")
|
||||||
|
UAC.ShellExecute "cmd.exe", "/c move /y ""$source"" ""$destination""", "", "runas", 0
|
||||||
|
Wscript.Sleep(300)
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
file_put_contents($script, $vbs);
|
||||||
|
|
||||||
|
if ($isCygwin) {
|
||||||
|
chmod($script, 0755);
|
||||||
|
$cygscript = sprintf('"%s"', exec(sprintf("cygpath -w '%s'", $script)));
|
||||||
|
$command = sprintf("cmd.exe /c '%s'", $cygscript);
|
||||||
|
} else {
|
||||||
|
$command = sprintf('"%s"', $script);
|
||||||
|
}
|
||||||
|
|
||||||
|
exec($command);
|
||||||
|
@unlink($script);
|
||||||
|
|
||||||
|
// see if the file was moved
|
||||||
|
if ($result = (hash_file('sha256', $localFilename) === $checksum)) {
|
||||||
|
$io->writeError('<info>Operation succeeded.</info>');
|
||||||
|
} else {
|
||||||
|
$io->writeError('<error>Operation failed (file not written). '.$helpMessage.'</error>');
|
||||||
|
};
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ class GitLab
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName()));
|
$this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName()));
|
||||||
$this->io->writeError('To revoke access to this token you can visit '.$originUrl.'/profile/applications');
|
$this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/profile/applications');
|
||||||
|
|
||||||
$attemptCounter = 0;
|
$attemptCounter = 0;
|
||||||
|
|
||||||
|
@ -131,12 +131,17 @@ class GitLab
|
||||||
// 403 is max login attempts exceeded
|
// 403 is max login attempts exceeded
|
||||||
if (in_array($e->getCode(), array(403, 401))) {
|
if (in_array($e->getCode(), array(403, 401))) {
|
||||||
if (401 === $e->getCode()) {
|
if (401 === $e->getCode()) {
|
||||||
$this->io->writeError('Bad credentials.');
|
$response = json_decode($e->getResponse(), true);
|
||||||
|
if (isset($response['error']) && $response['error'] === 'invalid_grant') {
|
||||||
|
$this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token');
|
||||||
|
} else {
|
||||||
|
$this->io->writeError('Bad credentials.');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
|
$this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError('You can also manually create a personal token at '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens');
|
$this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens');
|
||||||
$this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' <token>"');
|
$this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' <token>"');
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -351,7 +351,7 @@ class RemoteFilesystem
|
||||||
if ($originUrl === 'bitbucket.org'
|
if ($originUrl === 'bitbucket.org'
|
||||||
&& !$this->authHelper->isPublicBitBucketDownload($fileUrl)
|
&& !$this->authHelper->isPublicBitBucketDownload($fileUrl)
|
||||||
&& substr($fileUrl, -4) === '.zip'
|
&& substr($fileUrl, -4) === '.zip'
|
||||||
&& (!$locationHeader || substr($locationHeader, -4) !== '.zip')
|
&& (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip')
|
||||||
&& $contentType && preg_match('{^text/html\b}i', $contentType)
|
&& $contentType && preg_match('{^text/html\b}i', $contentType)
|
||||||
) {
|
) {
|
||||||
$result = false;
|
$result = false;
|
||||||
|
|
Loading…
Reference in New Issue