private function throwException($message, $url) { // git might delete a directory when it fails and php will not know clearstatcache(); if (0 !== $this->process->execute('git --version', $ignoredOutput)) { throw new \RuntimeException('Failed to clone ' . self::sanitizeUrl($url) . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException($message); }
/** * Retrieve removed files list between two package versions * * @param PackageInterface $targert new package version to install * @param PackageInterface $initial previous package version * * @return array */ protected function getRemovedFiles(PackageInterface $target, PackageInterface $initial) { $output = false; $removedFiles = array(); if ($initial->getSourceReference() != $target->getSourceReference()) { if ($this->io->isVerbose()) { $this->io->write("Retrieving list of removed files from previous version installed."); } $command = sprintf('git diff --name-only --diff-filter=D %s %s', $initial->getSourceReference(), $target->getSourceReference()); if (0 !== $this->process->execute($command, $output, $this->rubedoRootDir)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $removedFiles = explode(PHP_EOL, ltrim(rtrim($output))); } return $removedFiles; }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * * @return string * * @throws \RuntimeException */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } if ('Redirecting to URL ' === substr($buffer, 0, 19)) { return; } $output .= $buffer; if ($verbose) { $io->write($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } if (empty($output)) { $output = $this->process->getErrorOutput(); } // the error is not auth-related if (false === stripos($output, 'Could not authenticate to server:') && false === stripos($output, 'authorization failed') && false === stripos($output, 'svn: E170001:') && false === stripos($output, 'svn: E215004:')) { throw new \RuntimeException($output); } // no auth supported for non interactive calls if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode (' . $output . ')'); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES || !$this->hasAuth()) { $this->doAuthDance(); // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $output . ')'); }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * * @throws \RuntimeException * @return string */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { if (preg_match('{^(http|svn):}i', $url) && $this->config->get('secure-http')) { throw new TransportException("Your configuration does not allow connection to {$url}. See https://getcomposer.org/doc/06-config.md#secure-http for details."); } $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } if ('Redirecting to URL ' === substr($buffer, 0, 19)) { return; } $output .= $buffer; if ($verbose) { $io->writeError($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } $errorOutput = $this->process->getErrorOutput(); $fullOutput = implode("\n", array($output, $errorOutput)); // the error is not auth-related if (false === stripos($fullOutput, 'Could not authenticate to server:') && false === stripos($fullOutput, 'authorization failed') && false === stripos($fullOutput, 'svn: E170001:') && false === stripos($fullOutput, 'svn: E215004:')) { throw new \RuntimeException($fullOutput); } if (!$this->hasAuth()) { $this->doAuthDance(); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $fullOutput . ')'); }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param Boolean $verbose Output all output to the user * * @return string * * @throws \RuntimeException */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } $output .= $buffer; if ($verbose) { $io->write($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } if (empty($output)) { $output = $this->process->getErrorOutput(); } // the error is not auth-related if (false === stripos($output, 'authorization failed:')) { throw new \RuntimeException($output); } // no auth supported for non interactive calls if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode (' . $output . ')'); } // TODO keep a count of user auth attempts and ask 5 times before // failing hard (currently it fails hard directly if the URL has credentials) // try to authenticate if (!$this->hasAuth()) { $this->doAuthDance(); // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $output . ')'); }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * * @throws \RuntimeException * @return string */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } if ('Redirecting to URL ' === substr($buffer, 0, 19)) { return; } $output .= $buffer; if ($verbose) { $io->writeError($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } $errorOutput = $this->process->getErrorOutput(); $fullOutput = implode("\n", array($output, $errorOutput)); // the error is not auth-related if (false === stripos($fullOutput, 'Could not authenticate to server:') && false === stripos($fullOutput, 'authorization failed') && false === stripos($fullOutput, 'svn: E170001:') && false === stripos($fullOutput, 'svn: E215004:')) { throw new \RuntimeException($fullOutput); } if (!$this->hasAuth()) { $this->doAuthDance(); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $fullOutput . ')'); }
protected function execute($command, array $args, IOInterface $io = null, $workingDirectory = null) { $processExecutor = new ProcessExecutor($io); array_unshift($args, $this->findBinary($command)); $args = array_map([$processExecutor, 'escape'], $args); $outputHandler = function ($type, $data) use($io) { if (!$io->isVerbose()) { return; } switch ($type) { case Process::ERR: $io->writeError($data); break; case Process::OUT: default: $io->write($data); break; } }; $fullCommand = implode(' ', $args); if ($processExecutor->execute($fullCommand, $outputHandler, $workingDirectory) > 0) { throw ProcessFailedException::create($command, $fullCommand, $processExecutor->getErrorOutput()); } }
/** * {@inheritDoc} */ public static function supports(IOInterface $io, $url, $deep = false) { $url = self::normalizeUrl($url); if (preg_match('#(^svn://|^svn\\+ssh://|svn\\.)#i', $url)) { return true; } // proceed with deep check for local urls since they are fast to process if (!$deep && !static::isLocalUrl($url)) { return false; } $processExecutor = new ProcessExecutor(); $exit = $processExecutor->execute("svn info --non-interactive {$url}", $ignoredOutput); if ($exit === 0) { // This is definitely a Subversion repository. return true; } if (false !== stripos($processExecutor->getErrorOutput(), 'authorization failed:')) { // This is likely a remote Subversion repository that requires // authentication. We will handle actual authentication later. return true; } return false; }
public function testExecuteCapturesStderr() { $process = new ProcessExecutor(); $process->execute('cat foo', $output); $this->assertNotNull($process->getErrorOutput()); }
public static function supports(IOInterface $io, Config $config, $url, $deep = false) { $url = self::normalizeUrl($url); if (preg_match('#(^svn://|^svn\\+ssh://|svn\\.)#i', $url)) { return true; } if (!$deep && !static::isLocalUrl($url)) { return false; } $processExecutor = new ProcessExecutor(); $exit = $processExecutor->execute("svn info --non-interactive {$url}", $ignoredOutput); if ($exit === 0) { return true; } if (false !== stripos($processExecutor->getErrorOutput(), 'authorization failed:')) { return true; } return false; }
/** * Scan the directory set in $repoConfig['url'] * and create any found packages. */ protected function scanDir() { $dir = $this->repoConfig['url']; // make sure tilde is not escaped so it can be expanded // this allows '~/' followed by a path or just '~' if (($tilde = substr($dir, 0, 2)) === '~/' || $tilde === '~') { $dir = $tilde . ProcessExecutor::escape(substr($dir, strlen($tilde))); } else { $dir = ProcessExecutor::escape($dir); } // patterns specific to both plugins and themes // 'inflating' is a line printed by unzip which indicates which internal file we are looking at $patterns = ['inflating|Version|Description|Author|Author URI|License']; // files within the archives to look at $files = []; // look for plugins? if (isset($this->repoConfig['package-types']['wordpress-plugin']) || isset($this->repoConfig['package-types']['wordpress-muplugin'])) { $patterns[] = 'Plugin Name|Plugin URI'; $files[] = "'*.php'"; } // look for themes? if (isset($this->repoConfig['package-types']['wordpress-theme'])) { $patterns[] = 'Theme Name|Theme URI'; $files[] = "'style.css'"; } // determine if we have a depth limit $maxdepth = ($depth = (int) $this->repoConfig['max-depth']) > 0 ? "-maxdepth {$depth}" : ''; // assemble the command // 1. `find` to get all zip files in the given directory // 2. echo the filename so we can capture where the zip is // 3. use `unzip` piped into `grep` to scan the zip for WP // theme or plugin headers in style.css or *.php files, // respectively, but only in the top two directories within the zip $cmd = "find -L {$dir} {$maxdepth} -iname '*.zip' -exec echo '{}' ';' -exec sh -c " . "\"unzip -c {} '*.php' -x '*/*/*' | grep -iE '^[ ^I*]*(" . implode('|', $patterns) . ")'\" ';'"; // if this is using ssh, wrap the command in an ssh call instead if ($this->ssh) { $cmd = 'ssh ' . ProcessExecutor::escape($this->repoConfig['ssh']) . ' ' . ProcessExecutor::escape($cmd); } $process = new ProcessExecutor($this->io); // execute the command and see if the response code indicates success // @todo: do we need to catch any exceptions here? if (($code = $process->execute($cmd, $output)) === 0) { // store details about each of the files, which may be used to create a package $files = []; $zipFile = null; $fileName = null; // parse the response line-by-line to pluck out the header information foreach ($process->splitLines($output) as $line) { // is this a new zip file? if (strtolower(substr($line, -4)) === '.zip') { $zipFile = $line; // is this a new internal file? } else { if (preg_match('/^\\s*inflating:\\s*(.+?)\\s*$/i', $line, $matches)) { $fileName = $matches[1]; } else { // parse the line for information if (preg_match('/^[\\s*]*([^:]+):\\s*(.+?)\\s*$/i', $line, $matches)) { // for clarity list(, $property, $value) = $matches; $files[$zipFile][$fileName][$property] = $value; } } } } // take the header information and create packages! foreach ($files as $url => $packages) { // we will only consider zips that have one package inside if (count($packages) === 1) { // make sure all the keys are consistent $headers = array_change_key_case(reset($packages), CASE_LOWER); // file within the zip where the headers were found $fileName = key($packages); // the info used to create the package $package = []; // we have a theme! if (!empty($headers['theme name'])) { $package['type'] = 'wordpress-theme'; $name = Util::slugify($headers['theme name']); $name = Util::callFilter($this->repoConfig['name-filter'], $name, $url, $fileName, $headers); if (!empty($headers['theme uri'])) { $package['homepage'] = $headers['theme uri']; } // we have a plugin! } else { if (!empty($headers['plugin name'])) { $package['type'] = 'wordpress-plugin'; // use the basename of the file where the plugin headers were as the name // this is a wordpress convention, but may not always be accurate // @todo: what do we do about that? $name = Util::slugify($headers['plugin name']); $name = Util::callFilter($this->repoConfig['name-filter'], $name, $url, $fileName, $headers); if (!empty($headers['plugin uri'])) { $package['homepage'] = $headers['plugin uri']; } // does not appear to be a theme or plugin // sometimes other files get picked up } else { if ($this->io->isVerbose()) { $this->io->writeError("{$url} does not appear to contain a valid package"); } continue; } } // if the name is empty we don't use it if (!strlen($name)) { continue; } // add version if (!empty($headers['version'])) { $package['version'] = Util::fixVersion($headers['version'], 'dev-default'); } else { $package['version'] = 'dev-default'; } $package['version'] = Util::callFilter($this->repoConfig['version-filter'], $package['version'], $name, $url, $fileName, $headers); // empty version means we don't use it if (!strlen($package['version'])) { continue; } // add author information if (!empty($headers['author'])) { $package['authors'][0]['name'] = $headers['author']; if (!empty($headers['author uri'])) { $package['authors'][0]['homepage'] = $headers['author uri']; } } // add description if (!empty($headers['description'])) { $package['description'] = strip_tags($headers['description']); } // add license if (!empty($headers['license'])) { $package['license'] = $headers['license']; } // add dist information $package['dist'] = ['url' => $this->ssh ? "ssh://{$this->repoConfig['ssh']}:{$url}" : $url, 'type' => 'zip']; // add a new package for each vendor alias of the given type // @todo: maybe use links instead? or in addition to? foreach ($this->repoConfig['vendors'] as $vendor => $type) { // match wordpress-plugin for wordpress-muplugin vendors if ($type === $package['type'] || $type === 'wordpress-muplugin' && $package['type'] === 'wordpress-plugin') { // this makes sure muplugins are the correct type $package['type'] = $type; $package['name'] = "{$vendor}/{$name}"; $packageObj = $this->loader->load($package); Util::callFilter($this->repoConfig['package-filter'], $packageObj, $url, $fileName, $headers); $this->addPackage($packageObj); } } } else { // if the zip contains multiple packages, we can't use it @todo - maybe make it possible? if ($this->io->isVerbose()) { $this->io->writeError("Cannot use file {$url} as is appears to contain multiple packages."); } } } } else { // some sort of error - boo! throw new \RuntimeException('Could not complete directory scan of ' . $this->repoConfig['url'] . '. ' . $process->getErrorOutput()); } }
/** * Run the given script file */ private function rcubeRunScript($script, PackageInterface $package) { @(list($vendor, $plugin_name) = explode('/', $package->getPrettyName())); // run executable shell script if (($scriptfile = realpath($this->getVendorDir() . "/{$plugin_name}/{$script}")) && is_executable($scriptfile)) { system($scriptfile, $res); } else { if ($scriptfile && preg_match('/\\.php$/', $scriptfile)) { $incdir = realpath(getcwd() . '/program/include'); include_once $incdir . '/iniset.php'; include $scriptfile; } else { $process = new ProcessExecutor(); $exitCode = $process->execute($script); if ($exitCode !== 0) { throw new \RuntimeException('Error executing script: ' . $process->getErrorOutput(), $exitCode); } } } }
protected function initialize() { $oldCwd = getcwd(); self::$gitRepo = sys_get_temp_dir() . '/composer-git-' . mt_rand() . '/'; $locator = new ExecutableFinder(); if (!$locator->find('git')) { $this->skipped = 'This test needs a git binary in the PATH to be able to run'; return; } if (!@mkdir(self::$gitRepo) || !@chdir(self::$gitRepo)) { $this->skipped = 'Could not create and move into the temp git repo ' . self::$gitRepo; return; } // init $process = new ProcessExecutor(); $exec = function ($command) use($process) { $cwd = getcwd(); if ($process->execute($command, $output, $cwd) !== 0) { throw new \RuntimeException('Failed to execute ' . $command . ': ' . $process->getErrorOutput()); } }; $exec('git init'); $exec('git config user.email composertest@example.org'); $exec('git config user.name ComposerTest'); touch('foo'); $exec('git add foo'); $exec('git commit -m init'); // non-composed tag & branch $exec('git tag 0.5.0'); $exec('git branch oldbranch'); // add composed tag & master branch $composer = array('name' => 'a/b'); file_put_contents('composer.json', json_encode($composer)); $exec('git add composer.json'); $exec('git commit -m addcomposer'); $exec('git tag 0.6.0'); // add feature-a branch $exec('git checkout -b feature/a-1.0-B'); file_put_contents('foo', 'bar feature'); $exec('git add foo'); $exec('git commit -m change-a'); // add version to composer.json $exec('git checkout master'); $composer['version'] = '1.0.0'; file_put_contents('composer.json', json_encode($composer)); $exec('git add composer.json'); $exec('git commit -m addversion'); // create tag with wrong version in it $exec('git tag 0.9.0'); // create tag with correct version in it $exec('git tag 1.0.0'); // add feature-b branch $exec('git checkout -b feature-b'); file_put_contents('foo', 'baz feature'); $exec('git add foo'); $exec('git commit -m change-b'); // add 1.0 branch $exec('git checkout master'); $exec('git branch 1.0'); // add 1.0.x branch $exec('git branch 1.1.x'); // update master to 2.0 $composer['version'] = '2.0.0'; file_put_contents('composer.json', json_encode($composer)); $exec('git add composer.json'); $exec('git commit -m bump-version'); chdir($oldCwd); }