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