[!!!][TASK] Use request type constants everywhere
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / PathUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Class with helper functions for file paths.
19 */
20 class PathUtility
21 {
22 /**
23 * Gets the relative path from the current used script to a given directory.
24 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
25 *
26 * @param string $targetPath Absolute target path
27 * @return NULL|string
28 */
29 public static function getRelativePathTo($targetPath)
30 {
31 return self::getRelativePath(dirname(PATH_thisScript), $targetPath);
32 }
33
34 /**
35 * Creates an absolute URL out of really any input path, removes '../' parts for the targetPath
36 *
37 * @param string $targetPath can be "../typo3conf/ext/myext/myfile.js" or "/myfile.js"
38 * @return string something like "/mysite/typo3conf/ext/myext/myfile.js"
39 */
40 public static function getAbsoluteWebPath($targetPath)
41 {
42 if (self::isAbsolutePath($targetPath)) {
43 if (StringUtility::beginsWith($targetPath, PATH_site)) {
44 $targetPath = self::stripPathSitePrefix($targetPath);
45 if (!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
46 $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
47 }
48 }
49 } elseif (strpos($targetPath, '://') !== false) {
50 return $targetPath;
51 } else {
52 // Make an absolute path out of it
53 $targetPath = GeneralUtility::resolveBackPath(dirname(PATH_thisScript) . '/' . $targetPath);
54 $targetPath = self::stripPathSitePrefix($targetPath);
55 if (!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
56 $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
57 }
58 }
59 return $targetPath;
60 }
61
62 /**
63 * Gets the relative path from a source directory to a target directory.
64 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
65 *
66 * @param string $sourcePath Absolute source path
67 * @param string $targetPath Absolute target path
68 * @return NULL|string
69 */
70 public static function getRelativePath($sourcePath, $targetPath)
71 {
72 $relativePath = null;
73 $sourcePath = rtrim(GeneralUtility::fixWindowsFilePath($sourcePath), '/');
74 $targetPath = rtrim(GeneralUtility::fixWindowsFilePath($targetPath), '/');
75 if ($sourcePath !== $targetPath) {
76 $commonPrefix = self::getCommonPrefix(array($sourcePath, $targetPath));
77 if ($commonPrefix !== null && GeneralUtility::isAllowedAbsPath($commonPrefix)) {
78 $commonPrefixLength = strlen($commonPrefix);
79 $resolvedSourcePath = '';
80 $resolvedTargetPath = '';
81 $sourcePathSteps = 0;
82 if (strlen($sourcePath) > $commonPrefixLength) {
83 $resolvedSourcePath = (string)substr($sourcePath, $commonPrefixLength);
84 }
85 if (strlen($targetPath) > $commonPrefixLength) {
86 $resolvedTargetPath = (string)substr($targetPath, $commonPrefixLength);
87 }
88 if ($resolvedSourcePath !== '') {
89 $sourcePathSteps = count(explode('/', $resolvedSourcePath));
90 }
91 $relativePath = self::sanitizeTrailingSeparator(str_repeat('../', $sourcePathSteps) . $resolvedTargetPath);
92 }
93 }
94 return $relativePath;
95 }
96
97 /**
98 * Gets the common path prefix out of many paths.
99 * + /var/www/domain.com/typo3/sysext/frontend/
100 * + /var/www/domain.com/typo3/sysext/em/
101 * + /var/www/domain.com/typo3/sysext/file/
102 * = /var/www/domain.com/typo3/sysext/
103 *
104 * @param array $paths Paths to be processed
105 * @return NULL|string
106 */
107 public static function getCommonPrefix(array $paths)
108 {
109 $paths = array_map(array(\TYPO3\CMS\Core\Utility\GeneralUtility::class, 'fixWindowsFilePath'), $paths);
110 $commonPath = null;
111 if (count($paths) === 1) {
112 $commonPath = array_shift($paths);
113 } elseif (count($paths) > 1) {
114 $parts = explode('/', array_shift($paths));
115 $comparePath = '';
116 $break = false;
117 foreach ($parts as $part) {
118 $comparePath .= $part . '/';
119 foreach ($paths as $path) {
120 if (strpos($path . '/', $comparePath) !== 0) {
121 $break = true;
122 break;
123 }
124 }
125 if ($break) {
126 break;
127 }
128 $commonPath = $comparePath;
129 }
130 }
131 if ($commonPath !== null) {
132 $commonPath = self::sanitizeTrailingSeparator($commonPath, '/');
133 }
134 return $commonPath;
135 }
136
137 /**
138 * Sanitizes a trailing separator.
139 * (e.g. 'some/path' -> 'some/path/')
140 *
141 * @param string $path The path to be sanitized
142 * @param string $separator The separator to be used
143 * @return string
144 */
145 public static function sanitizeTrailingSeparator($path, $separator = '/')
146 {
147 return rtrim($path, $separator) . $separator;
148 }
149
150 /**
151 * Returns trailing name component of path
152 * Since basename() is locale dependent we need to access
153 * the filesystem with the same locale of the system, not
154 * the rendering context.
155 * @see http://www.php.net/manual/en/function.basename.php
156 *
157 *
158 * @param string $path
159 *
160 * @return string
161 *
162 */
163 public static function basename($path)
164 {
165 $currentLocale = setlocale(LC_CTYPE, 0);
166 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
167 $basename = basename($path);
168 setlocale(LC_CTYPE, $currentLocale);
169 return $basename;
170 }
171
172 /**
173 * Returns parent directory's path
174 * Since dirname() is locale dependent we need to access
175 * the filesystem with the same locale of the system, not
176 * the rendering context.
177 * @see http://www.php.net/manual/en/function.dirname.php
178 *
179 *
180 * @param string $path
181 *
182 * @return string
183 *
184 */
185 public static function dirname($path)
186 {
187 $currentLocale = setlocale(LC_CTYPE, 0);
188 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
189 $dirname = dirname($path);
190 setlocale(LC_CTYPE, $currentLocale);
191 return $dirname;
192 }
193
194 /**
195 * Returns parent directory's path
196 * Since dirname() is locale dependent we need to access
197 * the filesystem with the same locale of the system, not
198 * the rendering context.
199 * @see http://www.php.net/manual/en/function.dirname.php
200 *
201 *
202 * @param string $path
203 * @param int $options
204 *
205 * @return string|array
206 *
207 */
208 public static function pathinfo($path, $options = null)
209 {
210 $currentLocale = setlocale(LC_CTYPE, 0);
211 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
212 $pathinfo = $options == null ? pathinfo($path) : pathinfo($path, $options);
213 setlocale(LC_CTYPE, $currentLocale);
214 return $pathinfo;
215 }
216
217 /**
218 * Checks if the $path is absolute or relative (detecting either '/' or 'x:/' as first part of string) and returns TRUE if so.
219 *
220 * @param string $path File path to evaluate
221 * @return bool
222 */
223 public static function isAbsolutePath($path)
224 {
225 // On Windows also a path starting with a drive letter is absolute: X:/
226 if (static::isWindows() && (substr($path, 1, 2) === ':/' || substr($path, 1, 2) === ':\\')) {
227 return true;
228 }
229 // Path starting with a / is always absolute, on every system
230 return $path[0] === '/';
231 }
232
233 /**
234 * Gets the (absolute) path of an include file based on the (absolute) path of a base file
235 *
236 * Does NOT do any sanity checks. This is a task for the calling function, e.g.
237 * call GeneralUtility::getFileAbsFileName() on the result.
238 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName()
239 *
240 * Resolves all dots and slashes between that paths of both files.
241 * Whether the result is absolute or not, depends of the base file name.
242 *
243 * If the include file goes higher than a relative base file, then the result
244 * will contain dots as a relative part.
245 * <pre>
246 * base: abc/one.txt
247 * include: ../../two.txt
248 * result: ../two.txt
249 * </pre>
250 * The exact behavior, refer to getCanonicalPath().
251 *
252 * @param string $baseFilenameOrPath The name of the file or a path that serves as a base; a path will need to have a '/' at the end
253 * @param string $includeFileName The name of the file that is included in the file
254 * @return string The (absolute) path of the include file
255 */
256 public static function getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
257 {
258 $fileName = static::basename($includeFileName);
259 $basePath = substr($baseFilenameOrPath, -1) === '/' ? $baseFilenameOrPath : static::dirname($baseFilenameOrPath);
260 $newDir = static::getCanonicalPath($basePath . '/' . static::dirname($includeFileName));
261 // Avoid double slash on empty path
262 $result = (($newDir !== '/') ? $newDir : '') . '/' . $fileName;
263 return $result;
264 }
265
266 /*********************
267 *
268 * Cleaning methods
269 *
270 *********************/
271 /**
272 * Resolves all dots, slashes and removes spaces after or before a path...
273 *
274 * @param string $path Input string
275 * @return string Canonical path, always without trailing slash
276 */
277 public static function getCanonicalPath($path)
278 {
279 // Replace backslashes with slashes to work with Windows paths if given
280 $path = trim(str_replace('\\', '/', $path));
281
282 // @todo do we really need this? Probably only in testing context for vfs?
283 $protocol = '';
284 if (strpos($path, '://') !== false) {
285 list($protocol, $path) = explode('://', $path);
286 $protocol .= '://';
287 }
288
289 $absolutePathPrefix = '';
290 if (static::isAbsolutePath($path)) {
291 if (static::isWindows() && substr($path, 1, 2) === ':/') {
292 $absolutePathPrefix = substr($path, 0, 3);
293 $path = substr($path, 3);
294 } else {
295 $path = ltrim($path, '/');
296 $absolutePathPrefix = '/';
297 }
298 }
299
300 $theDirParts = explode('/', $path);
301 $theDirPartsCount = count($theDirParts);
302 for ($partCount = 0; $partCount < $theDirPartsCount; $partCount++) {
303 // double-slashes in path: remove element
304 if ($theDirParts[$partCount] === '') {
305 array_splice($theDirParts, $partCount, 1);
306 $partCount--;
307 $theDirPartsCount--;
308 }
309 // "." in path: remove element
310 if ($theDirParts[$partCount] === '.') {
311 array_splice($theDirParts, $partCount, 1);
312 $partCount--;
313 $theDirPartsCount--;
314 }
315 // ".." in path:
316 if ($theDirParts[$partCount] === '..') {
317 if ($partCount >= 1) {
318 // Rremove this and previous element
319 array_splice($theDirParts, $partCount - 1, 2);
320 $partCount -= 2;
321 $theDirPartsCount -= 2;
322 } elseif ($absolutePathPrefix) {
323 // can't go higher than root dir
324 // simply remove this part and continue
325 array_splice($theDirParts, $partCount, 1);
326 $partCount--;
327 $theDirPartsCount--;
328 }
329 }
330 }
331
332 return $protocol . $absolutePathPrefix . implode('/', $theDirParts);
333 }
334
335 /**
336 * Strip first part of a path, equal to the length of PATH_site
337 *
338 * @param string $path
339 * @return string
340 * @internal
341 */
342 public static function stripPathSitePrefix($path)
343 {
344 static $pathSiteLength = null;
345
346 // calculate length when first needed
347 if (!isset($pathSiteLength)) {
348 $pathSiteLength = strlen(PATH_site);
349 }
350 return substr($path, $pathSiteLength);
351 }
352
353 /*********************
354 *
355 * Helper methods
356 *
357 *********************/
358
359 /**
360 * Wrapper method to be able to test windows path transformation on other systems
361 *
362 * @return bool
363 */
364 protected static function isWindows()
365 {
366 return TYPO3_OS === 'WIN';
367 }
368 }