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