Skip to content

Commit bcab1c4

Browse files
authored
Fix Composer autoloader being hijackable by script/plugin event handlers (composer#11955)
1 parent d4396a8 commit bcab1c4

File tree

3 files changed

+48
-20
lines changed

3 files changed

+48
-20
lines changed

phpstan/baseline-8.1.neon

-5
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,6 @@ parameters:
180180
count: 2
181181
path: ../src/Composer/Util/Hg.php
182182

183-
-
184-
message: "#^Only booleans are allowed in &&, int\\<0, 2097152\\> given on the right side\\.$#"
185-
count: 1
186-
path: ../src/Composer/Util/Http/CurlDownloader.php
187-
188183
-
189184
message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#"
190185
count: 1

phpstan/baseline.neon

+10-15
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ parameters:
332332

333333
-
334334
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
335-
count: 4
335+
count: 3
336336
path: ../src/Composer/Command/DiagnoseCommand.php
337337

338338
-
@@ -1850,6 +1850,11 @@ parameters:
18501850
count: 1
18511851
path: ../src/Composer/Downloader/ZipDownloader.php
18521852

1853+
-
1854+
message: "#^Argument of an invalid type array\\<int, callable\\>\\|false supplied for foreach, only iterables are supported\\.$#"
1855+
count: 2
1856+
path: ../src/Composer/EventDispatcher/EventDispatcher.php
1857+
18531858
-
18541859
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
18551860
count: 1
@@ -4000,7 +4005,7 @@ parameters:
40004005

40014006
-
40024007
message: "#^Cannot access offset 'features' on array\\|false\\.$#"
4003-
count: 2
4008+
count: 1
40044009
path: ../src/Composer/Util/Http/CurlDownloader.php
40054010

40064011
-
@@ -4010,11 +4015,6 @@ parameters:
40104015

40114016
-
40124017
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
4013-
count: 3
4014-
path: ../src/Composer/Util/Http/CurlDownloader.php
4015-
4016-
-
4017-
message: "#^Only booleans are allowed in &&, int given on the right side\\.$#"
40184018
count: 1
40194019
path: ../src/Composer/Util/Http/CurlDownloader.php
40204020

@@ -4189,17 +4189,17 @@ parameters:
41894189
path: ../src/Composer/Util/Http/CurlDownloader.php
41904190

41914191
-
4192-
message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy::getCurlOptions\\(\\) should return array\\<int, int\\|string\\> but returns array\\<int\\|string, int\\|string\\>.$#"
4192+
message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#"
41934193
count: 1
41944194
path: ../src/Composer/Util/Http/RequestProxy.php
41954195

41964196
-
4197-
message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#"
4197+
message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#"
41984198
count: 1
41994199
path: ../src/Composer/Util/Http/RequestProxy.php
42004200

42014201
-
4202-
message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#"
4202+
message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy\\:\\:getCurlOptions\\(\\) should return array\\<int, int\\|string\\> but returns array\\<int\\|string, int\\|string\\>\\.$#"
42034203
count: 1
42044204
path: ../src/Composer/Util/Http/RequestProxy.php
42054205

@@ -4618,11 +4618,6 @@ parameters:
46184618
count: 1
46194619
path: ../src/Composer/Util/StreamContextFactory.php
46204620

4621-
-
4622-
message: "#^Only booleans are allowed in an if condition, array given\\.$#"
4623-
count: 1
4624-
path: ../src/Composer/Util/StreamContextFactory.php
4625-
46264621
-
46274622
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
46284623
count: 2

src/Composer/EventDispatcher/EventDispatcher.php

+38
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ protected function doDispatch(Event $event)
194194

195195
$this->pushEvent($event);
196196

197+
$autoloadersBefore = spl_autoload_functions();
198+
197199
try {
198200
$returnMax = 0;
199201
foreach ($listeners as $callable) {
@@ -411,6 +413,26 @@ protected function doDispatch(Event $event)
411413
}
412414
} finally {
413415
$this->popEvent();
416+
417+
$knownIdentifiers = [];
418+
foreach ($autoloadersBefore as $key => $cb) {
419+
$knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb];
420+
}
421+
foreach (spl_autoload_functions() as $cb) {
422+
// once we get to the first known autoloader, we can leave any appended autoloader without problems
423+
if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) {
424+
break;
425+
}
426+
427+
// other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first
428+
if ($cb instanceof ClassLoader) {
429+
$cb->unregister();
430+
$cb->register(false);
431+
} else {
432+
spl_autoload_unregister($cb);
433+
spl_autoload_register($cb);
434+
}
435+
}
414436
}
415437

416438
return $returnMax;
@@ -638,4 +660,20 @@ private function ensureBinDirIsInPath(): void
638660
}
639661
}
640662
}
663+
664+
private function getCallbackIdentifier(callable $cb): string
665+
{
666+
if (is_string($cb)) {
667+
return 'fn:'.$cb;
668+
}
669+
if (is_object($cb)) {
670+
return 'obj:'.spl_object_hash($cb);
671+
}
672+
if (is_array($cb)) {
673+
return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1];
674+
}
675+
676+
// not great but also do not want to break everything here
677+
return 'unsupported';
678+
}
641679
}

0 commit comments

Comments
 (0)