Skip to content

Commit ffe442c

Browse files
authored
Always treat stream wrappers as absolute paths (#18)
1 parent 4b0a223 commit ffe442c

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

src/ClassMapGenerator.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,19 @@ class ClassMapGenerator
4545
*/
4646
private $classMap;
4747

48+
/**
49+
* @var non-empty-string
50+
*/
51+
private $streamWrappersRegex;
52+
4853
/**
4954
* @param list<string> $extensions File extensions to scan for classes in the given paths
5055
*/
5156
public function __construct(array $extensions = ['php', 'inc'])
5257
{
5358
$this->extensions = $extensions;
5459
$this->classMap = new ClassMap;
60+
$this->streamWrappersRegex = sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers())));
5561
}
5662

5763
/**
@@ -142,18 +148,21 @@ public function scanPaths($path, ?string $excluded = null, string $autoloadType
142148
continue;
143149
}
144150

145-
if (!self::isAbsolutePath($filePath)) {
151+
$isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath);
152+
if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) {
146153
$filePath = $cwd . '/' . $filePath;
147154
$filePath = self::normalizePath($filePath);
148155
} else {
149-
$filePath = Preg::replace('{[\\\\/]{2,}}', '/', $filePath);
156+
$filePath = Preg::replace('{(?<!:)[\\\\/]{2,}}', '/', $filePath);
150157
}
151158

152159
if ('' === $filePath) {
153160
throw new \LogicException('Got an empty $filePath for '.$file->getPathname());
154161
}
155162

156-
$realPath = realpath($filePath);
163+
$realPath = $isStreamWrapperPath
164+
? $filePath
165+
: realpath($filePath);
157166

158167
// fallback just in case but this really should not happen
159168
if (false === $realPath) {

tests/ClassMapGeneratorTest.php

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
namespace Composer\ClassMapGenerator;
2020

21-
use Composer\ClassMapGenerator\ClassMapGenerator;
2221
use PHPUnit\Framework\TestCase;
2322
use Symfony\Component\Finder\Finder;
2423
use Symfony\Component\Filesystem\Filesystem;
@@ -150,6 +149,86 @@ public function testCreateMapFinderSupport(): void
150149
), ClassMapGenerator::createMap($finder));
151150
}
152151

152+
/**
153+
* @see ClassMapGenerator::isStreamWrapper()
154+
*/
155+
public function testStreamWrapperSupport(): void {
156+
157+
/**
158+
* A stream wrapper that given `test://myfile.php` will read `test://path/to/myfile.php` where `path/to` is
159+
* set on the `$rootPath` static variable.
160+
*/
161+
$testProxyStreamWrapper = new class() {
162+
/**
163+
* @var string
164+
*/
165+
public static $rootPath;
166+
167+
/**
168+
* @var string
169+
*/
170+
protected $real;
171+
172+
/**
173+
* @var resource|false
174+
*/
175+
protected $resource;
176+
177+
/**
178+
* @param string $path
179+
* @param string $mode
180+
* @param int $options
181+
* @param ?string $opened_path
182+
*
183+
* @return bool
184+
*/
185+
public function stream_open($path, $mode, $options, &$opened_path) {
186+
$scheme = parse_url($path, PHP_URL_SCHEME);
187+
$varname = str_replace($scheme . '://', '', $path);
188+
189+
$this->real = 'file://' . self::$rootPath . '/' . $varname;
190+
191+
$this->resource = fopen($this->real, $mode);
192+
193+
return (bool) $this->resource;
194+
}
195+
196+
/**
197+
* @param int<0, max>|null $count
198+
*
199+
* @return false|string
200+
*/
201+
public function stream_read($count) {
202+
return $this->resource === false ? false : fgets($this->resource, (int) $count);
203+
}
204+
205+
/**
206+
* @return array<int|string, int>|false
207+
*/
208+
public function stream_stat() {
209+
return $this->resource === false ? false : fstat($this->resource);
210+
}
211+
};
212+
213+
$testProxyStreamWrapper::$rootPath = realpath(__DIR__) . '/Fixtures/classmap';
214+
stream_wrapper_register('test', get_class($testProxyStreamWrapper));
215+
216+
$arrayOfSplFileInfoStreamPaths = [
217+
new \SplFileInfo('test://BackslashLineEndingString.php'),
218+
new \SplFileInfo('test://InvalidUnicode.php'),
219+
];
220+
221+
self::assertSame(
222+
[
223+
'Foo\\SlashedA' => 'test://BackslashLineEndingString.php',
224+
'Foo\\SlashedB' => 'test://BackslashLineEndingString.php',
225+
'Smarty_Internal_Compile_Block' => 'test://InvalidUnicode.php',
226+
'Smarty_Internal_Compile_Blockclose' => 'test://InvalidUnicode.php',
227+
],
228+
ClassMapGenerator::createMap($arrayOfSplFileInfoStreamPaths)
229+
);
230+
}
231+
153232
public function testAmbiguousReference(): void
154233
{
155234
$tempDir = self::getUniqueTmpDirectory();

0 commit comments

Comments
 (0)