Browse Source

Merge remote branch 'composer/master'

Conflicts:
	src/Composer/Downloader/FileDownloader.php
François Pluchino 13 years ago
parent
commit
2e3eed081a

+ 2 - 1
.gitignore

@@ -2,4 +2,5 @@
 /.project
 /.buildpath
 /composer.phar
-/vendor
+/vendor
+/nbproject

+ 2 - 1
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

+ 0 - 0
bin/compile


+ 0 - 0
bin/composer


+ 3 - 3
composer.lock

@@ -2,15 +2,15 @@
     "hash": "9c243b2c15fdc7c3e35c5200d704ba53",
     "packages": [
         {
-            "package": "symfony\/finder",
+            "package": "symfony\/process",
             "version": "2.1.0-dev"
         },
         {
-            "package": "symfony\/console",
+            "package": "symfony\/finder",
             "version": "2.1.0-dev"
         },
         {
-            "package": "symfony\/process",
+            "package": "symfony\/console",
             "version": "2.1.0-dev"
         }
     ]

+ 2 - 2
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'),

+ 5 - 2
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 <info>%s</info>.", $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("<info>You are using the latest composer version.</info>");
         }

+ 2 - 2
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'),

+ 2 - 1
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);
 

+ 46 - 18
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;

+ 9 - 0
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();

+ 1 - 1
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;
     }

+ 200 - 58
src/Composer/Repository/PearRepository.php

@@ -13,6 +13,7 @@
 namespace Composer\Repository;
 
 use Composer\Package\Loader\ArrayLoader;
+use Composer\Util\StreamContextFactory;
 
 /**
  * @author Benjamin Eberlei <kontakt@beberlei.de>
@@ -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);
+    }
 
-                    try {
-                        $deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt");
-                    } catch (\ErrorException $e) {
-                        if (strpos($e->getMessage(), '404')) {
-                            continue;
-                        }
-                        throw $e;
+    /**
+     * @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;
+    }
 
-                    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';
-                            }
-                        }
-                    }
+    /**
+     * @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']);
 
-                    try {
-                        $this->addPackage($loader->load($packageData));
-                    } catch (\UnexpectedValueException $e) {
-                        continue;
-                    }
+        $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)
+                    );
+                } 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.');
         }

+ 6 - 23
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: <comment>connection...</comment>", false);

+ 56 - 0
src/Composer/Util/StreamContextFactory.php

@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <jordan.alliot@gmail.com>
+ */
+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);
+    }
+}

+ 20 - 0
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 {

+ 66 - 0
tests/Composer/Test/Package/Dumper/ArrayDumperTest.php

@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <naderman@naderman.de>', 'Jordi Boggiano <j.boggiano@seld.be>')),
+            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')),
+        );
+    }
+}