PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Composer/Util/Svn.php

http://github.com/composer/composer
PHP | 381 lines | 186 code | 54 blank | 141 comment | 24 complexity | 488a34da2d9cafc03bb49f9d32a8ffa6 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Util;
  12. use Composer\Config;
  13. use Composer\IO\IOInterface;
  14. /**
  15. * @author Till Klampaeckel <till@php.net>
  16. * @author Jordi Boggiano <j.boggiano@seld.be>
  17. */
  18. class Svn
  19. {
  20. const MAX_QTY_AUTH_TRIES = 5;
  21. /**
  22. * @var array
  23. */
  24. protected $credentials;
  25. /**
  26. * @var bool
  27. */
  28. protected $hasAuth;
  29. /**
  30. * @var \Composer\IO\IOInterface
  31. */
  32. protected $io;
  33. /**
  34. * @var string
  35. */
  36. protected $url;
  37. /**
  38. * @var bool
  39. */
  40. protected $cacheCredentials = true;
  41. /**
  42. * @var ProcessExecutor
  43. */
  44. protected $process;
  45. /**
  46. * @var int
  47. */
  48. protected $qtyAuthTries = 0;
  49. /**
  50. * @var \Composer\Config
  51. */
  52. protected $config;
  53. /**
  54. * @var string|null
  55. */
  56. private static $version;
  57. /**
  58. * @param string $url
  59. * @param \Composer\IO\IOInterface $io
  60. * @param Config $config
  61. * @param ProcessExecutor $process
  62. */
  63. public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null)
  64. {
  65. $this->url = $url;
  66. $this->io = $io;
  67. $this->config = $config;
  68. $this->process = $process ?: new ProcessExecutor($io);
  69. }
  70. public static function cleanEnv()
  71. {
  72. // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
  73. putenv("DYLD_LIBRARY_PATH");
  74. unset($_SERVER['DYLD_LIBRARY_PATH']);
  75. }
  76. /**
  77. * Execute an SVN remote command and try to fix up the process with credentials
  78. * if necessary.
  79. *
  80. * @param string $command SVN command to run
  81. * @param string $url SVN url
  82. * @param string $cwd Working directory
  83. * @param string $path Target for a checkout
  84. * @param bool $verbose Output all output to the user
  85. *
  86. * @throws \RuntimeException
  87. * @return string
  88. */
  89. public function execute($command, $url, $cwd = null, $path = null, $verbose = false)
  90. {
  91. // Ensure we are allowed to use this URL by config
  92. $this->config->prohibitUrlByConfig($url, $this->io);
  93. return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose);
  94. }
  95. /**
  96. * Execute an SVN local command and try to fix up the process with credentials
  97. * if necessary.
  98. *
  99. * @param string $command SVN command to run
  100. * @param string $path Path argument passed thru to the command
  101. * @param string $cwd Working directory
  102. * @param bool $verbose Output all output to the user
  103. *
  104. * @throws \RuntimeException
  105. * @return string
  106. */
  107. public function executeLocal($command, $path, $cwd = null, $verbose = false)
  108. {
  109. // A local command has no remote url
  110. return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
  111. }
  112. private function executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose)
  113. {
  114. // Regenerate the command at each try, to use the newly user-provided credentials
  115. $command = $this->getCommand($svnCommand, $url, $path);
  116. $output = null;
  117. $io = $this->io;
  118. $handler = function ($type, $buffer) use (&$output, $io, $verbose) {
  119. if ($type !== 'out') {
  120. return;
  121. }
  122. if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
  123. return;
  124. }
  125. $output .= $buffer;
  126. if ($verbose) {
  127. $io->writeError($buffer, false);
  128. }
  129. };
  130. $status = $this->process->execute($command, $handler, $cwd);
  131. if (0 === $status) {
  132. return $output;
  133. }
  134. $errorOutput = $this->process->getErrorOutput();
  135. $fullOutput = implode("\n", array($output, $errorOutput));
  136. // the error is not auth-related
  137. if (false === stripos($fullOutput, 'Could not authenticate to server:')
  138. && false === stripos($fullOutput, 'authorization failed')
  139. && false === stripos($fullOutput, 'svn: E170001:')
  140. && false === stripos($fullOutput, 'svn: E215004:')) {
  141. throw new \RuntimeException($fullOutput);
  142. }
  143. if (!$this->hasAuth()) {
  144. $this->doAuthDance();
  145. }
  146. // try to authenticate if maximum quantity of tries not reached
  147. if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) {
  148. // restart the process
  149. return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose);
  150. }
  151. throw new \RuntimeException(
  152. 'wrong credentials provided ('.$fullOutput.')'
  153. );
  154. }
  155. /**
  156. * @param bool $cacheCredentials
  157. */
  158. public function setCacheCredentials($cacheCredentials)
  159. {
  160. $this->cacheCredentials = $cacheCredentials;
  161. }
  162. /**
  163. * Repositories requests credentials, let's put them in.
  164. *
  165. * @throws \RuntimeException
  166. * @return \Composer\Util\Svn
  167. */
  168. protected function doAuthDance()
  169. {
  170. // cannot ask for credentials in non interactive mode
  171. if (!$this->io->isInteractive()) {
  172. throw new \RuntimeException(
  173. 'can not ask for authentication in non interactive mode'
  174. );
  175. }
  176. $this->io->writeError("The Subversion server ({$this->url}) requested credentials:");
  177. $this->hasAuth = true;
  178. $this->credentials['username'] = $this->io->ask("Username: ");
  179. $this->credentials['password'] = $this->io->askAndHideAnswer("Password: ");
  180. $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true);
  181. return $this;
  182. }
  183. /**
  184. * A method to create the svn commands run.
  185. *
  186. * @param string $cmd Usually 'svn ls' or something like that.
  187. * @param string $url Repo URL.
  188. * @param string $path Target for a checkout
  189. *
  190. * @return string
  191. */
  192. protected function getCommand($cmd, $url, $path = null)
  193. {
  194. $cmd = sprintf(
  195. '%s %s%s %s',
  196. $cmd,
  197. '--non-interactive ',
  198. $this->getCredentialString(),
  199. ProcessExecutor::escape($url)
  200. );
  201. if ($path) {
  202. $cmd .= ' ' . ProcessExecutor::escape($path);
  203. }
  204. return $cmd;
  205. }
  206. /**
  207. * Return the credential string for the svn command.
  208. *
  209. * Adds --no-auth-cache when credentials are present.
  210. *
  211. * @return string
  212. */
  213. protected function getCredentialString()
  214. {
  215. if (!$this->hasAuth()) {
  216. return '';
  217. }
  218. return sprintf(
  219. ' %s--username %s --password %s ',
  220. $this->getAuthCache(),
  221. ProcessExecutor::escape($this->getUsername()),
  222. ProcessExecutor::escape($this->getPassword())
  223. );
  224. }
  225. /**
  226. * Get the password for the svn command. Can be empty.
  227. *
  228. * @throws \LogicException
  229. * @return string
  230. */
  231. protected function getPassword()
  232. {
  233. if ($this->credentials === null) {
  234. throw new \LogicException("No svn auth detected.");
  235. }
  236. return isset($this->credentials['password']) ? $this->credentials['password'] : '';
  237. }
  238. /**
  239. * Get the username for the svn command.
  240. *
  241. * @throws \LogicException
  242. * @return string
  243. */
  244. protected function getUsername()
  245. {
  246. if ($this->credentials === null) {
  247. throw new \LogicException("No svn auth detected.");
  248. }
  249. return $this->credentials['username'];
  250. }
  251. /**
  252. * Detect Svn Auth.
  253. *
  254. * @return bool
  255. */
  256. protected function hasAuth()
  257. {
  258. if (null !== $this->hasAuth) {
  259. return $this->hasAuth;
  260. }
  261. if (false === $this->createAuthFromConfig()) {
  262. $this->createAuthFromUrl();
  263. }
  264. return (bool) $this->hasAuth;
  265. }
  266. /**
  267. * Return the no-auth-cache switch.
  268. *
  269. * @return string
  270. */
  271. protected function getAuthCache()
  272. {
  273. return $this->cacheCredentials ? '' : '--no-auth-cache ';
  274. }
  275. /**
  276. * Create the auth params from the configuration file.
  277. *
  278. * @return bool
  279. */
  280. private function createAuthFromConfig()
  281. {
  282. if (!$this->config->has('http-basic')) {
  283. return $this->hasAuth = false;
  284. }
  285. $authConfig = $this->config->get('http-basic');
  286. $host = parse_url($this->url, PHP_URL_HOST);
  287. if (isset($authConfig[$host])) {
  288. $this->credentials['username'] = $authConfig[$host]['username'];
  289. $this->credentials['password'] = $authConfig[$host]['password'];
  290. return $this->hasAuth = true;
  291. }
  292. return $this->hasAuth = false;
  293. }
  294. /**
  295. * Create the auth params from the url
  296. *
  297. * @return bool
  298. */
  299. private function createAuthFromUrl()
  300. {
  301. $uri = parse_url($this->url);
  302. if (empty($uri['user'])) {
  303. return $this->hasAuth = false;
  304. }
  305. $this->credentials['username'] = $uri['user'];
  306. if (!empty($uri['pass'])) {
  307. $this->credentials['password'] = $uri['pass'];
  308. }
  309. return $this->hasAuth = true;
  310. }
  311. /**
  312. * Returns the version of the svn binary contained in PATH
  313. *
  314. * @return string|null
  315. */
  316. public function binaryVersion()
  317. {
  318. if (!self::$version) {
  319. if (0 === $this->process->execute('svn --version', $output)) {
  320. if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match)) {
  321. self::$version = $match[1];
  322. }
  323. }
  324. }
  325. return self::$version;
  326. }
  327. }