Skip to content

Commit 802a45b

Browse files
committed
add suggested fix from copilot
1 parent 28d4f2e commit 802a45b

File tree

1 file changed

+60
-8
lines changed

1 file changed

+60
-8
lines changed

system/Autoloader/Autoloader.php

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class Autoloader
9393
*/
9494
protected $helpers = ['url'];
9595

96+
/**
97+
* Track if Composer namespaces have been loaded in this initialization.
98+
* Resets on each initialize() call to allow tests to load Composer.
99+
*/
100+
private bool $composerLoaded = false;
101+
96102
public function __construct(private readonly string $composerPath = COMPOSER_PATH)
97103
{
98104
}
@@ -109,6 +115,8 @@ public function initialize(Autoload $config, Modules $modules)
109115
$this->classmap = [];
110116
$this->files = [];
111117

118+
$this->composerLoaded = false;
119+
112120
// We have to have one or the other, though we don't enforce the need
113121
// to have both present in order to work.
114122
if ($config->psr4 === [] && $config->classmap === []) {
@@ -146,16 +154,36 @@ private function loadComposerAutoloader(Modules $modules): void
146154
define('VENDORPATH', dirname($this->composerPath) . DIRECTORY_SEPARATOR);
147155
}
148156

149-
/** @var ClassLoader $composer */
150-
$composer = include $this->composerPath;
157+
// Skip if already loaded in this initialization
158+
if ($this->composerLoaded) {
159+
return;
160+
}
151161

152-
// Should we load through Composer's namespaces, also?
153-
if ($modules->discoverInComposer) {
154-
$composerPackages = $modules->composerPackages;
155-
$this->loadComposerNamespaces($composer, $composerPackages ?? []);
162+
// Use advisory file lock to coordinate between parallel processes
163+
$lockFile = sys_get_temp_dir() . '/ci_autoload_' . sha1($this->composerPath) . '.lock';
164+
$lockFp = @fopen($lockFile, 'cb');
165+
if ($lockFp) {
166+
flock($lockFp, LOCK_EX);
156167
}
157168

158-
unset($composer);
169+
try {
170+
/** @var ClassLoader $composer */
171+
$composer = include $this->composerPath;
172+
173+
// Should we load through Composer's namespaces, also?
174+
if ($modules->discoverInComposer) {
175+
$composerPackages = $modules->composerPackages;
176+
$this->loadComposerNamespaces($composer, $composerPackages ?? []);
177+
$this->composerLoaded = true;
178+
}
179+
180+
unset($composer);
181+
} finally {
182+
if ($lockFp) {
183+
flock($lockFp, LOCK_UN);
184+
fclose($lockFp);
185+
}
186+
}
159187
}
160188

161189
/**
@@ -441,7 +469,31 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa
441469

442470
if ($add) {
443471
// Composer stores namespaces with trailing slash. We don't.
444-
$newPaths[rtrim($namespace, '\\ ')] = $srcPaths;
472+
// Validate and absolutize paths to prevent issues in parallel execution
473+
$validatedPaths = [];
474+
foreach ($srcPaths as $srcPath) {
475+
// Handle relative paths and paths with vendor/composer prefixes
476+
if (! str_starts_with($srcPath, '/') && (strlen($srcPath) < 2 || $srcPath[1] !== ':')) {
477+
// Relative path - make it absolute relative to VENDORPATH
478+
$srcPath = rtrim(VENDORPATH, '/\\') . DIRECTORY_SEPARATOR . ltrim($srcPath, '/\\');
479+
}
480+
481+
// Remove /../ patterns to prevent malformed paths
482+
$srcPath = str_replace(['/../', '\\..\\'], DIRECTORY_SEPARATOR, $srcPath);
483+
484+
// Use realpath if the path exists to get the canonical absolute path
485+
if (is_dir($srcPath)) {
486+
$srcPath = realpath($srcPath);
487+
}
488+
489+
if ($srcPath !== false) {
490+
$validatedPaths[] = $srcPath;
491+
}
492+
}
493+
494+
if ($validatedPaths !== []) {
495+
$newPaths[rtrim($namespace, '\\ ')] = $validatedPaths;
496+
}
445497
}
446498
}
447499

0 commit comments

Comments
 (0)