67c826ce |
1 | <?php |
2 | |
3 | /** |
4 | * files.php |
5 | * |
6 | * This file includes various helper functions for working |
7 | * with the server filesystem. |
8 | * |
d4e46166 |
9 | * @copyright © 2008-2009 The SquirrelMail Project Team |
67c826ce |
10 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
18561566 |
11 | * @version $Id$ |
67c826ce |
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 | |
89f19004 |
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 | |