Browse Source

Add support for authentication with mercurial repositories.

Jonas Renaudot 6 years ago
parent
commit
1a725d5e1f

+ 15 - 16
src/Composer/Downloader/HgDownloader.php

@@ -14,6 +14,7 @@ namespace Composer\Downloader;
 
 use Composer\Package\PackageInterface;
 use Composer\Util\ProcessExecutor;
+use Composer\Util\Hg as HgUtils;
 
 /**
  * @author Per Bernhardt <plb@webfactory.de>
@@ -25,16 +26,15 @@ class HgDownloader extends VcsDownloader
      */
     public function doDownload(PackageInterface $package, $path, $url)
     {
-        // Ensure we are allowed to use this URL by config
-        $this->config->prohibitUrlByConfig($url, $this->io);
+        $hgUtils = new HgUtils($this->io, $this->config, $this->process);
+
+        $cloneCommand = function($url) use ($path) {
+            return sprintf('hg clone %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path));
+        };
+
+        $hgUtils->runCommand($cloneCommand, $url, $path);
 
-        $url = ProcessExecutor::escape($url);
         $ref = ProcessExecutor::escape($package->getSourceReference());
-        $this->io->writeError("Cloning ".$package->getSourceReference());
-        $command = sprintf('hg clone %s %s', $url, ProcessExecutor::escape($path));
-        if (0 !== $this->process->execute($command, $ignoredOutput)) {
-            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
-        }
         $command = sprintf('hg up %s', $ref);
         if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
             throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
@@ -46,21 +46,20 @@ class HgDownloader extends VcsDownloader
      */
     public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
     {
-        // Ensure we are allowed to use this URL by config
-        $this->config->prohibitUrlByConfig($url, $this->io);
+        $hgUtils = new HgUtils($this->io, $this->config, $this->process);
 
-        $url = ProcessExecutor::escape($url);
-        $ref = ProcessExecutor::escape($target->getSourceReference());
+        $ref = $target->getSourceReference();
         $this->io->writeError(" Updating to ".$target->getSourceReference());
 
         if (!$this->hasMetadataRepository($path)) {
             throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
         }
 
-        $command = sprintf('hg pull %s && hg up %s', $url, $ref);
-        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
-            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
-        }
+        $command = function($url) use ($ref) {
+            return sprintf('hg pull %s && hg up %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref));
+        };
+
+        $hgUtils->runCommand($command, $url, $path);
     }
 
     /**

+ 7 - 8
src/Composer/Repository/Vcs/HgDriver.php

@@ -13,6 +13,7 @@
 namespace Composer\Repository\Vcs;
 
 use Composer\Config;
+use Composer\Util\Hg as HgUtils;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\Filesystem;
 use Composer\IO\IOInterface;
@@ -49,6 +50,8 @@ class HgDriver extends VcsDriver
             // Ensure we are allowed to use this URL by config
             $this->config->prohibitUrlByConfig($this->url, $this->io);
 
+            $hgUtils = new HgUtils($this->io, $this->config, $this->process);
+
             // update the repo if it is a valid hg repository
             if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) {
                 if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) {
@@ -58,15 +61,11 @@ class HgDriver extends VcsDriver
                 // clean up directory and do a fresh clone into it
                 $fs->removeDirectory($this->repoDir);
 
-                if (0 !== $this->process->execute(sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoDir)), $output, $cacheDir)) {
-                    $output = $this->process->getErrorOutput();
-
-                    if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
-                        throw new \RuntimeException('Failed to clone '.$this->url.', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput());
-                    }
+                $command = function($url) {
+                    return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir));
+                };
 
-                    throw new \RuntimeException('Failed to clone '.$this->url.', could not read packages from it' . "\n\n" .$output);
-                }
+                $hgUtils->runCommand($command, $this->url, $this->repoDir);
             }
         }
 

+ 91 - 0
src/Composer/Util/Hg.php

@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Util;
+
+use Composer\Config;
+use Composer\IO\IOInterface;
+
+/**
+ * @author Jonas Renaudot <jonas.renaudot@gmail.com>
+ */
+class Hg
+{
+
+    /**
+     * @var \Composer\IO\IOInterface
+     */
+    private $io;
+
+    /**
+     * @var \Composer\Config
+     */
+    private $config;
+
+    /**
+     * @var \Composer\Util\ProcessExecutor
+     */
+    private $process;
+
+    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process)
+    {
+        $this->io = $io;
+        $this->config = $config;
+        $this->process = $process;
+    }
+
+    public function runCommand($commandCallable, $url, $cwd) {
+        $this->config->prohibitUrlByConfig($url, $this->io);
+
+        // Try as is
+        $command = call_user_func($commandCallable, $url);
+
+        if (0 === $this->process->execute($command, $ignoredOutput, $cwd)){
+            return;
+        }
+
+        // Try with the authentication informations available
+        if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) {
+            $auth = $this->io->getAuthentication($match[5]);
+            $authenticatedUrl = $match[1] . '://' . rawurldecode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6])? $match[6]: null);
+
+            $command = call_user_func($commandCallable, $authenticatedUrl);
+
+            if (0 === $this->process->execute($command)) {
+                return;
+            }
+        }
+
+        $this->throwException('Failed to clone ' . $url . ', aborting', $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(self::sanitizeUrl($message));
+    }
+}