82921d526020c99cda82c04a95b55f3b7dea83d8
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / PathUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2012-2013 Oliver Hader <oliver.hader@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Class with helper functions for file paths.
31 *
32 * @author Oliver Hader <oliver.hader@typo3.org>
33 */
34 class PathUtility {
35
36 /**
37 * Gets the relative path from the current used script to a given directory.
38 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
39 *
40 * @param string $targetPath Absolute target path
41 * @return NULL|string
42 */
43 static public function getRelativePathTo($targetPath) {
44 return self::getRelativePath(dirname(PATH_thisScript), $targetPath);
45 }
46
47 /**
48 * Gets the relative path from a source directory to a target directory.
49 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
50 *
51 * @param string $sourcePath Absolute source path
52 * @param string $targetPath Absolute target path
53 * @return NULL|string
54 */
55 static public function getRelativePath($sourcePath, $targetPath) {
56 $relativePath = NULL;
57 $sourcePath = rtrim(GeneralUtility::fixWindowsFilePath($sourcePath), '/');
58 $targetPath = rtrim(GeneralUtility::fixWindowsFilePath($targetPath), '/');
59 if ($sourcePath !== $targetPath) {
60 $commonPrefix = self::getCommonPrefix(array($sourcePath, $targetPath));
61 if ($commonPrefix !== NULL && \TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath($commonPrefix)) {
62 $commonPrefixLength = strlen($commonPrefix);
63 $resolvedSourcePath = '';
64 $resolvedTargetPath = '';
65 $sourcePathSteps = 0;
66 if (strlen($sourcePath) > $commonPrefixLength) {
67 $resolvedSourcePath = (string) substr($sourcePath, $commonPrefixLength);
68 }
69 if (strlen($targetPath) > $commonPrefixLength) {
70 $resolvedTargetPath = (string) substr($targetPath, $commonPrefixLength);
71 }
72 if ($resolvedSourcePath !== '') {
73 $sourcePathSteps = count(explode('/', $resolvedSourcePath));
74 }
75 $relativePath = self::sanitizeTrailingSeparator(str_repeat('../', $sourcePathSteps) . $resolvedTargetPath);
76 }
77 }
78 return $relativePath;
79 }
80
81 /**
82 * Gets the common path prefix out of many paths.
83 * + /var/www/domain.com/typo3/sysext/cms/
84 * + /var/www/domain.com/typo3/sysext/em/
85 * + /var/www/domain.com/typo3/sysext/file/
86 * = /var/www/domain.com/typo3/sysext/
87 *
88 * @param array $paths Paths to be processed
89 * @return NULL|string
90 */
91 static public function getCommonPrefix(array $paths) {
92 $paths = array_map(array('TYPO3\\CMS\\Core\\Utility\\GeneralUtility', 'fixWindowsFilePath'), $paths);
93 $commonPath = NULL;
94 if (count($paths) === 1) {
95 $commonPath = array_shift($paths);
96 } elseif (count($paths) > 1) {
97 $parts = explode('/', array_shift($paths));
98 $comparePath = '';
99 $break = FALSE;
100 foreach ($parts as $part) {
101 $comparePath .= $part . '/';
102 foreach ($paths as $path) {
103 if (strpos($path . '/', $comparePath) !== 0) {
104 $break = TRUE;
105 break;
106 }
107 }
108 if ($break) {
109 break;
110 }
111 $commonPath = $comparePath;
112 }
113 }
114 if ($commonPath !== NULL) {
115 $commonPath = self::sanitizeTrailingSeparator($commonPath, '/');
116 }
117 return $commonPath;
118 }
119
120 /**
121 * Sanitizes a trailing separator.
122 * (e.g. 'some/path' -> 'some/path/')
123 *
124 * @param string $path The path to be sanitized
125 * @param string $separator The separator to be used
126 * @return string
127 */
128 static public function sanitizeTrailingSeparator($path, $separator = '/') {
129 return rtrim($path, $separator) . $separator;
130 }
131
132 /**
133 * Returns trailing name component of path
134 * Since basename() is locale dependent we need to access
135 * the filesystem with the same locale of the system, not
136 * the rendering context.
137 * @see http://www.php.net/manual/en/function.basename.php
138 *
139 *
140 * @param string $path
141 *
142 * @return string
143 *
144 */
145 static public function basename($path) {
146 $currentLocale = setlocale(LC_CTYPE, 0);
147 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
148 $basename = basename($path);
149 setlocale(LC_CTYPE, $currentLocale);
150 return $basename;
151 }
152
153 /**
154 * Returns parent directory's path
155 * Since dirname() is locale dependent we need to access
156 * the filesystem with the same locale of the system, not
157 * the rendering context.
158 * @see http://www.php.net/manual/en/function.dirname.php
159 *
160 *
161 * @param string $path
162 *
163 * @return string
164 *
165 */
166 static public function dirname($path) {
167 $currentLocale = setlocale(LC_CTYPE, 0);
168 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
169 $dirname = dirname($path);
170 setlocale(LC_CTYPE, $currentLocale);
171 return $dirname;
172 }
173
174 /**
175 * Returns parent directory's path
176 * Since dirname() is locale dependent we need to access
177 * the filesystem with the same locale of the system, not
178 * the rendering context.
179 * @see http://www.php.net/manual/en/function.dirname.php
180 *
181 *
182 * @param string $path
183 * @param integer $options
184 *
185 * @return string|array
186 *
187 */
188 static public function pathinfo($path, $options = NULL) {
189 $currentLocale = setlocale(LC_CTYPE, 0);
190 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
191 $pathinfo = $options == NULL ? pathinfo($path) : pathinfo($path, $options);
192 setlocale(LC_CTYPE, $currentLocale);
193 return $pathinfo;
194 }
195
196 /**
197 * Checks if the $path is absolute or relative (detecting either '/' or 'x:/' as first part of string) and returns TRUE if so.
198 *
199 * @param string $path File path to evaluate
200 * @return boolean
201 */
202 static public function isAbsolutePath($path) {
203 // On Windows also a path starting with a drive letter is absolute: X:/
204 if (static::isWindows() && substr($path, 1, 2) === ':/') {
205 return TRUE;
206 }
207 // Path starting with a / is always absolute, on every system
208 return substr($path, 0, 1) === '/';
209 }
210
211
212 /*********************
213 *
214 * Cleaning methods
215 *
216 *********************/
217 /**
218 * Resolves all dots, slashes and removes spaces after or before a path...
219 *
220 * @param string $path Input string
221 * @return string Canonical path, always without trailing slash
222 */
223 static public function getCanonicalPath($path) {
224 // Replace backslashes with slashes to work with Windows paths if given
225 $path = trim(str_replace('\\', '/', $path));
226
227 // TODO: do we really need this? Probably only in testing context for vfs?
228 $protocol = '';
229 if (strpos($path, '://') !== FALSE) {
230 list($protocol, $path) = explode('://', $path);
231 $protocol .= '://';
232 }
233
234 $absolutePathPrefix = '';
235 if (static::isAbsolutePath($path)) {
236 if (static::isWindows() && substr($path, 1, 2) === ':/') {
237 $absolutePathPrefix = substr($path, 0, 3);
238 $path = substr($path, 3);
239 } else {
240 $path = ltrim($path, '/');
241 $absolutePathPrefix = '/';
242 }
243 }
244
245 $theDirParts = explode('/', $path);
246 $theDirPartsCount = count($theDirParts);
247 for ($partCount = 0; $partCount < $theDirPartsCount; $partCount++) {
248 // double-slashes in path: remove element
249 if ($theDirParts[$partCount] === '') {
250 array_splice($theDirParts, $partCount, 1);
251 $partCount--;
252 $theDirPartsCount--;
253 }
254 // "." in path: remove element
255 if ($theDirParts[$partCount] === '.') {
256 array_splice($theDirParts, $partCount, 1);
257 $partCount--;
258 $theDirPartsCount--;
259 }
260 // ".." in path:
261 if ($theDirParts[$partCount] === '..') {
262 if ($partCount >= 1) {
263 // Rremove this and previous element
264 array_splice($theDirParts, $partCount - 1, 2);
265 $partCount -= 2;
266 $theDirPartsCount -= 2;
267 } elseif ($absolutePathPrefix) {
268 // can't go higher than root dir
269 // simply remove this part and continue
270 array_splice($theDirParts, $partCount, 1);
271 $partCount--;
272 $theDirPartsCount--;
273 }
274 }
275 }
276
277 return $protocol . $absolutePathPrefix . implode('/', $theDirParts);
278 }
279
280 /*********************
281 *
282 * Helper methods
283 *
284 *********************/
285
286 /**
287 * Wrapper method to be able to test windows path transformation on other systems
288 *
289 * @return bool
290 */
291 static protected function isWindows() {
292 return TYPO3_OS === 'WIN';
293 }
294
295 }
296
297
298 ?>