comparison vendor/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php @ 5:12f9dff5fda9 tip

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