root / drupal7 / misc / typo3 / phar-stream-wrapper / src / Resolver / PharInvocationResolver.php @ 6b24a280
1 |
<?php
|
---|---|
2 |
namespace TYPO3\PharStreamWrapper\Resolver; |
3 |
|
4 |
/*
|
5 |
* This file is part of the TYPO3 project.
|
6 |
*
|
7 |
* It is free software; you can redistribute it and/or modify it under the terms
|
8 |
* of the MIT License (MIT). For the full copyright and license information,
|
9 |
* please read the LICENSE file that was distributed with this source code.
|
10 |
*
|
11 |
* The TYPO3 project - inspiring people to share!
|
12 |
*/
|
13 |
|
14 |
use TYPO3\PharStreamWrapper\Helper; |
15 |
use TYPO3\PharStreamWrapper\Manager; |
16 |
use TYPO3\PharStreamWrapper\Phar\Reader; |
17 |
use TYPO3\PharStreamWrapper\Phar\ReaderException; |
18 |
use TYPO3\PharStreamWrapper\Resolvable; |
19 |
|
20 |
class PharInvocationResolver implements Resolvable |
21 |
{ |
22 |
const RESOLVE_REALPATH = 1; |
23 |
const RESOLVE_ALIAS = 2; |
24 |
const ASSERT_INTERNAL_INVOCATION = 32; |
25 |
|
26 |
/**
|
27 |
* @var string[]
|
28 |
*/
|
29 |
private $invocationFunctionNames = array( |
30 |
'include',
|
31 |
'include_once',
|
32 |
'require',
|
33 |
'require_once'
|
34 |
); |
35 |
|
36 |
/**
|
37 |
* Contains resolved base names in order to reduce file IO.
|
38 |
*
|
39 |
* @var string[]
|
40 |
*/
|
41 |
private $baseNames = array(); |
42 |
|
43 |
/**
|
44 |
* Resolves PharInvocation value object (baseName and optional alias).
|
45 |
*
|
46 |
* Phar aliases are intended to be used only inside Phar archives, however
|
47 |
* PharStreamWrapper needs this information exposed outside of Phar as well
|
48 |
* It is possible that same alias is used for different $baseName values.
|
49 |
* That's why PharInvocationCollection behaves like a stack when resolving
|
50 |
* base-name for a given alias. On the other hand it is not possible that
|
51 |
* one $baseName is referring to multiple aliases.
|
52 |
* @see https://secure.php.net/manual/en/phar.setalias.php
|
53 |
* @see https://secure.php.net/manual/en/phar.mapphar.php
|
54 |
*
|
55 |
* @param string $path
|
56 |
* @param int|null $flags
|
57 |
* @return null|PharInvocation
|
58 |
*/
|
59 |
public function resolve($path, $flags = null) |
60 |
{ |
61 |
$hasPharPrefix = Helper::hasPharPrefix($path); |
62 |
if ($flags === null) { |
63 |
$flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS; |
64 |
} |
65 |
|
66 |
if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) { |
67 |
$invocation = $this->findByAlias($path); |
68 |
if ($invocation !== null) { |
69 |
return $invocation; |
70 |
} |
71 |
} |
72 |
|
73 |
$baseName = $this->resolveBaseName($path, $flags); |
74 |
if ($baseName === null) { |
75 |
return null; |
76 |
} |
77 |
|
78 |
if ($flags & static::RESOLVE_REALPATH) { |
79 |
$baseName = $this->baseNames[$baseName]; |
80 |
} |
81 |
|
82 |
return $this->retrieveInvocation($baseName, $flags); |
83 |
} |
84 |
|
85 |
/**
|
86 |
* Retrieves PharInvocation, either existing in collection or created on demand
|
87 |
* with resolving a potential alias name used in the according Phar archive.
|
88 |
*
|
89 |
* @param string $baseName
|
90 |
* @param int $flags
|
91 |
* @return PharInvocation
|
92 |
*/
|
93 |
private function retrieveInvocation($baseName, $flags) |
94 |
{ |
95 |
$invocation = $this->findByBaseName($baseName); |
96 |
if ($invocation !== null) { |
97 |
return $invocation; |
98 |
} |
99 |
|
100 |
if ($flags & static::RESOLVE_ALIAS) { |
101 |
$reader = new Reader($baseName); |
102 |
$alias = $reader->resolveContainer()->getAlias(); |
103 |
} else {
|
104 |
$alias = ''; |
105 |
} |
106 |
// add unconfirmed(!) new invocation to collection
|
107 |
$invocation = new PharInvocation($baseName, $alias); |
108 |
Manager::instance()->getCollection()->collect($invocation); |
109 |
return $invocation; |
110 |
} |
111 |
|
112 |
/**
|
113 |
* @param string $path
|
114 |
* @param int $flags
|
115 |
* @return null|string
|
116 |
*/
|
117 |
private function resolveBaseName($path, $flags) |
118 |
{ |
119 |
$baseName = $this->findInBaseNames($path); |
120 |
if ($baseName !== null) { |
121 |
return $baseName; |
122 |
} |
123 |
|
124 |
$baseName = Helper::determineBaseFile($path); |
125 |
if ($baseName !== null) { |
126 |
$this->addBaseName($baseName); |
127 |
return $baseName; |
128 |
} |
129 |
|
130 |
$possibleAlias = $this->resolvePossibleAlias($path); |
131 |
if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) { |
132 |
return null; |
133 |
} |
134 |
|
135 |
$trace = debug_backtrace(); |
136 |
foreach ($trace as $item) { |
137 |
if (!isset($item['function']) || !isset($item['args'][0]) |
138 |
|| !in_array($item['function'], $this->invocationFunctionNames, true)) { |
139 |
continue;
|
140 |
} |
141 |
$currentPath = $item['args'][0]; |
142 |
if (Helper::hasPharPrefix($currentPath)) { |
143 |
continue;
|
144 |
} |
145 |
$currentBaseName = Helper::determineBaseFile($currentPath); |
146 |
if ($currentBaseName === null) { |
147 |
continue;
|
148 |
} |
149 |
// ensure the possible alias name (how we have been called initially) matches
|
150 |
// the resolved alias name that was retrieved by the current possible base name
|
151 |
try {
|
152 |
$reader = new Reader($currentBaseName); |
153 |
$currentAlias = $reader->resolveContainer()->getAlias(); |
154 |
} catch (ReaderException $exception) { |
155 |
// most probably that was not a Phar file
|
156 |
continue;
|
157 |
} |
158 |
if (empty($currentAlias) || $currentAlias !== $possibleAlias) { |
159 |
continue;
|
160 |
} |
161 |
$this->addBaseName($currentBaseName); |
162 |
return $currentBaseName; |
163 |
} |
164 |
|
165 |
return null; |
166 |
} |
167 |
|
168 |
/**
|
169 |
* @param string $path
|
170 |
* @return null|string
|
171 |
*/
|
172 |
private function resolvePossibleAlias($path) |
173 |
{ |
174 |
$normalizedPath = Helper::normalizePath($path); |
175 |
return strstr($normalizedPath, '/', true) ?: null; |
176 |
} |
177 |
|
178 |
/**
|
179 |
* @param string $baseName
|
180 |
* @return null|PharInvocation
|
181 |
*/
|
182 |
private function findByBaseName($baseName) |
183 |
{ |
184 |
return Manager::instance()->getCollection()->findByCallback( |
185 |
function (PharInvocation $candidate) use ($baseName) { |
186 |
return $candidate->getBaseName() === $baseName; |
187 |
}, |
188 |
true
|
189 |
); |
190 |
} |
191 |
|
192 |
/**
|
193 |
* @param string $path
|
194 |
* @return null|string
|
195 |
*/
|
196 |
private function findInBaseNames($path) |
197 |
{ |
198 |
// return directly if the resolved base name was submitted
|
199 |
if (in_array($path, $this->baseNames, true)) { |
200 |
return $path; |
201 |
} |
202 |
|
203 |
$parts = explode('/', Helper::normalizePath($path)); |
204 |
|
205 |
while (count($parts)) { |
206 |
$currentPath = implode('/', $parts); |
207 |
if (isset($this->baseNames[$currentPath])) { |
208 |
return $currentPath; |
209 |
} |
210 |
array_pop($parts); |
211 |
} |
212 |
|
213 |
return null; |
214 |
} |
215 |
|
216 |
/**
|
217 |
* @param string $baseName
|
218 |
*/
|
219 |
private function addBaseName($baseName) |
220 |
{ |
221 |
if (isset($this->baseNames[$baseName])) { |
222 |
return;
|
223 |
} |
224 |
$this->baseNames[$baseName] = Helper::normalizeWindowsPath( |
225 |
realpath($baseName) |
226 |
); |
227 |
} |
228 |
|
229 |
/**
|
230 |
* Finds confirmed(!) invocations by alias.
|
231 |
*
|
232 |
* @param string $path
|
233 |
* @return null|PharInvocation
|
234 |
* @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
|
235 |
*/
|
236 |
private function findByAlias($path) |
237 |
{ |
238 |
$possibleAlias = $this->resolvePossibleAlias($path); |
239 |
if ($possibleAlias === null) { |
240 |
return null; |
241 |
} |
242 |
return Manager::instance()->getCollection()->findByCallback( |
243 |
function (PharInvocation $candidate) use ($possibleAlias) { |
244 |
return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias; |
245 |
}, |
246 |
true
|
247 |
); |
248 |
} |
249 |
} |