Svn.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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. * @param string $url
  55. * @param \Composer\IO\IOInterface $io
  56. * @param Config $config
  57. * @param ProcessExecutor $process
  58. */
  59. public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null)
  60. {
  61. $this->url = $url;
  62. $this->io = $io;
  63. $this->config = $config;
  64. $this->process = $process ?: new ProcessExecutor;
  65. }
  66. public static function cleanEnv()
  67. {
  68. // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
  69. putenv("DYLD_LIBRARY_PATH");
  70. unset($_SERVER['DYLD_LIBRARY_PATH']);
  71. }
  72. /**
  73. * Execute an SVN command and try to fix up the process with credentials
  74. * if necessary.
  75. *
  76. * @param string $command SVN command to run
  77. * @param string $url SVN url
  78. * @param string $cwd Working directory
  79. * @param string $path Target for a checkout
  80. * @param bool $verbose Output all output to the user
  81. *
  82. * @throws \RuntimeException
  83. * @return string
  84. */
  85. public function execute($command, $url, $cwd = null, $path = null, $verbose = false)
  86. {
  87. // Ensure we are allowed to use this URL by config
  88. $this->config->prohibitUrlByConfig($url, $this->io);
  89. $svnCommand = $this->getCommand($command, $url, $path);
  90. $output = null;
  91. $io = $this->io;
  92. $handler = function ($type, $buffer) use (&$output, $io, $verbose) {
  93. if ($type !== 'out') {
  94. return;
  95. }
  96. if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
  97. return;
  98. }
  99. $output .= $buffer;
  100. if ($verbose) {
  101. $io->writeError($buffer, false);
  102. }
  103. };
  104. $status = $this->process->execute($svnCommand, $handler, $cwd);
  105. if (0 === $status) {
  106. return $output;
  107. }
  108. $errorOutput = $this->process->getErrorOutput();
  109. $fullOutput = implode("\n", array($output, $errorOutput));
  110. // the error is not auth-related
  111. if (false === stripos($fullOutput, 'Could not authenticate to server:')
  112. && false === stripos($fullOutput, 'authorization failed')
  113. && false === stripos($fullOutput, 'svn: E170001:')
  114. && false === stripos($fullOutput, 'svn: E215004:')) {
  115. throw new \RuntimeException($fullOutput);
  116. }
  117. if (!$this->hasAuth()) {
  118. $this->doAuthDance();
  119. }
  120. // try to authenticate if maximum quantity of tries not reached
  121. if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) {
  122. // restart the process
  123. return $this->execute($command, $url, $cwd, $path, $verbose);
  124. }
  125. throw new \RuntimeException(
  126. 'wrong credentials provided ('.$fullOutput.')'
  127. );
  128. }
  129. /**
  130. * @param bool $cacheCredentials
  131. */
  132. public function setCacheCredentials($cacheCredentials)
  133. {
  134. $this->cacheCredentials = $cacheCredentials;
  135. }
  136. /**
  137. * Repositories requests credentials, let's put them in.
  138. *
  139. * @throws \RuntimeException
  140. * @return \Composer\Util\Svn
  141. */
  142. protected function doAuthDance()
  143. {
  144. // cannot ask for credentials in non interactive mode
  145. if (!$this->io->isInteractive()) {
  146. throw new \RuntimeException(
  147. 'can not ask for authentication in non interactive mode'
  148. );
  149. }
  150. $this->io->writeError("The Subversion server ({$this->url}) requested credentials:");
  151. $this->hasAuth = true;
  152. $this->credentials['username'] = $this->io->ask("Username: ");
  153. $this->credentials['password'] = $this->io->askAndHideAnswer("Password: ");
  154. $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true);
  155. return $this;
  156. }
  157. /**
  158. * A method to create the svn commands run.
  159. *
  160. * @param string $cmd Usually 'svn ls' or something like that.
  161. * @param string $url Repo URL.
  162. * @param string $path Target for a checkout
  163. *
  164. * @return string
  165. */
  166. protected function getCommand($cmd, $url, $path = null)
  167. {
  168. $cmd = sprintf('%s %s%s %s',
  169. $cmd,
  170. '--non-interactive ',
  171. $this->getCredentialString(),
  172. ProcessExecutor::escape($url)
  173. );
  174. if ($path) {
  175. $cmd .= ' ' . ProcessExecutor::escape($path);
  176. }
  177. return $cmd;
  178. }
  179. /**
  180. * Return the credential string for the svn command.
  181. *
  182. * Adds --no-auth-cache when credentials are present.
  183. *
  184. * @return string
  185. */
  186. protected function getCredentialString()
  187. {
  188. if (!$this->hasAuth()) {
  189. return '';
  190. }
  191. return sprintf(
  192. ' %s--username %s --password %s ',
  193. $this->getAuthCache(),
  194. ProcessExecutor::escape($this->getUsername()),
  195. ProcessExecutor::escape($this->getPassword())
  196. );
  197. }
  198. /**
  199. * Get the password for the svn command. Can be empty.
  200. *
  201. * @throws \LogicException
  202. * @return string
  203. */
  204. protected function getPassword()
  205. {
  206. if ($this->credentials === null) {
  207. throw new \LogicException("No svn auth detected.");
  208. }
  209. return isset($this->credentials['password']) ? $this->credentials['password'] : '';
  210. }
  211. /**
  212. * Get the username for the svn command.
  213. *
  214. * @throws \LogicException
  215. * @return string
  216. */
  217. protected function getUsername()
  218. {
  219. if ($this->credentials === null) {
  220. throw new \LogicException("No svn auth detected.");
  221. }
  222. return $this->credentials['username'];
  223. }
  224. /**
  225. * Detect Svn Auth.
  226. *
  227. * @return bool
  228. */
  229. protected function hasAuth()
  230. {
  231. if (null !== $this->hasAuth) {
  232. return $this->hasAuth;
  233. }
  234. if (false === $this->createAuthFromConfig()) {
  235. $this->createAuthFromUrl();
  236. }
  237. return $this->hasAuth;
  238. }
  239. /**
  240. * Return the no-auth-cache switch.
  241. *
  242. * @return string
  243. */
  244. protected function getAuthCache()
  245. {
  246. return $this->cacheCredentials ? '' : '--no-auth-cache ';
  247. }
  248. /**
  249. * Create the auth params from the configuration file.
  250. *
  251. * @return bool
  252. */
  253. private function createAuthFromConfig()
  254. {
  255. if (!$this->config->has('http-basic')) {
  256. return $this->hasAuth = false;
  257. }
  258. $authConfig = $this->config->get('http-basic');
  259. $host = parse_url($this->url, PHP_URL_HOST);
  260. if (isset($authConfig[$host])) {
  261. $this->credentials['username'] = $authConfig[$host]['username'];
  262. $this->credentials['password'] = $authConfig[$host]['password'];
  263. return $this->hasAuth = true;
  264. }
  265. return $this->hasAuth = false;
  266. }
  267. /**
  268. * Create the auth params from the url
  269. *
  270. * @return bool
  271. */
  272. private function createAuthFromUrl()
  273. {
  274. $uri = parse_url($this->url);
  275. if (empty($uri['user'])) {
  276. return $this->hasAuth = false;
  277. }
  278. $this->credentials['username'] = $uri['user'];
  279. if (!empty($uri['pass'])) {
  280. $this->credentials['password'] = $uri['pass'];
  281. }
  282. return $this->hasAuth = true;
  283. }
  284. }