php composer updates
[feisty_meow.git] / production / example_apps / shared_calendar / composer-setup.php
1 <?php
2
3 /*
4  * This file is part of Composer.
5  *
6  * (c) Nils Adermann <naderman@naderman.de>
7  *     Jordi Boggiano <j.boggiano@seld.be>
8  *
9  * For the full copyright and license information, please view the LICENSE
10  * file that was distributed with this source code.
11  */
12
13 setupEnvironment();
14 process(is_array($argv) ? $argv : array());
15
16 /**
17  * Initializes various values
18  *
19  * @throws RuntimeException If uopz extension prevents exit calls
20  */
21 function setupEnvironment()
22 {
23     ini_set('display_errors', 1);
24
25     if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
26         // uopz works at opcode level and disables exit calls
27         if (function_exists('uopz_allow_exit')) {
28             @uopz_allow_exit(true);
29         } else {
30             throw new RuntimeException('The uopz extension ignores exit calls and breaks this installer.');
31         }
32     }
33
34     $installer = 'ComposerInstaller';
35
36     if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
37         if ($version = getenv('COMPOSERSETUP')) {
38             $installer = sprintf('Composer-Setup.exe/%s', $version);
39         }
40     }
41
42     define('COMPOSER_INSTALLER', $installer);
43 }
44
45 /**
46  * Processes the installer
47  */
48 function process($argv)
49 {
50     // Determine ANSI output from --ansi and --no-ansi flags
51     setUseAnsi($argv);
52
53     if (in_array('--help', $argv)) {
54         displayHelp();
55         exit(0);
56     }
57
58     $check      = in_array('--check', $argv);
59     $help       = in_array('--help', $argv);
60     $force      = in_array('--force', $argv);
61     $quiet      = in_array('--quiet', $argv);
62     $channel    = 'stable';
63     if (in_array('--snapshot', $argv)) {
64         $channel = 'snapshot';
65     } elseif (in_array('--preview', $argv)) {
66         $channel = 'preview';
67     } elseif (in_array('--1', $argv)) {
68         $channel = '1';
69     } elseif (in_array('--2', $argv)) {
70         $channel = '2';
71     }
72     $disableTls = in_array('--disable-tls', $argv);
73     $installDir = getOptValue('--install-dir', $argv, false);
74     $version    = getOptValue('--version', $argv, false);
75     $filename   = getOptValue('--filename', $argv, 'composer.phar');
76     $cafile     = getOptValue('--cafile', $argv, false);
77
78     if (!checkParams($installDir, $version, $cafile)) {
79         exit(1);
80     }
81
82     $ok = checkPlatform($warnings, $quiet, $disableTls, true);
83
84     if ($check) {
85         // Only show warnings if we haven't output any errors
86         if ($ok) {
87             showWarnings($warnings);
88             showSecurityWarning($disableTls);
89         }
90         exit($ok ? 0 : 1);
91     }
92
93     if ($ok || $force) {
94         $installer = new Installer($quiet, $disableTls, $cafile);
95         if ($installer->run($version, $installDir, $filename, $channel)) {
96             showWarnings($warnings);
97             showSecurityWarning($disableTls);
98             exit(0);
99         }
100     }
101
102     exit(1);
103 }
104
105 /**
106  * Displays the help
107  */
108 function displayHelp()
109 {
110     echo <<<EOF
111 Composer Installer
112 ------------------
113 Options
114 --help               this help
115 --check              for checking environment only
116 --force              forces the installation
117 --ansi               force ANSI color output
118 --no-ansi            disable ANSI color output
119 --quiet              do not output unimportant messages
120 --install-dir="..."  accepts a target installation directory
121 --preview            install the latest version from the preview (alpha/beta/rc) channel instead of stable
122 --snapshot           install the latest version from the snapshot (dev builds) channel instead of stable
123 --1                  install the latest stable Composer 1.x version
124 --2                  install the latest stable Composer 2.x version
125 --version="..."      accepts a specific version to install instead of the latest
126 --filename="..."     accepts a target filename (default: composer.phar)
127 --disable-tls        disable SSL/TLS security for file downloads
128 --cafile="..."       accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification
129
130 EOF;
131 }
132
133 /**
134  * Sets the USE_ANSI define for colorizing output
135  *
136  * @param array $argv Command-line arguments
137  */
138 function setUseAnsi($argv)
139 {
140     // --no-ansi wins over --ansi
141     if (in_array('--no-ansi', $argv)) {
142         define('USE_ANSI', false);
143     } elseif (in_array('--ansi', $argv)) {
144         define('USE_ANSI', true);
145     } else {
146         define('USE_ANSI', outputSupportsColor());
147     }
148 }
149
150 /**
151  * Returns whether color output is supported
152  *
153  * @return bool
154  */
155 function outputSupportsColor()
156 {
157     if (false !== getenv('NO_COLOR') || !defined('STDOUT')) {
158         return false;
159     }
160
161     if ('Hyper' === getenv('TERM_PROGRAM')) {
162         return true;
163     }
164
165     if (defined('PHP_WINDOWS_VERSION_BUILD')) {
166         return (function_exists('sapi_windows_vt100_support')
167             && sapi_windows_vt100_support(STDOUT))
168             || false !== getenv('ANSICON')
169             || 'ON' === getenv('ConEmuANSI')
170             || 'xterm' === getenv('TERM');
171     }
172
173     if (function_exists('stream_isatty')) {
174         return stream_isatty(STDOUT);
175     }
176
177     if (function_exists('posix_isatty')) {
178         return posix_isatty(STDOUT);
179     }
180
181     $stat = fstat(STDOUT);
182     // Check if formatted mode is S_IFCHR
183     return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
184 }
185
186 /**
187  * Returns the value of a command-line option
188  *
189  * @param string $opt The command-line option to check
190  * @param array $argv Command-line arguments
191  * @param mixed $default Default value to be returned
192  *
193  * @return mixed The command-line value or the default
194  */
195 function getOptValue($opt, $argv, $default)
196 {
197     $optLength = strlen($opt);
198
199     foreach ($argv as $key => $value) {
200         $next = $key + 1;
201         if (0 === strpos($value, $opt)) {
202             if ($optLength === strlen($value) && isset($argv[$next])) {
203                 return trim($argv[$next]);
204             } else {
205                 return trim(substr($value, $optLength + 1));
206             }
207         }
208     }
209
210     return $default;
211 }
212
213 /**
214  * Checks that user-supplied params are valid
215  *
216  * @param mixed $installDir The required istallation directory
217  * @param mixed $version The required composer version to install
218  * @param mixed $cafile Certificate Authority file
219  *
220  * @return bool True if the supplied params are okay
221  */
222 function checkParams($installDir, $version, $cafile)
223 {
224     $result = true;
225
226     if (false !== $installDir && !is_dir($installDir)) {
227         out("The defined install dir ({$installDir}) does not exist.", 'info');
228         $result = false;
229     }
230
231     if (false !== $version && 1 !== preg_match('/^\d+\.\d+\.\d+(\-(alpha|beta|RC)\d*)*$/', $version)) {
232         out("The defined install version ({$version}) does not match release pattern.", 'info');
233         $result = false;
234     }
235
236     if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) {
237         out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info');
238         $result = false;
239     }
240     return $result;
241 }
242
243 /**
244  * Checks the platform for possible issues running Composer
245  *
246  * Errors are written to the output, warnings are saved for later display.
247  *
248  * @param array $warnings Populated by method, to be shown later
249  * @param bool $quiet Quiet mode
250  * @param bool $disableTls Bypass tls
251  * @param bool $install If we are installing, rather than diagnosing
252  *
253  * @return bool True if there are no errors
254  */
255 function checkPlatform(&$warnings, $quiet, $disableTls, $install)
256 {
257     getPlatformIssues($errors, $warnings, $install);
258
259     // Make openssl warning an error if tls has not been specifically disabled
260     if (isset($warnings['openssl']) && !$disableTls) {
261         $errors['openssl'] = $warnings['openssl'];
262         unset($warnings['openssl']);
263     }
264
265     if (!empty($errors)) {
266         // Composer-Setup.exe uses "Some settings" to flag platform errors
267         out('Some settings on your machine make Composer unable to work properly.', 'error');
268         out('Make sure that you fix the issues listed below and run this script again:', 'error');
269         outputIssues($errors);
270         return false;
271     }
272
273     if (empty($warnings) && !$quiet) {
274         out('All settings correct for using Composer', 'success');
275     }
276     return true;
277 }
278
279 /**
280  * Checks platform configuration for common incompatibility issues
281  *
282  * @param array $errors Populated by method
283  * @param array $warnings Populated by method
284  * @param bool $install If we are installing, rather than diagnosing
285  *
286  * @return bool If any errors or warnings have been found
287  */
288 function getPlatformIssues(&$errors, &$warnings, $install)
289 {
290     $errors = array();
291     $warnings = array();
292
293     if ($iniPath = php_ini_loaded_file()) {
294         $iniMessage = PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath;
295     } else {
296         $iniMessage = PHP_EOL.'A php.ini file does not exist. You will have to create one.';
297     }
298     $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';
299
300     if (ini_get('detect_unicode')) {
301         $errors['unicode'] = array(
302             'The detect_unicode setting must be disabled.',
303             'Add the following to the end of your `php.ini`:',
304             '    detect_unicode = Off',
305             $iniMessage
306         );
307     }
308
309     if (extension_loaded('suhosin')) {
310         $suhosin = ini_get('suhosin.executor.include.whitelist');
311         $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist');
312         if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) {
313             $errors['suhosin'] = array(
314                 'The suhosin.executor.include.whitelist setting is incorrect.',
315                 'Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):',
316                 '    suhosin.executor.include.whitelist = phar '.$suhosin,
317                 $iniMessage
318             );
319         }
320     }
321
322     if (!function_exists('json_decode')) {
323         $errors['json'] = array(
324             'The json extension is missing.',
325             'Install it or recompile php without --disable-json'
326         );
327     }
328
329     if (!extension_loaded('Phar')) {
330         $errors['phar'] = array(
331             'The phar extension is missing.',
332             'Install it or recompile php without --disable-phar'
333         );
334     }
335
336     if (!extension_loaded('filter')) {
337         $errors['filter'] = array(
338             'The filter extension is missing.',
339             'Install it or recompile php without --disable-filter'
340         );
341     }
342
343     if (!extension_loaded('hash')) {
344         $errors['hash'] = array(
345             'The hash extension is missing.',
346             'Install it or recompile php without --disable-hash'
347         );
348     }
349
350     if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
351         $errors['iconv_mbstring'] = array(
352             'The iconv OR mbstring extension is required and both are missing.',
353             'Install either of them or recompile php without --disable-iconv'
354         );
355     }
356
357     if (!ini_get('allow_url_fopen')) {
358         $errors['allow_url_fopen'] = array(
359             'The allow_url_fopen setting is incorrect.',
360             'Add the following to the end of your `php.ini`:',
361             '    allow_url_fopen = On',
362             $iniMessage
363         );
364     }
365
366     if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
367         $ioncube = ioncube_loader_version();
368         $errors['ioncube'] = array(
369             'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.',
370             'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:',
371             '    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so',
372             $iniMessage
373         );
374     }
375
376     if (version_compare(PHP_VERSION, '5.3.2', '<')) {
377         $errors['php'] = array(
378             'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.'
379         );
380     }
381
382     if (version_compare(PHP_VERSION, '5.3.4', '<')) {
383         $warnings['php'] = array(
384             'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.',
385             'Composer works with 5.3.2+ for most people, but there might be edge case issues.'
386         );
387     }
388
389     if (!extension_loaded('openssl')) {
390         $warnings['openssl'] = array(
391             'The openssl extension is missing, which means that secure HTTPS transfers are impossible.',
392             'If possible you should enable it or recompile php with --with-openssl'
393         );
394     }
395
396     if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
397         // Attempt to parse version number out, fallback to whole string value.
398         $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' '));
399         $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' '));
400         $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT;
401
402         $warnings['openssl_version'] = array(
403             'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.',
404             'If possible you should upgrade OpenSSL to version 1.0.1 or above.'
405         );
406     }
407
408     if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) {
409         $warnings['apc_cli'] = array(
410             'The apc.enable_cli setting is incorrect.',
411             'Add the following to the end of your `php.ini`:',
412             '    apc.enable_cli = Off',
413             $iniMessage
414         );
415     }
416
417     if (!$install && extension_loaded('xdebug')) {
418         $warnings['xdebug_loaded'] = array(
419             'The xdebug extension is loaded, this can slow down Composer a little.',
420             'Disabling it when using Composer is recommended.'
421         );
422
423         if (ini_get('xdebug.profiler_enabled')) {
424             $warnings['xdebug_profile'] = array(
425                 'The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.',
426                 'Add the following to the end of your `php.ini` to disable it:',
427                 '    xdebug.profiler_enabled = 0',
428                 $iniMessage
429             );
430         }
431     }
432
433     if (!extension_loaded('zlib')) {
434         $warnings['zlib'] = array(
435             'The zlib extension is not loaded, this can slow down Composer a lot.',
436             'If possible, install it or recompile php with --with-zlib',
437             $iniMessage
438         );
439     }
440
441     if (defined('PHP_WINDOWS_VERSION_BUILD')
442         && (version_compare(PHP_VERSION, '7.2.23', '<')
443         || (version_compare(PHP_VERSION, '7.3.0', '>=')
444         && version_compare(PHP_VERSION, '7.3.10', '<')))) {
445         $warnings['onedrive'] = array(
446             'The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.',
447             'Upgrade your PHP ('.PHP_VERSION.') to use this location with Composer.'
448         );
449     }
450
451     if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
452         $warnings['uopz'] = array(
453             'The uopz extension ignores exit calls and may not work with all Composer commands.',
454             'Disabling it when using Composer is recommended.'
455         );
456     }
457
458     ob_start();
459     phpinfo(INFO_GENERAL);
460     $phpinfo = ob_get_clean();
461     if (preg_match('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
462         $configure = $match[1];
463
464         if (false !== strpos($configure, '--enable-sigchild')) {
465             $warnings['sigchild'] = array(
466                 'PHP was compiled with --enable-sigchild which can cause issues on some platforms.',
467                 'Recompile it without this flag if possible, see also:',
468                 '    https://bugs.php.net/bug.php?id=22999'
469             );
470         }
471
472         if (false !== strpos($configure, '--with-curlwrappers')) {
473             $warnings['curlwrappers'] = array(
474                 'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.',
475                 'Recompile it without this flag if possible'
476             );
477         }
478     }
479
480     // Stringify the message arrays
481     foreach ($errors as $key => $value) {
482         $errors[$key] = PHP_EOL.implode(PHP_EOL, $value);
483     }
484
485     foreach ($warnings as $key => $value) {
486         $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value);
487     }
488
489     return !empty($errors) || !empty($warnings);
490 }
491
492
493 /**
494  * Outputs an array of issues
495  *
496  * @param array $issues
497  */
498 function outputIssues($issues)
499 {
500     foreach ($issues as $issue) {
501         out($issue, 'info');
502     }
503     out('');
504 }
505
506 /**
507  * Outputs any warnings found
508  *
509  * @param array $warnings
510  */
511 function showWarnings($warnings)
512 {
513     if (!empty($warnings)) {
514         out('Some settings on your machine may cause stability issues with Composer.', 'error');
515         out('If you encounter issues, try to change the following:', 'error');
516         outputIssues($warnings);
517     }
518 }
519
520 /**
521  * Outputs an end of process warning if tls has been bypassed
522  *
523  * @param bool $disableTls Bypass tls
524  */
525 function showSecurityWarning($disableTls)
526 {
527     if ($disableTls) {
528         out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info');
529         out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info');
530     }
531 }
532
533 /**
534  * colorize output
535  */
536 function out($text, $color = null, $newLine = true)
537 {
538     $styles = array(
539         'success' => "\033[0;32m%s\033[0m",
540         'error' => "\033[31;31m%s\033[0m",
541         'info' => "\033[33;33m%s\033[0m"
542     );
543
544     $format = '%s';
545
546     if (isset($styles[$color]) && USE_ANSI) {
547         $format = $styles[$color];
548     }
549
550     if ($newLine) {
551         $format .= PHP_EOL;
552     }
553
554     printf($format, $text);
555 }
556
557 /**
558  * Returns the system-dependent Composer home location, which may not exist
559  *
560  * @return string
561  */
562 function getHomeDir()
563 {
564     $home = getenv('COMPOSER_HOME');
565     if ($home) {
566         return $home;
567     }
568
569     $userDir = getUserDir();
570
571     if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
572         return $userDir.'/Composer';
573     }
574
575     $dirs = array();
576
577     if (useXdg()) {
578         // XDG Base Directory Specifications
579         $xdgConfig = getenv('XDG_CONFIG_HOME');
580         if (!$xdgConfig) {
581             $xdgConfig = $userDir . '/.config';
582         }
583
584         $dirs[] = $xdgConfig . '/composer';
585     }
586
587     $dirs[] = $userDir . '/.composer';
588
589     // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer
590     foreach ($dirs as $dir) {
591         if (is_dir($dir)) {
592             return $dir;
593         }
594     }
595
596     // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise)
597     return $dirs[0];
598 }
599
600 /**
601  * Returns the location of the user directory from the environment
602  * @throws RuntimeException If the environment value does not exists
603  *
604  * @return string
605  */
606 function getUserDir()
607 {
608     $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME';
609     $userDir = getenv($userEnv);
610
611     if (!$userDir) {
612         throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly');
613     }
614
615     return rtrim(strtr($userDir, '\\', '/'), '/');
616 }
617
618 /**
619  * @return bool
620  */
621 function useXdg()
622 {
623     foreach (array_keys($_SERVER) as $key) {
624         if (strpos($key, 'XDG_') === 0) {
625             return true;
626         }
627     }
628
629     if (is_dir('/etc/xdg')) {
630         return true;
631     }
632
633     return false;
634 }
635
636 function validateCaFile($contents)
637 {
638     // assume the CA is valid if php is vulnerable to
639     // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
640     if (
641         PHP_VERSION_ID <= 50327
642         || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
643         || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
644     ) {
645         return !empty($contents);
646     }
647
648     return (bool) openssl_x509_parse($contents);
649 }
650
651 class Installer
652 {
653     private $quiet;
654     private $disableTls;
655     private $cafile;
656     private $displayPath;
657     private $target;
658     private $tmpFile;
659     private $tmpCafile;
660     private $baseUrl;
661     private $algo;
662     private $errHandler;
663     private $httpClient;
664     private $pubKeys = array();
665     private $installs = array();
666
667     /**
668      * Constructor - must not do anything that throws an exception
669      *
670      * @param bool $quiet Quiet mode
671      * @param bool $disableTls Bypass tls
672      * @param mixed $cafile Path to CA bundle, or false
673      */
674     public function __construct($quiet, $disableTls, $caFile)
675     {
676         if (($this->quiet = $quiet)) {
677             ob_start();
678         }
679         $this->disableTls = $disableTls;
680         $this->cafile = $caFile;
681         $this->errHandler = new ErrorHandler();
682     }
683
684     /**
685      * Runs the installer
686      *
687      * @param mixed $version Specific version to install, or false
688      * @param mixed $installDir Specific installation directory, or false
689      * @param string $filename Specific filename to save to, or composer.phar
690      * @param string $channel Specific version channel to use
691      * @throws Exception If anything other than a RuntimeException is caught
692      *
693      * @return bool If the installation succeeded
694      */
695     public function run($version, $installDir, $filename, $channel)
696     {
697         try {
698             $this->initTargets($installDir, $filename);
699             $this->initTls();
700             $this->httpClient = new HttpClient($this->disableTls, $this->cafile);
701             $result = $this->install($version, $channel);
702
703             // in case --1 or --2 is passed, we leave the default channel for next self-update to stable
704             if (is_numeric($channel)) {
705                 $channel = 'stable';
706             }
707
708             if ($result && $channel !== 'stable' && !$version && defined('PHP_BINARY')) {
709                 $null = (defined('PHP_WINDOWS_VERSION_MAJOR') ? 'NUL' : '/dev/null');
710                 @exec(escapeshellarg(PHP_BINARY) .' '.escapeshellarg($this->target).' self-update --'.$channel.' --set-channel-only -q > '.$null.' 2> '.$null, $output);
711             }
712         } catch (Exception $e) {
713             $result = false;
714         }
715
716         // Always clean up
717         $this->cleanUp($result);
718
719         if (isset($e)) {
720             // Rethrow anything that is not a RuntimeException
721             if (!$e instanceof RuntimeException) {
722                 throw $e;
723             }
724             out($e->getMessage(), 'error');
725         }
726         return $result;
727     }
728
729     /**
730      * Initialization methods to set the required filenames and composer url
731      *
732      * @param mixed $installDir Specific installation directory, or false
733      * @param string $filename Specific filename to save to, or composer.phar
734      * @throws RuntimeException If the installation directory is not writable
735      */
736     protected function initTargets($installDir, $filename)
737     {
738         $this->displayPath = ($installDir ? rtrim($installDir, '/').'/' : '').$filename;
739         $installDir = $installDir ? realpath($installDir) : getcwd();
740
741         if (!is_writeable($installDir)) {
742             throw new RuntimeException('The installation directory "'.$installDir.'" is not writable');
743         }
744
745         $this->target = $installDir.DIRECTORY_SEPARATOR.$filename;
746         $this->tmpFile = $installDir.DIRECTORY_SEPARATOR.basename($this->target, '.phar').'-temp.phar';
747
748         $uriScheme = $this->disableTls ? 'http' : 'https';
749         $this->baseUrl = $uriScheme.'://getcomposer.org';
750     }
751
752     /**
753      * A wrapper around methods to check tls and write public keys
754      * @throws RuntimeException If SHA384 is not supported
755      */
756     protected function initTls()
757     {
758         if ($this->disableTls) {
759             return;
760         }
761
762         if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) {
763             throw new RuntimeException('SHA384 is not supported by your openssl extension');
764         }
765
766         $this->algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
767         $home = $this->getComposerHome();
768
769         $this->pubKeys = array(
770             'dev' => $this->installKey(self::getPKDev(), $home, 'keys.dev.pub'),
771             'tags' => $this->installKey(self::getPKTags(), $home, 'keys.tags.pub')
772         );
773
774         if (empty($this->cafile) && !HttpClient::getSystemCaRootBundlePath()) {
775             $this->cafile = $this->tmpCafile = $this->installKey(HttpClient::getPackagedCaFile(), $home, 'cacert-temp.pem');
776         }
777     }
778
779     /**
780      * Returns the Composer home directory, creating it if required
781      * @throws RuntimeException If the directory cannot be created
782      *
783      * @return string
784      */
785     protected function getComposerHome()
786     {
787         $home = getHomeDir();
788
789         if (!is_dir($home)) {
790             $this->errHandler->start();
791
792             if (!mkdir($home, 0777, true)) {
793                 throw new RuntimeException(sprintf(
794                     'Unable to create Composer home directory "%s": %s',
795                     $home,
796                     $this->errHandler->message
797                 ));
798             }
799             $this->installs[] = $home;
800             $this->errHandler->stop();
801         }
802         return $home;
803     }
804
805     /**
806      * Writes public key data to disc
807      *
808      * @param string $data The public key(s) in pem format
809      * @param string $path The directory to write to
810      * @param string $filename The name of the file
811      * @throws RuntimeException If the file cannot be written
812      *
813      * @return string The path to the saved data
814      */
815     protected function installKey($data, $path, $filename)
816     {
817         $this->errHandler->start();
818
819         $target = $path.DIRECTORY_SEPARATOR.$filename;
820         $installed = file_exists($target);
821         $write = file_put_contents($target, $data, LOCK_EX);
822         @chmod($target, 0644);
823
824         $this->errHandler->stop();
825
826         if (!$write) {
827             throw new RuntimeException(sprintf('Unable to write %s to: %s', $filename, $path));
828         }
829
830         if (!$installed) {
831             $this->installs[] = $target;
832         }
833
834         return $target;
835     }
836
837     /**
838      * The main install function
839      *
840      * @param mixed $version Specific version to install, or false
841      * @param string $channel Version channel to use
842      *
843      * @return bool If the installation succeeded
844      */
845     protected function install($version, $channel)
846     {
847         $retries = 3;
848         $result = false;
849         $infoMsg = 'Downloading...';
850         $infoType = 'info';
851
852         while ($retries--) {
853             if (!$this->quiet) {
854                 out($infoMsg, $infoType);
855                 $infoMsg = 'Retrying...';
856                 $infoType = 'error';
857             }
858
859             if (!$this->getVersion($channel, $version, $url, $error)) {
860                 out($error, 'error');
861                 continue;
862             }
863
864             if (!$this->downloadToTmp($url, $signature, $error)) {
865                 out($error, 'error');
866                 continue;
867             }
868
869             if (!$this->verifyAndSave($version, $signature, $error)) {
870                 out($error, 'error');
871                 continue;
872             }
873
874             $result = true;
875             break;
876         }
877
878         if (!$this->quiet) {
879             if ($result) {
880                 out(PHP_EOL."Composer (version {$version}) successfully installed to: {$this->target}", 'success');
881                 out("Use it: php {$this->displayPath}", 'info');
882                 out('');
883             } else {
884                 out('The download failed repeatedly, aborting.', 'error');
885             }
886         }
887         return $result;
888     }
889
890     /**
891      * Sets the version url, downloading version data if required
892      *
893      * @param string $channel Version channel to use
894      * @param false|string $version Version to install, or set by method
895      * @param null|string $url The versioned url, set by method
896      * @param null|string $error Set by method on failure
897      *
898      * @return bool If the operation succeeded
899      */
900     protected function getVersion($channel, &$version, &$url, &$error)
901     {
902         $error = '';
903
904         if ($version) {
905             if (empty($url)) {
906                 $url = $this->baseUrl."/download/{$version}/composer.phar";
907             }
908             return true;
909         }
910
911         $this->errHandler->start();
912
913         if ($this->downloadVersionData($data, $error)) {
914             $this->parseVersionData($data, $channel, $version, $url);
915         }
916
917         $this->errHandler->stop();
918         return empty($error);
919     }
920
921     /**
922      * Downloads and json-decodes version data
923      *
924      * @param null|array $data Downloaded version data, set by method
925      * @param null|string $error Set by method on failure
926      *
927      * @return bool If the operation succeeded
928      */
929     protected function downloadVersionData(&$data, &$error)
930     {
931         $url = $this->baseUrl.'/versions';
932         $errFmt = 'The "%s" file could not be %s: %s';
933
934         if (!$json = $this->httpClient->get($url)) {
935             $error = sprintf($errFmt, $url, 'downloaded', $this->errHandler->message);
936             return false;
937         }
938
939         if (!$data = json_decode($json, true)) {
940             $error = sprintf($errFmt, $url, 'json-decoded', $this->getJsonError());
941             return false;
942         }
943         return true;
944     }
945
946     /**
947      * A wrapper around the methods needed to download and save the phar
948      *
949      * @param string $url The versioned download url
950      * @param null|string $signature Set by method on successful download
951      * @param null|string $error Set by method on failure
952      *
953      * @return bool If the operation succeeded
954      */
955     protected function downloadToTmp($url, &$signature, &$error)
956     {
957         $error = '';
958         $errFmt = 'The "%s" file could not be downloaded: %s';
959         $sigUrl = $url.'.sig';
960         $this->errHandler->start();
961
962         if (!$fh = fopen($this->tmpFile, 'w')) {
963             $error = sprintf('Could not create file "%s": %s', $this->tmpFile, $this->errHandler->message);
964
965         } elseif (!$this->getSignature($sigUrl, $signature)) {
966             $error = sprintf($errFmt, $sigUrl, $this->errHandler->message);
967
968         } elseif (!fwrite($fh, $this->httpClient->get($url))) {
969             $error = sprintf($errFmt, $url, $this->errHandler->message);
970         }
971
972         if (is_resource($fh)) {
973             fclose($fh);
974         }
975         $this->errHandler->stop();
976         return empty($error);
977     }
978
979     /**
980      * Verifies the downloaded file and saves it to the target location
981      *
982      * @param string $version The composer version downloaded
983      * @param string $signature The digital signature to check
984      * @param null|string $error Set by method on failure
985      *
986      * @return bool If the operation succeeded
987      */
988     protected function verifyAndSave($version, $signature, &$error)
989     {
990         $error = '';
991
992         if (!$this->validatePhar($this->tmpFile, $pharError)) {
993             $error = 'The download is corrupt: '.$pharError;
994
995         } elseif (!$this->verifySignature($version, $signature, $this->tmpFile)) {
996             $error = 'Signature mismatch, could not verify the phar file integrity';
997
998         } else {
999             $this->errHandler->start();
1000
1001             if (!rename($this->tmpFile, $this->target)) {
1002                 $error = sprintf('Could not write to file "%s": %s', $this->target, $this->errHandler->message);
1003             }
1004             chmod($this->target, 0755);
1005             $this->errHandler->stop();
1006         }
1007
1008         return empty($error);
1009     }
1010
1011     /**
1012      * Parses an array of version data to match the required channel
1013      *
1014      * @param array $data Downloaded version data
1015      * @param mixed $channel Version channel to use
1016      * @param false|string $version Set by method
1017      * @param mixed $url The versioned url, set by method
1018      */
1019     protected function parseVersionData(array $data, $channel, &$version, &$url)
1020     {
1021         foreach ($data[$channel] as $candidate) {
1022             if ($candidate['min-php'] <= PHP_VERSION_ID) {
1023                 $version = $candidate['version'];
1024                 $url = $this->baseUrl.$candidate['path'];
1025                 break;
1026             }
1027         }
1028
1029         if (!$version) {
1030             $error = sprintf(
1031                 'None of the %d %s version(s) of Composer matches your PHP version (%s / ID: %d)',
1032                 count($data[$channel]),
1033                 $channel,
1034                 PHP_VERSION,
1035                 PHP_VERSION_ID
1036             );
1037             throw new RuntimeException($error);
1038         }
1039     }
1040
1041     /**
1042      * Downloads the digital signature of required phar file
1043      *
1044      * @param string $url The signature url
1045      * @param null|string $signature Set by method on success
1046      *
1047      * @return bool If the download succeeded
1048      */
1049     protected function getSignature($url, &$signature)
1050     {
1051         if (!$result = $this->disableTls) {
1052             $signature = $this->httpClient->get($url);
1053
1054             if ($signature) {
1055                 $signature = json_decode($signature, true);
1056                 $signature = base64_decode($signature['sha384']);
1057                 $result = true;
1058             }
1059         }
1060
1061         return $result;
1062     }
1063
1064     /**
1065      * Verifies the signature of the downloaded phar
1066      *
1067      * @param string $version The composer versione
1068      * @param string $signature The downloaded digital signature
1069      * @param string $file The temp phar file
1070      *
1071      * @return bool If the operation succeeded
1072      */
1073     protected function verifySignature($version, $signature, $file)
1074     {
1075         if (!$result = $this->disableTls) {
1076             $path = preg_match('{^[0-9a-f]{40}$}', $version) ? $this->pubKeys['dev'] : $this->pubKeys['tags'];
1077             $pubkeyid = openssl_pkey_get_public('file://'.$path);
1078
1079             $result = 1 === openssl_verify(
1080                 file_get_contents($file),
1081                 $signature,
1082                 $pubkeyid,
1083                 $this->algo
1084             );
1085
1086             // PHP 8 automatically frees the key instance and deprecates the function
1087             if (PHP_VERSION_ID < 80000) {
1088                 openssl_free_key($pubkeyid);
1089             }
1090         }
1091
1092         return $result;
1093     }
1094
1095     /**
1096      * Validates the downloaded phar file
1097      *
1098      * @param string $pharFile The temp phar file
1099      * @param null|string $error Set by method on failure
1100      *
1101      * @return bool If the operation succeeded
1102      */
1103     protected function validatePhar($pharFile, &$error)
1104     {
1105         if (ini_get('phar.readonly')) {
1106             return true;
1107         }
1108
1109         try {
1110             // Test the phar validity
1111             $phar = new Phar($pharFile);
1112             // Free the variable to unlock the file
1113             unset($phar);
1114             $result = true;
1115
1116         } catch (Exception $e) {
1117             if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) {
1118                 throw $e;
1119             }
1120             $error = $e->getMessage();
1121             $result = false;
1122         }
1123         return $result;
1124     }
1125
1126     /**
1127      * Returns a string representation of the last json error
1128      *
1129      * @return string The error string or code
1130      */
1131     protected function getJsonError()
1132     {
1133         if (function_exists('json_last_error_msg')) {
1134             return json_last_error_msg();
1135         } else {
1136             return 'json_last_error = '.json_last_error();
1137         }
1138     }
1139
1140     /**
1141      * Cleans up resources at the end of the installation
1142      *
1143      * @param bool $result If the installation succeeded
1144      */
1145     protected function cleanUp($result)
1146     {
1147         if (!$result) {
1148             // Output buffered errors
1149             if ($this->quiet) {
1150                 $this->outputErrors();
1151             }
1152             // Clean up stuff we created
1153             $this->uninstall();
1154         } elseif ($this->tmpCafile) {
1155             @unlink($this->tmpCafile);
1156         }
1157     }
1158
1159     /**
1160      * Outputs unique errors when in quiet mode
1161      *
1162      */
1163     protected function outputErrors()
1164     {
1165         $errors = explode(PHP_EOL, ob_get_clean());
1166         $shown = array();
1167
1168         foreach ($errors as $error) {
1169             if ($error && !in_array($error, $shown)) {
1170                 out($error, 'error');
1171                 $shown[] = $error;
1172             }
1173         }
1174     }
1175
1176     /**
1177      * Uninstalls newly-created files and directories on failure
1178      *
1179      */
1180     protected function uninstall()
1181     {
1182         foreach (array_reverse($this->installs) as $target) {
1183             if (is_file($target)) {
1184                 @unlink($target);
1185             } elseif (is_dir($target)) {
1186                 @rmdir($target);
1187             }
1188         }
1189
1190         if (file_exists($this->tmpFile)) {
1191             @unlink($this->tmpFile);
1192         }
1193     }
1194
1195     public static function getPKDev()
1196     {
1197         return <<<PKDEV
1198 -----BEGIN PUBLIC KEY-----
1199 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
1200 FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
1201 i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
1202 hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
1203 o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
1204 8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
1205 8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
1206 TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
1207 pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
1208 8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
1209 r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
1210 wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
1211 -----END PUBLIC KEY-----
1212 PKDEV;
1213     }
1214
1215     public static function getPKTags()
1216     {
1217         return <<<PKTAGS
1218 -----BEGIN PUBLIC KEY-----
1219 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
1220 MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
1221 vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
1222 bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
1223 mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
1224 noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
1225 nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
1226 rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
1227 RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
1228 tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
1229 TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
1230 RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
1231 -----END PUBLIC KEY-----
1232 PKTAGS;
1233     }
1234 }
1235
1236 class ErrorHandler
1237 {
1238     public $message;
1239     protected $active;
1240
1241     /**
1242      * Handle php errors
1243      *
1244      * @param mixed $code The error code
1245      * @param mixed $msg The error message
1246      */
1247     public function handleError($code, $msg)
1248     {
1249         if ($this->message) {
1250             $this->message .= PHP_EOL;
1251         }
1252         $this->message .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
1253     }
1254
1255     /**
1256      * Starts error-handling if not already active
1257      *
1258      * Any message is cleared
1259      */
1260     public function start()
1261     {
1262         if (!$this->active) {
1263             set_error_handler(array($this, 'handleError'));
1264             $this->active = true;
1265         }
1266         $this->message = '';
1267     }
1268
1269     /**
1270      * Stops error-handling if active
1271      *
1272      * Any message is preserved until the next call to start()
1273      */
1274     public function stop()
1275     {
1276         if ($this->active) {
1277             restore_error_handler();
1278             $this->active = false;
1279         }
1280     }
1281 }
1282
1283 class NoProxyPattern
1284 {
1285     private $composerInNoProxy = false;
1286     private $rulePorts = array();
1287
1288     public function __construct($pattern)
1289     {
1290         $rules = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY);
1291
1292         if ($matches = preg_grep('{getcomposer\.org(?::\d+)?}i', $rules)) {
1293             $this->composerInNoProxy = true;
1294
1295             foreach ($matches as $match) {
1296                 if (strpos($match, ':') !== false) {
1297                     list(, $port) = explode(':', $match);
1298                     $this->rulePorts[] = (int) $port;
1299                 }
1300             }
1301         }
1302     }
1303
1304     /**
1305      * Returns true if NO_PROXY contains getcomposer.org
1306      *
1307      * @param string $url http(s)://getcomposer.org
1308      *
1309      * @return bool
1310      */
1311     public function test($url)
1312     {
1313         if (!$this->composerInNoProxy) {
1314             return false;
1315         }
1316
1317         if (empty($this->rulePorts)) {
1318             return true;
1319         }
1320
1321         if (strpos($url, 'http://') === 0) {
1322             $port = 80;
1323         } else {
1324             $port = 443;
1325         }
1326
1327         return in_array($port, $this->rulePorts);
1328     }
1329 }
1330
1331 class HttpClient {
1332
1333     private $options = array('http' => array());
1334     private $disableTls = false;
1335
1336     public function __construct($disableTls = false, $cafile = false)
1337     {
1338         $this->disableTls = $disableTls;
1339         if ($this->disableTls === false) {
1340             if (!empty($cafile) && !is_dir($cafile)) {
1341                 if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) {
1342                     throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.');
1343                 }
1344             }
1345             $options = $this->getTlsStreamContextDefaults($cafile);
1346             $this->options = array_replace_recursive($this->options, $options);
1347         }
1348     }
1349
1350     public function get($url)
1351     {
1352         $context = $this->getStreamContext($url);
1353         $result = file_get_contents($url, false, $context);
1354
1355         if ($result && extension_loaded('zlib')) {
1356             $decode = false;
1357             foreach ($http_response_header as $header) {
1358                 if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
1359                     $decode = true;
1360                     continue;
1361                 } elseif (preg_match('{^HTTP/}i', $header)) {
1362                     $decode = false;
1363                 }
1364             }
1365
1366             if ($decode) {
1367                 if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
1368                     $result = zlib_decode($result);
1369                 } else {
1370                     // work around issue with gzuncompress & co that do not work with all gzip checksums
1371                     $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
1372                 }
1373
1374                 if (!$result) {
1375                     throw new RuntimeException('Failed to decode zlib stream');
1376                 }
1377             }
1378         }
1379
1380         return $result;
1381     }
1382
1383     protected function getStreamContext($url)
1384     {
1385         if ($this->disableTls === false) {
1386             if (PHP_VERSION_ID < 50600) {
1387                 $this->options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
1388             }
1389         }
1390         // Keeping the above mostly isolated from the code copied from Composer.
1391         return $this->getMergedStreamContext($url);
1392     }
1393
1394     protected function getTlsStreamContextDefaults($cafile)
1395     {
1396         $ciphers = implode(':', array(
1397             'ECDHE-RSA-AES128-GCM-SHA256',
1398             'ECDHE-ECDSA-AES128-GCM-SHA256',
1399             'ECDHE-RSA-AES256-GCM-SHA384',
1400             'ECDHE-ECDSA-AES256-GCM-SHA384',
1401             'DHE-RSA-AES128-GCM-SHA256',
1402             'DHE-DSS-AES128-GCM-SHA256',
1403             'kEDH+AESGCM',
1404             'ECDHE-RSA-AES128-SHA256',
1405             'ECDHE-ECDSA-AES128-SHA256',
1406             'ECDHE-RSA-AES128-SHA',
1407             'ECDHE-ECDSA-AES128-SHA',
1408             'ECDHE-RSA-AES256-SHA384',
1409             'ECDHE-ECDSA-AES256-SHA384',
1410             'ECDHE-RSA-AES256-SHA',
1411             'ECDHE-ECDSA-AES256-SHA',
1412             'DHE-RSA-AES128-SHA256',
1413             'DHE-RSA-AES128-SHA',
1414             'DHE-DSS-AES128-SHA256',
1415             'DHE-RSA-AES256-SHA256',
1416             'DHE-DSS-AES256-SHA',
1417             'DHE-RSA-AES256-SHA',
1418             'AES128-GCM-SHA256',
1419             'AES256-GCM-SHA384',
1420             'AES128-SHA256',
1421             'AES256-SHA256',
1422             'AES128-SHA',
1423             'AES256-SHA',
1424             'AES',
1425             'CAMELLIA',
1426             'DES-CBC3-SHA',
1427             '!aNULL',
1428             '!eNULL',
1429             '!EXPORT',
1430             '!DES',
1431             '!RC4',
1432             '!MD5',
1433             '!PSK',
1434             '!aECDH',
1435             '!EDH-DSS-DES-CBC3-SHA',
1436             '!EDH-RSA-DES-CBC3-SHA',
1437             '!KRB5-DES-CBC3-SHA',
1438         ));
1439
1440         /**
1441          * CN_match and SNI_server_name are only known once a URL is passed.
1442          * They will be set in the getOptionsForUrl() method which receives a URL.
1443          *
1444          * cafile or capath can be overridden by passing in those options to constructor.
1445          */
1446         $options = array(
1447             'ssl' => array(
1448                 'ciphers' => $ciphers,
1449                 'verify_peer' => true,
1450                 'verify_depth' => 7,
1451                 'SNI_enabled' => true,
1452             )
1453         );
1454
1455         /**
1456          * Attempt to find a local cafile or throw an exception.
1457          * The user may go download one if this occurs.
1458          */
1459         if (!$cafile) {
1460             $cafile = self::getSystemCaRootBundlePath();
1461         }
1462         if (is_dir($cafile)) {
1463             $options['ssl']['capath'] = $cafile;
1464         } elseif ($cafile) {
1465             $options['ssl']['cafile'] = $cafile;
1466         } else {
1467             throw new RuntimeException('A valid cafile could not be located automatically.');
1468         }
1469
1470         /**
1471          * Disable TLS compression to prevent CRIME attacks where supported.
1472          */
1473         if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
1474             $options['ssl']['disable_compression'] = true;
1475         }
1476
1477         return $options;
1478     }
1479
1480     /**
1481      * function copied from Composer\Util\StreamContextFactory::initOptions
1482      *
1483      * Any changes should be applied there as well, or backported here.
1484      *
1485      * @param string $url URL the context is to be used for
1486      * @return resource Default context
1487      * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
1488      */
1489     protected function getMergedStreamContext($url)
1490     {
1491         $options = $this->options;
1492
1493         // Handle HTTP_PROXY/http_proxy on CLI only for security reasons
1494         if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
1495             $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
1496         }
1497
1498         // Prefer CGI_HTTP_PROXY if available
1499         if (!empty($_SERVER['CGI_HTTP_PROXY'])) {
1500             $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']);
1501         }
1502
1503         // Override with HTTPS proxy if present and URL is https
1504         if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
1505             $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
1506         }
1507
1508         // Remove proxy if URL matches no_proxy directive
1509         if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
1510             $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']);
1511             if ($pattern->test($url)) {
1512                 unset($proxy);
1513             }
1514         }
1515
1516         if (!empty($proxy)) {
1517             $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
1518             $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
1519
1520             if (isset($proxy['port'])) {
1521                 $proxyURL .= ":" . $proxy['port'];
1522             } elseif (strpos($proxyURL, 'http://') === 0) {
1523                 $proxyURL .= ":80";
1524             } elseif (strpos($proxyURL, 'https://') === 0) {
1525                 $proxyURL .= ":443";
1526             }
1527
1528             // check for a secure proxy
1529             if (strpos($proxyURL, 'https://') === 0) {
1530                 if (!extension_loaded('openssl')) {
1531                     throw new RuntimeException('You must enable the openssl extension to use a secure proxy.');
1532                 }
1533                 if (strpos($url, 'https://') === 0) {
1534                     throw new RuntimeException('PHP does not support https requests through a secure proxy.');
1535                 }
1536             }
1537
1538             // http(s):// is not supported in proxy
1539             $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);
1540
1541             $options['http'] = array(
1542                 'proxy' => $proxyURL,
1543             );
1544
1545             // add request_fulluri for http requests
1546             if ('http' === parse_url($url, PHP_URL_SCHEME)) {
1547                 $options['http']['request_fulluri'] = true;
1548             }
1549
1550             // handle proxy auth if present
1551             if (isset($proxy['user'])) {
1552                 $auth = rawurldecode($proxy['user']);
1553                 if (isset($proxy['pass'])) {
1554                     $auth .= ':' . rawurldecode($proxy['pass']);
1555                 }
1556                 $auth = base64_encode($auth);
1557
1558                 $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
1559             }
1560         }
1561
1562         if (isset($options['http']['header'])) {
1563             $options['http']['header'] .= "Connection: close\r\n";
1564         } else {
1565             $options['http']['header'] = "Connection: close\r\n";
1566         }
1567         if (extension_loaded('zlib')) {
1568             $options['http']['header'] .= "Accept-Encoding: gzip\r\n";
1569         }
1570         $options['http']['header'] .= "User-Agent: ".COMPOSER_INSTALLER."\r\n";
1571         $options['http']['protocol_version'] = 1.1;
1572         $options['http']['timeout'] = 600;
1573
1574         return stream_context_create($options);
1575     }
1576
1577     /**
1578     * This method was adapted from Sslurp.
1579     * https://github.com/EvanDotPro/Sslurp
1580     *
1581     * (c) Evan Coury <me@evancoury.com>
1582     *
1583     * For the full copyright and license information, please see below:
1584     *
1585     * Copyright (c) 2013, Evan Coury
1586     * All rights reserved.
1587     *
1588     * Redistribution and use in source and binary forms, with or without modification,
1589     * are permitted provided that the following conditions are met:
1590     *
1591     *     * Redistributions of source code must retain the above copyright notice,
1592     *       this list of conditions and the following disclaimer.
1593     *
1594     *     * Redistributions in binary form must reproduce the above copyright notice,
1595     *       this list of conditions and the following disclaimer in the documentation
1596     *       and/or other materials provided with the distribution.
1597     *
1598     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1599     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1600     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1601     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
1602     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1603     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
1604     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
1605     * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1606     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
1607     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1608     */
1609     public static function getSystemCaRootBundlePath()
1610     {
1611         static $caPath = null;
1612
1613         if ($caPath !== null) {
1614             return $caPath;
1615         }
1616
1617         // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
1618         // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
1619         $envCertFile = getenv('SSL_CERT_FILE');
1620         if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) {
1621             return $caPath = $envCertFile;
1622         }
1623
1624         // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
1625         // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
1626         $envCertDir = getenv('SSL_CERT_DIR');
1627         if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) {
1628             return $caPath = $envCertDir;
1629         }
1630
1631         $configured = ini_get('openssl.cafile');
1632         if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) {
1633             return $caPath = $configured;
1634         }
1635
1636         $configured = ini_get('openssl.capath');
1637         if ($configured && is_dir($configured) && is_readable($configured)) {
1638             return $caPath = $configured;
1639         }
1640
1641         $caBundlePaths = array(
1642             '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
1643             '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
1644             '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
1645             '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
1646             '/usr/ssl/certs/ca-bundle.crt', // Cygwin
1647             '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
1648             '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
1649             '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
1650             '/etc/ssl/cert.pem', // OpenBSD
1651             '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
1652             '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
1653             '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
1654         );
1655
1656         foreach ($caBundlePaths as $caBundle) {
1657             if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) {
1658                 return $caPath = $caBundle;
1659             }
1660         }
1661
1662         foreach ($caBundlePaths as $caBundle) {
1663             $caBundle = dirname($caBundle);
1664             if (is_dir($caBundle) && glob($caBundle.'/*')) {
1665                 return $caPath = $caBundle;
1666             }
1667         }
1668
1669         return $caPath = false;
1670     }
1671
1672     public static function getPackagedCaFile()
1673     {
1674         return <<<CACERT
1675 ##
1676 ## Bundle of CA Root Certificates for Let's Encrypt
1677 ##
1678 ## See https://letsencrypt.org/certificates/#root-certificates
1679 ##
1680 ## ISRG Root X1 (RSA 4096) expires Jun 04 11:04:38 2035 GMT
1681 ## ISRG Root X2 (ECDSA P-384) expires Sep 17 16:00:00 2040 GMT
1682 ##
1683 ## Both these are self-signed CA root certificates
1684 ##
1685
1686 ISRG Root X1
1687 ============
1688 -----BEGIN CERTIFICATE-----
1689 MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
1690 TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
1691 cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
1692 WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
1693 ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
1694 MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
1695 h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
1696 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
1697 A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
1698 T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
1699 B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
1700 B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
1701 KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
1702 OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
1703 jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
1704 qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
1705 rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
1706 HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
1707 hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
1708 ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
1709 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
1710 NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
1711 ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
1712 TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
1713 jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
1714 oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
1715 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
1716 mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
1717 emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
1718 -----END CERTIFICATE-----
1719
1720 ISRG Root X2
1721 ============
1722 -----BEGIN CERTIFICATE-----
1723 MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
1724 CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
1725 R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
1726 MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
1727 ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
1728 EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
1729 +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
1730 ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
1731 AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
1732 zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
1733 tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
1734 /q4AaOeMSQ+2b1tbFfLn
1735 -----END CERTIFICATE-----
1736 CACERT;
1737     }
1738 }