Svn.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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\IO\IOInterface;
  13. /**
  14. * @author Till Klampaeckel <till@php.net>
  15. * @author Jordi Boggiano <j.boggiano@seld.be>
  16. */
  17. class Svn
  18. {
  19. const MAX_QTY_AUTH_TRIES = 5;
  20. /**
  21. * @var array
  22. */
  23. protected $credentials;
  24. /**
  25. * @var bool
  26. */
  27. protected $hasAuth;
  28. /**
  29. * @var \Composer\IO\IOInterface
  30. */
  31. protected $io;
  32. /**
  33. * @var string
  34. */
  35. protected $url;
  36. /**
  37. * @var bool
  38. */
  39. protected $cacheCredentials = true;
  40. /**
  41. * @var ProcessExecutor
  42. */
  43. protected $process;
  44. /**
  45. * @var integer
  46. */
  47. protected $qtyAuthTries = 0;
  48. /**
  49. * @param string $url
  50. * @param \Composer\IO\IOInterface $io
  51. * @param ProcessExecutor $process
  52. */
  53. public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
  54. {
  55. $this->url = $url;
  56. $this->io = $io;
  57. $this->process = $process ?: new ProcessExecutor;
  58. }
  59. /**
  60. * Execute an SVN command and try to fix up the process with credentials
  61. * if necessary.
  62. *
  63. * @param string $command SVN command to run
  64. * @param string $url SVN url
  65. * @param string $cwd Working directory
  66. * @param string $path Target for a checkout
  67. * @param bool $verbose Output all output to the user
  68. *
  69. * @return string
  70. *
  71. * @throws \RuntimeException
  72. */
  73. public function execute($command, $url, $cwd = null, $path = null, $verbose = false)
  74. {
  75. $svnCommand = $this->getCommand($command, $url, $path);
  76. $output = null;
  77. $io = $this->io;
  78. $handler = function ($type, $buffer) use (&$output, $io, $verbose) {
  79. if ($type !== 'out') {
  80. return;
  81. }
  82. if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
  83. return;
  84. }
  85. $output .= $buffer;
  86. if ($verbose) {
  87. $io->write($buffer, false);
  88. }
  89. };
  90. $status = $this->process->execute($svnCommand, $handler, $cwd);
  91. if (0 === $status) {
  92. return $output;
  93. }
  94. if (empty($output)) {
  95. $output = $this->process->getErrorOutput();
  96. }
  97. // the error is not auth-related
  98. if (false === stripos($output, 'Could not authenticate to server:')
  99. && false === stripos($output, 'authorization failed')
  100. && false === stripos($output, 'svn: E170001:')
  101. && false === stripos($output, 'svn: E215004:')) {
  102. throw new \RuntimeException($output);
  103. }
  104. // no auth supported for non interactive calls
  105. if (!$this->io->isInteractive()) {
  106. throw new \RuntimeException(
  107. 'can not ask for authentication in non interactive mode ('.$output.')'
  108. );
  109. }
  110. // try to authenticate if maximum quantity of tries not reached
  111. if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES || !$this->hasAuth()) {
  112. $this->doAuthDance();
  113. // restart the process
  114. return $this->execute($command, $url, $cwd, $path, $verbose);
  115. }
  116. throw new \RuntimeException(
  117. 'wrong credentials provided ('.$output.')'
  118. );
  119. }
  120. /**
  121. * Repositories requests credentials, let's put them in.
  122. *
  123. * @return \Composer\Util\Svn
  124. */
  125. protected function doAuthDance()
  126. {
  127. $this->io->write("The Subversion server ({$this->url}) requested credentials:");
  128. $this->hasAuth = true;
  129. $this->credentials['username'] = $this->io->ask("Username: ");
  130. $this->credentials['password'] = $this->io->askAndHideAnswer("Password: ");
  131. $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true);
  132. return $this;
  133. }
  134. /**
  135. * A method to create the svn commands run.
  136. *
  137. * @param string $cmd Usually 'svn ls' or something like that.
  138. * @param string $url Repo URL.
  139. * @param string $path Target for a checkout
  140. *
  141. * @return string
  142. */
  143. protected function getCommand($cmd, $url, $path = null)
  144. {
  145. $cmd = sprintf('%s %s%s %s',
  146. $cmd,
  147. '--non-interactive ',
  148. $this->getCredentialString(),
  149. escapeshellarg($url)
  150. );
  151. if ($path) {
  152. $cmd .= ' ' . escapeshellarg($path);
  153. }
  154. return $cmd;
  155. }
  156. /**
  157. * Return the credential string for the svn command.
  158. *
  159. * Adds --no-auth-cache when credentials are present.
  160. *
  161. * @return string
  162. */
  163. protected function getCredentialString()
  164. {
  165. if (!$this->hasAuth()) {
  166. return '';
  167. }
  168. return sprintf(
  169. ' %s--username %s --password %s ',
  170. $this->getAuthCache(),
  171. escapeshellarg($this->getUsername()),
  172. escapeshellarg($this->getPassword())
  173. );
  174. }
  175. /**
  176. * Get the password for the svn command. Can be empty.
  177. *
  178. * @return string
  179. * @throws \LogicException
  180. */
  181. protected function getPassword()
  182. {
  183. if ($this->credentials === null) {
  184. throw new \LogicException("No svn auth detected.");
  185. }
  186. return isset($this->credentials['password']) ? $this->credentials['password'] : '';
  187. }
  188. /**
  189. * Get the username for the svn command.
  190. *
  191. * @return string
  192. * @throws \LogicException
  193. */
  194. protected function getUsername()
  195. {
  196. if ($this->credentials === null) {
  197. throw new \LogicException("No svn auth detected.");
  198. }
  199. return $this->credentials['username'];
  200. }
  201. /**
  202. * Detect Svn Auth.
  203. *
  204. * @return bool
  205. */
  206. protected function hasAuth()
  207. {
  208. if (null !== $this->hasAuth) {
  209. return $this->hasAuth;
  210. }
  211. $uri = parse_url($this->url);
  212. if (empty($uri['user'])) {
  213. return $this->hasAuth = false;
  214. }
  215. $this->credentials['username'] = $uri['user'];
  216. if (!empty($uri['pass'])) {
  217. $this->credentials['password'] = $uri['pass'];
  218. }
  219. return $this->hasAuth = true;
  220. }
  221. /**
  222. * Return the no-auth-cache switch.
  223. *
  224. * @return string
  225. */
  226. protected function getAuthCache()
  227. {
  228. return $this->cacheCredentials ? '' : '--no-auth-cache ';
  229. }
  230. }