diff --git a/.gitignore b/.gitignore index dfbe013f5..5992f93fc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.project /.buildpath /composer.phar -/vendor \ No newline at end of file +/vendor +/nbproject diff --git a/README.md b/README.md index 50788a490..9dae242e9 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Installation / Usage 1. Download the [`composer.phar`](http://getcomposer.org/composer.phar) executable or use the installer. - + ``` sh $ curl -s http://getcomposer.org/installer | php + ``` 2. Create a composer.json defining your dependencies. Note that this example is diff --git a/bin/compile b/bin/compile old mode 100755 new mode 100644 diff --git a/bin/composer b/bin/composer old mode 100755 new mode 100644 diff --git a/composer.lock b/composer.lock index 953c1a7a2..da880d14a 100644 --- a/composer.lock +++ b/composer.lock @@ -1,6 +1,10 @@ { "hash": "9c243b2c15fdc7c3e35c5200d704ba53", "packages": [ + { + "package": "symfony\/process", + "version": "2.1.0-dev" + }, { "package": "symfony\/finder", "version": "2.1.0-dev" @@ -8,10 +12,6 @@ { "package": "symfony\/console", "version": "2.1.0-dev" - }, - { - "package": "symfony\/process", - "version": "2.1.0-dev" } ] } \ No newline at end of file diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 26e1a5ba2..d656ad5d1 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -46,7 +46,7 @@ class InstallCommand extends Command ->setName('install') ->setDescription('Parses the composer.json file and downloads the needed dependencies.') ->setDefinition(array( - new InputOption('dev', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-install-recommends', null, InputOption::VALUE_NONE, 'Do not install recommended packages (ignored when installing from an existing lock file).'), new InputOption('install-suggests', null, InputOption::VALUE_NONE, 'Also install suggested packages (ignored when installing from an existing lock file).'), @@ -73,7 +73,7 @@ EOT $io, $composer, $eventDispatcher, - (Boolean)$input->getOption('dev'), + (Boolean)$input->getOption('prefer-source'), (Boolean)$input->getOption('dry-run'), (Boolean)$input->getOption('verbose'), (Boolean)$input->getOption('no-install-recommends'), diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 50ad96c6d..febc78295 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Composer; +use Composer\Util\StreamContextFactory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -39,7 +40,9 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $latest = trim(file_get_contents('http://getcomposer.org/version')); + $ctx = StreamContextFactory::getContext(); + + $latest = trim(file_get_contents('http://getcomposer.org/version'), false, $ctx); if (Composer::VERSION !== $latest) { $output->writeln(sprintf("Updating to version %s.", $latest)); @@ -47,7 +50,7 @@ EOT $remoteFilename = 'http://getcomposer.org/composer.phar'; $localFilename = $_SERVER['argv'][0]; - file_put_contents($localFilename, file_get_contents($remoteFilename)); + copy($remoteFilename, $localFilename, $ctx); } else { $output->writeln("You are using the latest composer version."); } diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 3c2b394c0..7e1e8e0c1 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -35,7 +35,7 @@ class UpdateCommand extends Command ->setName('update') ->setDescription('Updates your dependencies to the latest version, and updates the composer.lock file.') ->setDefinition(array( - new InputOption('dev', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-install-recommends', null, InputOption::VALUE_NONE, 'Do not install recommended packages.'), new InputOption('install-suggests', null, InputOption::VALUE_NONE, 'Also install suggested packages.'), @@ -63,7 +63,7 @@ EOT $io, $composer, $eventDispatcher, - (Boolean)$input->getOption('dev'), + (Boolean)$input->getOption('prefer-source'), (Boolean)$input->getOption('dry-run'), (Boolean)$input->getOption('verbose'), (Boolean)$input->getOption('no-install-recommends'), diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 4898fd795..c21b66248 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -79,7 +79,8 @@ class Compiler $phar->stopBuffering(); - $phar->compressFiles(\Phar::GZ); + // disabled for interoperability with systems without gzip ext + // $phar->compressFiles(\Phar::GZ); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false); diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 67a60c1ea..ecd286d78 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -14,6 +14,7 @@ namespace Composer\Json; use Composer\Repository\RepositoryManager; use Composer\Composer; +use Composer\Util\StreamContextFactory; /** * Reads/writes json files. @@ -59,11 +60,12 @@ class JsonFile */ public function read() { - $context = stream_context_create(array( - 'http' => array('header' => 'User-Agent: Composer/'.Composer::VERSION."\r\n") - )); + $ctx = StreamContextFactory::getContext(array( + 'http' => array( + 'header' => 'User-Agent: Composer/'.Composer::VERSION."\r\n" + ))); - $json = file_get_contents($this->path, false, $context); + $json = file_get_contents($this->path, false, $ctx); if (!$json) { throw new \RuntimeException('Could not read '.$this->path.', you are probably offline'); } @@ -76,8 +78,9 @@ class JsonFile * * @param array $hash writes hash into json file * @param Boolean $prettyPrint If true, output is pretty-printed + * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped */ - public function write(array $hash, $prettyPrint = true) + public function write(array $hash, $prettyPrint = true, $unescapeUnicode = true) { $dir = dirname($this->path); if (!is_dir($dir)) { @@ -92,7 +95,7 @@ class JsonFile ); } } - file_put_contents($this->path, static::encode($hash, $prettyPrint)); + file_put_contents($this->path, static::encode($hash, $prettyPrint, $unescapeUnicode)); } /** @@ -103,17 +106,23 @@ class JsonFile * * @param array $hash Data to encode into a formatted JSON string * @param Boolean $prettyPrint If true, output is pretty-printed + * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped * @return string Indented version of the original JSON string */ - static public function encode(array $hash, $prettyPrint = true) + static public function encode(array $hash, $prettyPrint = true, $unescapeUnicode = true) { - if ($prettyPrint && defined('JSON_PRETTY_PRINT')) { - return json_encode($hash, JSON_PRETTY_PRINT); + if (version_compare(PHP_VERSION, '5.4', '>=')) { + $options = $prettyPrint ? JSON_PRETTY_PRINT : 0; + if ($unescapeUnicode) { + $options |= JSON_UNESCAPED_UNICODE; + } + + return json_encode($hash, $options); } $json = json_encode($hash); - if (!$prettyPrint) { + if (!$prettyPrint && !$unescapeUnicode) { return $json; } @@ -122,21 +131,43 @@ class JsonFile $strLen = strlen($json); $indentStr = ' '; $newLine = "\n"; - $prevChar = ''; $outOfQuotes = true; + $buffer = ''; + $noescape = true; for ($i = 0; $i <= $strLen; $i++) { // Grab the next character in the string $char = substr($json, $i, 1); // Are we inside a quoted string? - if ('"' === $char && ('\\' !== $prevChar || '\\\\' === substr($json, $i-2, 2))) { + if ('"' === $char && $noescape) { $outOfQuotes = !$outOfQuotes; - } elseif (':' === $char && $outOfQuotes) { + } + + if (!$outOfQuotes) { + $buffer .= $char; + $noescape = '\\' === $char ? !$noescape : true; + continue; + } elseif ('' !== $buffer) { + if ($unescapeUnicode && function_exists('mb_convert_encoding')) { + // http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha + $result .= preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($match) { + return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); + }, $buffer.$char); + } else { + $result .= $buffer.$char; + } + + $buffer = ''; + continue; + } + + if (':' === $char) { // Add a space after the : character $char .= ' '; - } elseif (('}' === $char || ']' === $char) && $outOfQuotes) { + } elseif (('}' === $char || ']' === $char)) { $pos--; + $prevChar = substr($json, $i - 1, 1); if ('{' !== $prevChar && '[' !== $prevChar) { // If this character is the end of an element, @@ -151,12 +182,11 @@ class JsonFile } } - // Add the character to the result string $result .= $char; // If the last character was the beginning of an element, // output a new line and indent the next line - if ((',' === $char || '{' === $char || '[' === $char) && $outOfQuotes) { + if (',' === $char || '{' === $char || '[' === $char) { $result .= $newLine; if ('{' === $char || '[' === $char) { @@ -167,8 +197,6 @@ class JsonFile $result .= $indentStr; } } - - $prevChar = $char; } return $result; diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index ee2a0b376..f372929b1 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -24,11 +24,16 @@ class ArrayDumper { $keys = array( 'binaries' => 'bin', + 'scripts', 'type', 'names', 'extra', 'installationSource' => 'installation-source', 'license', + 'authors', + 'description', + 'homepage', + 'keywords', 'autoload', 'repositories', ); @@ -41,6 +46,10 @@ class ArrayDumper $data['target-dir'] = $package->getTargetDir(); } + if ($package->getReleaseDate()) { + $data['time'] = $package->getReleaseDate()->format('Y-m-d H:i:s'); + } + if ($package->getSourceType()) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); diff --git a/src/Composer/Package/MemoryPackage.php b/src/Composer/Package/MemoryPackage.php index 02222e43b..d1e63e102 100644 --- a/src/Composer/Package/MemoryPackage.php +++ b/src/Composer/Package/MemoryPackage.php @@ -438,7 +438,7 @@ class MemoryPackage extends BasePackage * * @param DateTime $releaseDate */ - public function setReleasedate(\DateTime $releaseDate) + public function setReleaseDate(\DateTime $releaseDate) { $this->releaseDate = $releaseDate; } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 51c1722b4..edf5b9e3b 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,6 +13,7 @@ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; +use Composer\Util\StreamContextFactory; /** * @author Benjamin Eberlei @@ -20,7 +21,8 @@ use Composer\Package\Loader\ArrayLoader; */ class PearRepository extends ArrayRepository { - protected $url; + private $url; + private $streamContext; public function __construct(array $config) { @@ -31,7 +33,7 @@ class PearRepository extends ArrayRepository throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']); } - $this->url = $config['url']; + $this->url = rtrim($config['url'], '/'); } protected function initialize() @@ -41,6 +43,7 @@ class PearRepository extends ArrayRepository set_error_handler(function($severity, $message, $file, $line) { throw new \ErrorException($message, $severity, $severity, $file, $line); }); + $this->streamContext = StreamContextFactory::getContext(); $this->fetchFromServer(); restore_error_handler(); } @@ -51,24 +54,62 @@ class PearRepository extends ArrayRepository $categories = $categoryXML->getElementsByTagName("c"); foreach ($categories as $category) { - $categoryLink = $category->getAttribute("xlink:href"); - $categoryLink = str_replace("info.xml", "packages.xml", $categoryLink); - if ('/' !== substr($categoryLink, 0, 1)) { - $categoryLink = '/' . $categoryLink; + $link = '/' . ltrim($category->getAttribute("xlink:href"), '/'); + try { + $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link); + $this->fetchPear2Packages($this->url . $packagesLink); + } catch (\ErrorException $e) { + if (false === strpos($e->getMessage(), '404')) { + throw $e; + } + $categoryLink = str_replace("info.xml", "packages.xml", $link); + $this->fetchPearPackages($this->url . $categoryLink); } - $packagesXML = $this->requestXml($this->url . $categoryLink); - $packages = $packagesXML->getElementsByTagName('p'); - $loader = new ArrayLoader(); - foreach ($packages as $package) { - $packageName = $package->nodeValue; + } + } - $packageLink = $package->getAttribute('xlink:href'); - $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink); - $allReleasesLink = $releaseLink . "/allreleases2.xml"; + /** + * @param string $categoryLink + * @throws ErrorException + * @throws InvalidArgumentException + */ + private function fetchPearPackages($categoryLink) + { + $packagesXML = $this->requestXml($categoryLink); + $packages = $packagesXML->getElementsByTagName('p'); + $loader = new ArrayLoader(); + foreach ($packages as $package) { + $packageName = $package->nodeValue; + + $packageLink = $package->getAttribute('xlink:href'); + $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink); + $allReleasesLink = $releaseLink . "/allreleases2.xml"; + + try { + $releasesXML = $this->requestXml($allReleasesLink); + } catch (\ErrorException $e) { + if (strpos($e->getMessage(), '404')) { + continue; + } + throw $e; + } + + $releases = $releasesXML->getElementsByTagName('r'); + + foreach ($releases as $release) { + /* @var $release \DOMElement */ + $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue; + + $packageData = array( + 'name' => $packageName, + 'type' => 'library', + 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"), + 'version' => $pearVersion, + ); try { - $releasesXML = $this->requestXml($allReleasesLink); + $deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt", false, $this->streamContext); } catch (\ErrorException $e) { if (strpos($e->getMessage(), '404')) { continue; @@ -76,54 +117,155 @@ class PearRepository extends ArrayRepository throw $e; } - $releases = $releasesXML->getElementsByTagName('r'); + $packageData += $this->parseDependencies($deps); - foreach ($releases as $release) { - /* @var $release DOMElement */ - $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue; + try { + $this->addPackage($loader->load($packageData)); + } catch (\UnexpectedValueException $e) { + continue; + } + } + } + } - $packageData = array( - 'name' => $packageName, - 'type' => 'library', - 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"), - 'version' => $pearVersion, + /** + * @param array $data + * @return string + */ + private function parseVersion(array $data) + { + if (!isset($data['min']) && !isset($data['max'])) { + return '*'; + } + $versions = array(); + if (isset($data['min'])) { + $versions[] = '>=' . $data['min']; + } + if (isset($data['max'])) { + $versions[] = '<=' . $data['max']; + } + return implode(',', $versions); + } + + /** + * @todo Improve dependencies resolution of pear packages. + * @param array $options + * @return array + */ + private function parseDependenciesOptions(array $depsOptions) + { + $data = array(); + foreach ($depsOptions as $name => $options) { + // make sure single deps are wrapped in an array + if (isset($options['name'])) { + $options = array($options); + } + if ('php' == $name) { + $data[$name] = $this->parseVersion($options); + } elseif ('package' == $name) { + foreach ($options as $key => $value) { + if (is_array($value)) { + $dataKey = $value['name']; + $data[$dataKey] = $this->parseVersion($value); + } + } + } elseif ('extension' == $name) { + foreach ($options as $key => $value) { + $dataKey = 'ext-' . $value['name']; + $data[$dataKey] = $this->parseVersion($value); + } + } + } + return $data; + } + + /** + * @param string $deps + * @return array + * @throws InvalidArgumentException + */ + private function parseDependencies($deps) + { + if (preg_match('((O:([0-9])+:"([^"]+)"))', $deps, $matches)) { + if (strlen($matches[3]) == $matches[2]) { + throw new \InvalidArgumentException("Invalid dependency data, it contains serialized objects."); + } + } + $deps = (array) @unserialize($deps); + unset($deps['required']['pearinstaller']); + + $depsData = array(); + if (!empty($deps['required'])) { + $depsData['require'] = $this->parseDependenciesOptions($deps['required']); + } + + if (!empty($deps['optional'])) { + $depsData['suggest'] = $this->parseDependenciesOptions($deps['optional']); + } + + return $depsData; + } + + /** + * @param string $packagesLink + * @return void + * @throws InvalidArgumentException + */ + private function fetchPear2Packages($packagesLink) + { + $loader = new ArrayLoader(); + $packagesXml = $this->requestXml($packagesLink); + $informations = $packagesXml->getElementsByTagName('pi'); + foreach ($informations as $information) { + $package = $information->getElementsByTagName('p')->item(0); + + $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue; + $packageData = array( + 'name' => $packageName, + 'type' => 'library' + ); + $packageKeys = array('l' => 'license', 'd' => 'description'); + foreach ($packageKeys as $pear => $composer) { + if ($package->getElementsByTagName($pear)->length > 0 + && ($pear = $package->getElementsByTagName($pear)->item(0)->nodeValue)) { + $packageData[$composer] = $pear; + } + } + + $depsData = array(); + foreach ($information->getElementsByTagName('deps') as $depElement) { + $depsVersion = $depElement->getElementsByTagName('v')->item(0)->nodeValue; + $depsData[$depsVersion] = $this->parseDependencies( + $depElement->getElementsByTagName('d')->item(0)->nodeValue + ); + } + + $releases = $information->getElementsByTagName('a')->item(0); + if (!$releases) { + continue; + } + + $releases = $releases->getElementsByTagName('r'); + $packageUrl = $this->url . '/get/' . $packageName; + foreach ($releases as $release) { + $version = $release->getElementsByTagName('v')->item(0)->nodeValue; + $releaseData = array( + 'dist' => array( + 'type' => 'pear', + 'url' => $packageUrl . '-' . $version . '.tgz' + ), + 'version' => $version + ); + if (isset($depsData[$version])) { + $releaseData += $depsData[$version]; + } + + try { + $this->addPackage( + $loader->load($packageData + $releaseData) ); - - try { - $deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt"); - } catch (\ErrorException $e) { - if (strpos($e->getMessage(), '404')) { - continue; - } - throw $e; - } - - if (preg_match('((O:([0-9])+:"([^"]+)"))', $deps, $matches)) { - if (strlen($matches[3]) == $matches[2]) { - throw new \InvalidArgumentException("Invalid dependency data, it contains serialized objects."); - } - } - $deps = unserialize($deps); - if (isset($deps['required']['package'])) { - - if (isset($deps['required']['package']['name'])) { - $deps['required']['package'] = array($deps['required']['package']); - } - - foreach ($deps['required']['package'] as $dependency) { - if (isset($dependency['min'])) { - $packageData['require'][$dependency['name']] = '>='.$dependency['min']; - } else { - $packageData['require'][$dependency['name']] = '>=0.0.0'; - } - } - } - - try { - $this->addPackage($loader->load($packageData)); - } catch (\UnexpectedValueException $e) { - continue; - } + } catch (\UnexpectedValueException $e) { + continue; } } } @@ -135,7 +277,7 @@ class PearRepository extends ArrayRepository */ private function requestXml($url) { - $content = file_get_contents($url); + $content = file_get_contents($url, false, $this->streamContext); if (!$content) { throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 54aca9356..036c0b2fa 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -87,37 +87,20 @@ class RemoteFilesystem $this->fileName = $fileName; $this->progress = $progess; - // Handle system proxy - $params = array('http' => array()); - - if (isset($_SERVER['HTTP_PROXY'])) { - // http(s):// is not supported in proxy - $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']); - - if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { - throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); - } - - $params['http'] = array( - 'proxy' => $proxy, - 'request_fulluri' => true, - ); - } - // add authorization in context + $options = array(); if ($this->io->hasAuthorization($originUrl)) { - $auth = $this->io->getAuthorization($originUrl); - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); + $auth = $this->io->getAuthorization($originUrl); + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $options['http']['header'] = "Authorization: Basic $authStr\r\n"; } else if (null !== $this->io->getLastUsername()) { $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); - $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); } - $ctx = stream_context_create($params); - stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); + $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); if ($this->progress) { $this->io->overwrite(" Downloading: connection...", false); diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php new file mode 100644 index 000000000..4ea31da4d --- /dev/null +++ b/src/Composer/Util/StreamContextFactory.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Allows the creation of a basic context supporting http proxy + * + * @author Jordan Alliot + */ +final class StreamContextFactory +{ + /** + * Creates a context supporting HTTP proxies + * + * @param array $defaultOptions Options to merge with the default + * @param array $defaultParams Parameters to specify on the context + * @return resource Default context + * @throws \RuntimeException if https proxy required and OpenSSL uninstalled + */ + static public function getContext(array $defaultOptions = array(), array $defaultParams = array()) + { + $options = array('http' => array()); + + // Handle system proxy + if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) { + // Some systems seem to rely on a lowercased version instead... + $proxy = isset($_SERVER['HTTP_PROXY']) ? $_SERVER['HTTP_PROXY'] : $_SERVER['http_proxy']; + + // http(s):// is not supported in proxy + $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxy); + + if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { + throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); + } + + $options['http'] = array( + 'proxy' => $proxy, + 'request_fulluri' => true, + ); + } + + $options = array_merge_recursive($options, $defaultOptions); + + return stream_context_create($options, $defaultParams); + } +} diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 4ebf7b0c5..4ed016fa6 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -121,6 +121,26 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertJsonFormat($json, $data); } + public function testEscape() + { + $data = array("Metadata\\\"" => 'src/'); + $json = '{ + "Metadata\\\\\\"": "src\/" +}'; + + $this->assertJsonFormat($json, $data); + } + + public function testUnicode() + { + $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €"); + $json = '{ + "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" +}'; + + $this->assertJsonFormat($json, $data); + } + private function expectParseException($text, $json) { try { diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php new file mode 100644 index 000000000..eab3450bf --- /dev/null +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Dumper; + +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\MemoryPackage; + +class ArrayDumperTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + $this->dumper = new ArrayDumper(); + } + + public function testRequiredInformations() + { + $package = new MemoryPackage('foo', '1.0.0.0', '1.0'); + + $config = $this->dumper->dump($package); + $this->assertEquals(array('name', 'version', 'version_normalized', 'type', 'names'), array_keys($config)); + } + + /** + * @dataProvider getKeys + */ + public function testKeys($key, $value, $expectedValue = null, $method = null) + { + $package = new MemoryPackage('foo', '1.0.0.0', '1.0'); + + $setter = 'set'.ucfirst($method ?: $key); + $package->$setter($value); + + $config = $this->dumper->dump($package); + $this->assertArrayHasKey($key, $config); + + $expectedValue = $expectedValue ?: $value; + $this->assertSame($expectedValue, $config[$key]); + } + + public function getKeys() + { + return array( + array('time', new \DateTime('2012-02-01'), '2012-02-01 00:00:00', 'ReleaseDate'), + array('authors', array('Nils Adermann ', 'Jordi Boggiano ')), + array('homepage', 'http://getcomposer.org'), + array('description', 'Package Manager'), + array('keywords', array('package', 'dependency', 'autoload')), + array('bin', array('bin/composer'), null, 'binaries'), + array('license', array('MIT')), + array('autoload', array('psr-0' => array('Composer' => 'src/'))), + array('repositories', array('packagist' => false)), + array('scripts', array('post-update-cmd' => 'MyVendor\\MyClass::postUpdate')), + array('extra', array('class' => 'MyVendor\\Installer')), + ); + } +}