1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage FileSystem
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 if (!defined('JPATH_ROOT'))
13 {
14 // Define a string constant for the root directory of the file system in native format
15 $pathHelper = new JFilesystemWrapperPath;
16 define('JPATH_ROOT', $pathHelper->clean(JPATH_SITE));
17 }
18
19 /**
20 * A Path handling class
21 *
22 * @since 11.1
23 */
24 class JPath
25 {
26 /**
27 * Checks if a path's permissions can be changed.
28 *
29 * @param string $path Path to check.
30 *
31 * @return boolean True if path can have mode changed.
32 *
33 * @since 11.1
34 */
35 public static function canChmod($path)
36 {
37 $perms = fileperms($path);
38
39 if ($perms !== false)
40 {
41 if (@chmod($path, $perms ^ 0001))
42 {
43 @chmod($path, $perms);
44
45 return true;
46 }
47 }
48
49 return false;
50 }
51
52 /**
53 * Chmods files and directories recursively to given permissions.
54 *
55 * @param string $path Root path to begin changing mode [without trailing slash].
56 * @param string $filemode Octal representation of the value to change file mode to [null = no change].
57 * @param string $foldermode Octal representation of the value to change folder mode to [null = no change].
58 *
59 * @return boolean True if successful [one fail means the whole operation failed].
60 *
61 * @since 11.1
62 */
63 public static function setPermissions($path, $filemode = '0644', $foldermode = '0755')
64 {
65 // Initialise return value
66 $ret = true;
67
68 if (is_dir($path))
69 {
70 $dh = opendir($path);
71
72 while ($file = readdir($dh))
73 {
74 if ($file != '.' && $file != '..')
75 {
76 $fullpath = $path . '/' . $file;
77
78 if (is_dir($fullpath))
79 {
80 if (!self::setPermissions($fullpath, $filemode, $foldermode))
81 {
82 $ret = false;
83 }
84 }
85 else
86 {
87 if (isset($filemode))
88 {
89 if (!@ chmod($fullpath, octdec($filemode)))
90 {
91 $ret = false;
92 }
93 }
94 }
95 }
96 }
97
98 closedir($dh);
99
100 if (isset($foldermode))
101 {
102 if (!@ chmod($path, octdec($foldermode)))
103 {
104 $ret = false;
105 }
106 }
107 }
108 else
109 {
110 if (isset($filemode))
111 {
112 $ret = @ chmod($path, octdec($filemode));
113 }
114 }
115
116 return $ret;
117 }
118
119 /**
120 * Get the permissions of the file/folder at a given path.
121 *
122 * @param string $path The path of a file/folder.
123 *
124 * @return string Filesystem permissions.
125 *
126 * @since 11.1
127 */
128 public static function getPermissions($path)
129 {
130 $path = self::clean($path);
131 $mode = @ decoct(@ fileperms($path) & 0777);
132
133 if (strlen($mode) < 3)
134 {
135 return '---------';
136 }
137
138 $parsed_mode = '';
139
140 for ($i = 0; $i < 3; $i++)
141 {
142 // Read
143 $parsed_mode .= ($mode{$i} & 04) ? 'r' : '-';
144
145 // Write
146 $parsed_mode .= ($mode{$i} & 02) ? 'w' : '-';
147
148 // Execute
149 $parsed_mode .= ($mode{$i} & 01) ? 'x' : '-';
150 }
151
152 return $parsed_mode;
153 }
154
155 /**
156 * Checks for snooping outside of the file system root.
157 *
158 * @param string $path A file system path to check.
159 *
160 * @return string A cleaned version of the path or exit on error.
161 *
162 * @since 11.1
163 * @throws Exception
164 */
165 public static function check($path)
166 {
167 if (strpos($path, '..') !== false)
168 {
169 // Don't translate
170 throw new Exception('JPath::check Use of relative paths not permitted', 20);
171 }
172
173 $path = self::clean($path);
174
175 if ((JPATH_ROOT != '') && strpos($path, self::clean(JPATH_ROOT)) !== 0)
176 {
177 throw new Exception('JPath::check Snooping out of bounds @ ' . $path, 20);
178 }
179
180 return $path;
181 }
182
183 /**
184 * Function to strip additional / or \ in a path name.
185 *
186 * @param string $path The path to clean.
187 * @param string $ds Directory separator (optional).
188 *
189 * @return string The cleaned path.
190 *
191 * @since 11.1
192 * @throws UnexpectedValueException
193 */
194 public static function clean($path, $ds = DIRECTORY_SEPARATOR)
195 {
196 if (!is_string($path) && !empty($path))
197 {
198 throw new UnexpectedValueException('JPath::clean: $path is not a string.');
199 }
200
201 $path = trim($path);
202
203 if (empty($path))
204 {
205 $path = JPATH_ROOT;
206 }
207 // Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
208 // If dealing with a UNC path don't forget to prepend the path with a backslash.
209 elseif (($ds == '\\') && substr($path, 0, 2) == '\\\\')
210 {
211 $path = "\\" . preg_replace('#[/\\\\]+#', $ds, $path);
212 }
213 else
214 {
215 $path = preg_replace('#[/\\\\]+#', $ds, $path);
216 }
217
218 return $path;
219 }
220
221 /**
222 * Method to determine if script owns the path.
223 *
224 * @param string $path Path to check ownership.
225 *
226 * @return boolean True if the php script owns the path passed.
227 *
228 * @since 11.1
229 */
230 public static function isOwner($path)
231 {
232 jimport('joomla.filesystem.file');
233
234 $tmp = md5(JCrypt::genRandomBytes());
235 $ssp = ini_get('session.save_path');
236 $jtp = JPATH_SITE . '/tmp';
237
238 // Try to find a writable directory
239 $dir = false;
240
241 foreach (array($jtp, $ssp, '/tmp') as $currentDir)
242 {
243 if (is_writable($currentDir))
244 {
245 $dir = $currentDir;
246
247 break;
248 }
249 }
250
251 if ($dir)
252 {
253 $fileObject = new JFilesystemWrapperFile;
254 $test = $dir . '/' . $tmp;
255
256 // Create the test file
257 $blank = '';
258 $fileObject->write($test, $blank, false);
259
260 // Test ownership
261 $return = (fileowner($test) == fileowner($path));
262
263 // Delete the test file
264 $fileObject->delete($test);
265
266 return $return;
267 }
268
269 return false;
270 }
271
272 /**
273 * Searches the directory paths for a given file.
274 *
275 * @param mixed $paths An path string or array of path strings to search in
276 * @param string $file The file name to look for.
277 *
278 * @return mixed The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
279 *
280 * @since 11.1
281 */
282 public static function find($paths, $file)
283 {
284 // Force to array
285 if (!is_array($paths) && !($paths instanceof Iterator))
286 {
287 settype($paths, 'array');
288 }
289
290 // Start looping through the path set
291 foreach ($paths as $path)
292 {
293 // Get the path to the file
294 $fullname = $path . '/' . $file;
295
296 // Is the path based on a stream?
297 if (strpos($path, '://') === false)
298 {
299 // Not a stream, so do a realpath() to avoid directory
300 // traversal attempts on the local file system.
301
302 // Needed for substr() later
303 $path = realpath($path);
304 $fullname = realpath($fullname);
305 }
306
307 /*
308 * The substr() check added to make sure that the realpath()
309 * results in a directory registered so that
310 * non-registered directories are not accessible via directory
311 * traversal attempts.
312 */
313 if (file_exists($fullname) && substr($fullname, 0, strlen($path)) == $path)
314 {
315 return $fullname;
316 }
317 }
318
319 // Could not find the file in the set of paths
320 return false;
321 }
322 }
323