Happy 2015
[squirrelmail.git] / functions / files.php
1 <?php
2
3 /**
4 * files.php
5 *
6 * This file includes various helper functions for working
7 * with the server filesystem.
8 *
9 * @copyright 2008-2015 The SquirrelMail Project Team
10 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
11 * @version $Id$
12 * @package squirrelmail
13 */
14
15
16 /**
17 * Generates a unique file in a specific directory and
18 * returns the file name (without the path).
19 *
20 * @param directory The directory within which to create the file
21 *
22 * @return mixed FALSE when a failure occurs, otherwise a string
23 * containing the filename of the file only (not
24 * its full path)
25 *
26 * @since 1.5.2
27 *
28 */
29 function sq_create_tempfile($directory)
30 {
31
32 // give up after 1000 tries
33 $maximum_tries = 1000;
34
35 // using PHP >= 4.3.2 we can be truly atomic here
36 $filemods = check_php_version(4, 3, 2) ? 'x' : 'w';
37
38 for ($try = 0; $try < $maximum_tries; ++$try) {
39
40 $localfilename = GenerateRandomString(32, '', 7);
41 $full_localfilename = $directory . DIRECTORY_SEPARATOR . $localfilename;
42
43 // filename collision. try again
44 if ( file_exists($full_localfilename) ) {
45 continue;
46 }
47
48 // try to open for (binary) writing
49 $fp = @fopen( $full_localfilename, $filemods);
50
51 if ($fp !== FALSE) {
52 // success! make sure it's not readable, close and return filename
53 chmod($full_localfilename, 0600);
54 fclose($fp);
55 return $localfilename;
56 }
57
58 }
59
60 // we tried as many times as we could but didn't succeed.
61 return FALSE;
62
63 }
64
65
66 /**
67 * PHP's is_writable() is broken in some versions due to either
68 * safe_mode or because of problems correctly determining the
69 * actual file permissions under Windows. Under safe_mode or
70 * Windows, we'll try to actually write something in order to
71 * see for sure...
72 *
73 * @param string $path The full path to the file or directory to
74 * be tested
75 *
76 * @return boolean Whether or not the file or directory exists
77 * and is writable
78 *
79 * @since 1.5.2
80 *
81 **/
82 function sq_is_writable($path) {
83
84 global $server_os;
85
86
87 // under *nix with safe_mode off, use the native is_writable()
88 //
89 if ($server_os == '*nix' && !(bool)ini_get('safe_mode'))
90 return is_writable($path);
91
92
93 // if it's a directory, that means we have to create a temporary
94 // file therein
95 //
96 $delete_temp_file = FALSE;
97 if (@is_dir($path) && ($temp_filename = @sq_create_tempfile($path)))
98 {
99 $path .= DIRECTORY_SEPARATOR . $temp_filename;
100 $delete_temp_file = TRUE;
101 }
102
103
104 // try to open the file for writing (without trying to create it)
105 //
106 if (!@is_dir($path) && ($FILE = @fopen($path, 'r+')))
107 {
108 @fclose($FILE);
109
110 // delete temp file if needed
111 //
112 if ($delete_temp_file)
113 @unlink($path);
114
115 return TRUE;
116 }
117
118
119 // delete temp file if needed
120 //
121 if ($delete_temp_file)
122 @unlink($path);
123
124 return FALSE;
125
126 }
127
128
129 /**
130 * Find files and/or directories in a given directory optionally
131 * limited to only those with the given file extension. If the
132 * directory is not found or cannot be opened, no error is generated;
133 * only an empty file list is returned.
134 FIXME: do we WANT to throw an error or a notice or... or return FALSE?
135 *
136 * @param string $directory_path The path (relative or absolute)
137 * to the desired directory.
138 * @param mixed $extension The file extension filter - either
139 * an array of desired extension(s),
140 * or a comma-separated list of same
141 * (optional; default is to return
142 * all files (dirs).
143 * @param boolean $return_filenames_only When TRUE, only file/dir names
144 * are returned, otherwise the
145 * $directory_path string is
146 * prepended to each file/dir in
147 * the returned list (optional;
148 * default is filename/dirname only)
149 * @param boolean $include_directories When TRUE, directories are
150 * included (optional; default
151 * DO include directories).
152 * @param boolean $directories_only When TRUE, ONLY directories
153 * are included (optional; default
154 * is to include files too).
155 * @param boolean $separate_files_and_directories When TRUE, files and
156 * directories are returned
157 * in separate lists, so
158 * the return value is
159 * formatted as a two-element
160 * array with the two keys
161 * "FILES" and "DIRECTORIES",
162 * where corresponding values
163 * are lists of either all
164 * files or all directories
165 * (optional; default do not
166 * split up return array).
167 * @param boolean $only_sm When TRUE, a security check will
168 * limit directory access to only
169 * paths within the SquirrelMail
170 * installation currently being used
171 * (optional; default TRUE)
172 *
173 * @return array The requested file/directory list(s).
174 *
175 * @since 1.5.2
176 *
177 */
178 function list_files($directory_path, $extensions='', $return_filenames_only=TRUE,
179 $include_directories=TRUE, $directories_only=FALSE,
180 $separate_files_and_directories=FALSE, $only_sm=TRUE) {
181
182 $files = array();
183 $directories = array();
184
185
186 // make sure requested path is under SM_PATH if needed
187 //
188 if ($only_sm) {
189 if (strpos(realpath($directory_path), realpath(SM_PATH)) !== 0) {
190 //plain_error_message(_("Illegal filesystem access was requested"));
191 echo _("Illegal filesystem access was requested");
192 exit;
193 }
194 }
195
196
197 // validate given directory
198 //
199 if (empty($directory_path)
200 || !is_dir($directory_path)
201 || !($DIR = opendir($directory_path))) {
202 return $files;
203 }
204
205
206 // ensure extensions is an array and is properly formatted
207 //
208 if (!empty($extensions)) {
209 if (!is_array($extensions))
210 $extensions = explode(',', $extensions);
211 $temp_extensions = array();
212 foreach ($extensions as $ext)
213 $temp_extensions[] = '.' . trim(trim($ext), '.');
214 $extensions = $temp_extensions;
215 } else $extensions = array();
216
217
218 $directory_path = rtrim($directory_path, '/');
219
220
221 // parse through the files
222 //
223 while (($file = readdir($DIR)) !== false) {
224
225 if ($file == '.' || $file == '..') continue;
226
227 if (!empty($extensions))
228 foreach ($extensions as $ext)
229 if (strrpos($file, $ext) !== (strlen($file) - strlen($ext)))
230 continue 2;
231
232 // only use is_dir() if we really need to (be as efficient as possible)
233 //
234 $is_dir = FALSE;
235 if (!$include_directories || $directories_only
236 || $separate_files_and_directories) {
237 if (is_dir($directory_path . '/' . $file)) {
238 if (!$include_directories) continue;
239 $is_dir = TRUE;
240 $directories[] = ($return_filenames_only
241 ? $file
242 : $directory_path . '/' . $file);
243 }
244 if ($directories_only) continue;
245 }
246
247 if (!$separate_files_and_directories
248 || ($separate_files_and_directories && !$is_dir)) {
249 $files[] = ($return_filenames_only
250 ? $file
251 : $directory_path . '/' . $file);
252 }
253
254 }
255 closedir($DIR);
256
257
258 if ($directories_only) return $directories;
259 if ($separate_files_and_directories) return array('FILES' => $files,
260 'DIRECTORIES' => $directories);
261 return $files;
262
263 }
264
265
266 /**
267 * Determine if there are lines in a file longer than a given length
268 *
269 * @param string $filename The full file path of the file to inspect
270 * @param int $max_length If any lines in the file are GREATER THAN
271 * this number, this function returns TRUE.
272 *
273 * @return boolean TRUE as explained above, otherwise, (no long lines
274 * found) FALSE is returned.
275 *
276 */
277 function file_has_long_lines($filename, $max_length) {
278
279 $FILE = @fopen($filename, 'rb');
280
281 if ($FILE) {
282 while (!feof($FILE)) {
283 $buffer = fgets($FILE, 4096);
284 if (strlen($buffer) > $max_length) {
285 fclose($FILE);
286 return TRUE;
287 }
288 }
289 fclose($FILE);
290 }
291
292 return FALSE;
293 }
294
295