webmaster@1
|
1 <?php |
webmaster@1
|
2 // $Id: file.inc,v 1.121.2.1 2008/02/07 18:20:37 goba Exp $ |
webmaster@1
|
3 |
webmaster@1
|
4 /** |
webmaster@1
|
5 * @file |
webmaster@1
|
6 * API for handling file uploads and server file management. |
webmaster@1
|
7 */ |
webmaster@1
|
8 |
webmaster@1
|
9 /** |
webmaster@1
|
10 * @defgroup file File interface |
webmaster@1
|
11 * @{ |
webmaster@1
|
12 * Common file handling functions. |
webmaster@1
|
13 */ |
webmaster@1
|
14 |
webmaster@1
|
15 define('FILE_DOWNLOADS_PUBLIC', 1); |
webmaster@1
|
16 define('FILE_DOWNLOADS_PRIVATE', 2); |
webmaster@1
|
17 define('FILE_CREATE_DIRECTORY', 1); |
webmaster@1
|
18 define('FILE_MODIFY_PERMISSIONS', 2); |
webmaster@1
|
19 define('FILE_EXISTS_RENAME', 0); |
webmaster@1
|
20 define('FILE_EXISTS_REPLACE', 1); |
webmaster@1
|
21 define('FILE_EXISTS_ERROR', 2); |
webmaster@1
|
22 |
webmaster@1
|
23 /** |
webmaster@1
|
24 * A files status can be one of two values: temporary or permanent. The status |
webmaster@1
|
25 * for each file Drupal manages is stored in the {files} tables. If the status |
webmaster@1
|
26 * is temporary Drupal's file garbage collection will delete the file and |
webmaster@1
|
27 * remove it from the files table after a set period of time. |
webmaster@1
|
28 * |
webmaster@1
|
29 * If you wish to add custom statuses for use by contrib modules please expand as |
webmaster@1
|
30 * binary flags and consider the first 8 bits reserved. (0,1,2,4,8,16,32,64,128) |
webmaster@1
|
31 */ |
webmaster@1
|
32 define('FILE_STATUS_TEMPORARY', 0); |
webmaster@1
|
33 define('FILE_STATUS_PERMANENT', 1); |
webmaster@1
|
34 |
webmaster@1
|
35 /** |
webmaster@1
|
36 * Create the download path to a file. |
webmaster@1
|
37 * |
webmaster@1
|
38 * @param $path A string containing the path of the file to generate URL for. |
webmaster@1
|
39 * @return A string containing a URL that can be used to download the file. |
webmaster@1
|
40 */ |
webmaster@1
|
41 function file_create_url($path) { |
webmaster@1
|
42 // Strip file_directory_path from $path. We only include relative paths in urls. |
webmaster@1
|
43 if (strpos($path, file_directory_path() .'/') === 0) { |
webmaster@1
|
44 $path = trim(substr($path, strlen(file_directory_path())), '\\/'); |
webmaster@1
|
45 } |
webmaster@1
|
46 switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) { |
webmaster@1
|
47 case FILE_DOWNLOADS_PUBLIC: |
webmaster@1
|
48 return $GLOBALS['base_url'] .'/'. file_directory_path() .'/'. str_replace('\\', '/', $path); |
webmaster@1
|
49 case FILE_DOWNLOADS_PRIVATE: |
webmaster@1
|
50 return url('system/files/'. $path, array('absolute' => TRUE)); |
webmaster@1
|
51 } |
webmaster@1
|
52 } |
webmaster@1
|
53 |
webmaster@1
|
54 /** |
webmaster@1
|
55 * Make sure the destination is a complete path and resides in the file system |
webmaster@1
|
56 * directory, if it is not prepend the file system directory. |
webmaster@1
|
57 * |
webmaster@1
|
58 * @param $dest A string containing the path to verify. If this value is |
webmaster@1
|
59 * omitted, Drupal's 'files' directory will be used. |
webmaster@1
|
60 * @return A string containing the path to file, with file system directory |
webmaster@1
|
61 * appended if necessary, or FALSE if the path is invalid (i.e. outside the |
webmaster@1
|
62 * configured 'files' or temp directories). |
webmaster@1
|
63 */ |
webmaster@1
|
64 function file_create_path($dest = 0) { |
webmaster@1
|
65 $file_path = file_directory_path(); |
webmaster@1
|
66 if (!$dest) { |
webmaster@1
|
67 return $file_path; |
webmaster@1
|
68 } |
webmaster@1
|
69 // file_check_location() checks whether the destination is inside the Drupal files directory. |
webmaster@1
|
70 if (file_check_location($dest, $file_path)) { |
webmaster@1
|
71 return $dest; |
webmaster@1
|
72 } |
webmaster@1
|
73 // check if the destination is instead inside the Drupal temporary files directory. |
webmaster@1
|
74 else if (file_check_location($dest, file_directory_temp())) { |
webmaster@1
|
75 return $dest; |
webmaster@1
|
76 } |
webmaster@1
|
77 // Not found, try again with prefixed directory path. |
webmaster@1
|
78 else if (file_check_location($file_path .'/'. $dest, $file_path)) { |
webmaster@1
|
79 return $file_path .'/'. $dest; |
webmaster@1
|
80 } |
webmaster@1
|
81 // File not found. |
webmaster@1
|
82 return FALSE; |
webmaster@1
|
83 } |
webmaster@1
|
84 |
webmaster@1
|
85 /** |
webmaster@1
|
86 * Check that the directory exists and is writable. Directories need to |
webmaster@1
|
87 * have execute permissions to be considered a directory by FTP servers, etc. |
webmaster@1
|
88 * |
webmaster@1
|
89 * @param $directory A string containing the name of a directory path. |
webmaster@1
|
90 * @param $mode A Boolean value to indicate if the directory should be created |
webmaster@1
|
91 * if it does not exist or made writable if it is read-only. |
webmaster@1
|
92 * @param $form_item An optional string containing the name of a form item that |
webmaster@1
|
93 * any errors will be attached to. This is useful for settings forms that |
webmaster@1
|
94 * require the user to specify a writable directory. If it can't be made to |
webmaster@1
|
95 * work, a form error will be set preventing them from saving the settings. |
webmaster@1
|
96 * @return FALSE when directory not found, or TRUE when directory exists. |
webmaster@1
|
97 */ |
webmaster@1
|
98 function file_check_directory(&$directory, $mode = 0, $form_item = NULL) { |
webmaster@1
|
99 $directory = rtrim($directory, '/\\'); |
webmaster@1
|
100 |
webmaster@1
|
101 // Check if directory exists. |
webmaster@1
|
102 if (!is_dir($directory)) { |
webmaster@1
|
103 if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) { |
webmaster@1
|
104 drupal_set_message(t('The directory %directory has been created.', array('%directory' => $directory))); |
webmaster@1
|
105 @chmod($directory, 0775); // Necessary for non-webserver users. |
webmaster@1
|
106 } |
webmaster@1
|
107 else { |
webmaster@1
|
108 if ($form_item) { |
webmaster@1
|
109 form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory))); |
webmaster@1
|
110 } |
webmaster@1
|
111 return FALSE; |
webmaster@1
|
112 } |
webmaster@1
|
113 } |
webmaster@1
|
114 |
webmaster@1
|
115 // Check to see if the directory is writable. |
webmaster@1
|
116 if (!is_writable($directory)) { |
webmaster@1
|
117 if (($mode & FILE_MODIFY_PERMISSIONS) && @chmod($directory, 0775)) { |
webmaster@1
|
118 drupal_set_message(t('The permissions of directory %directory have been changed to make it writable.', array('%directory' => $directory))); |
webmaster@1
|
119 } |
webmaster@1
|
120 else { |
webmaster@1
|
121 form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory))); |
webmaster@1
|
122 watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR); |
webmaster@1
|
123 return FALSE; |
webmaster@1
|
124 } |
webmaster@1
|
125 } |
webmaster@1
|
126 |
webmaster@1
|
127 if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) { |
webmaster@1
|
128 $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks"; |
webmaster@1
|
129 if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) { |
webmaster@1
|
130 fclose($fp); |
webmaster@1
|
131 chmod($directory .'/.htaccess', 0664); |
webmaster@1
|
132 } |
webmaster@1
|
133 else { |
webmaster@1
|
134 $variables = array('%directory' => $directory, '!htaccess' => '<br />'. nl2br(check_plain($htaccess_lines))); |
webmaster@1
|
135 form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables)); |
webmaster@1
|
136 watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR); |
webmaster@1
|
137 } |
webmaster@1
|
138 } |
webmaster@1
|
139 |
webmaster@1
|
140 return TRUE; |
webmaster@1
|
141 } |
webmaster@1
|
142 |
webmaster@1
|
143 /** |
webmaster@1
|
144 * Checks path to see if it is a directory, or a dir/file. |
webmaster@1
|
145 * |
webmaster@1
|
146 * @param $path A string containing a file path. This will be set to the |
webmaster@1
|
147 * directory's path. |
webmaster@1
|
148 * @return If the directory is not in a Drupal writable directory, FALSE is |
webmaster@1
|
149 * returned. Otherwise, the base name of the path is returned. |
webmaster@1
|
150 */ |
webmaster@1
|
151 function file_check_path(&$path) { |
webmaster@1
|
152 // Check if path is a directory. |
webmaster@1
|
153 if (file_check_directory($path)) { |
webmaster@1
|
154 return ''; |
webmaster@1
|
155 } |
webmaster@1
|
156 |
webmaster@1
|
157 // Check if path is a possible dir/file. |
webmaster@1
|
158 $filename = basename($path); |
webmaster@1
|
159 $path = dirname($path); |
webmaster@1
|
160 if (file_check_directory($path)) { |
webmaster@1
|
161 return $filename; |
webmaster@1
|
162 } |
webmaster@1
|
163 |
webmaster@1
|
164 return FALSE; |
webmaster@1
|
165 } |
webmaster@1
|
166 |
webmaster@1
|
167 /** |
webmaster@1
|
168 * Check if a file is really located inside $directory. Should be used to make |
webmaster@1
|
169 * sure a file specified is really located within the directory to prevent |
webmaster@1
|
170 * exploits. |
webmaster@1
|
171 * |
webmaster@1
|
172 * @code |
webmaster@1
|
173 * // Returns FALSE: |
webmaster@1
|
174 * file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files'); |
webmaster@1
|
175 * @endcode |
webmaster@1
|
176 * |
webmaster@1
|
177 * @param $source A string set to the file to check. |
webmaster@1
|
178 * @param $directory A string where the file should be located. |
webmaster@1
|
179 * @return 0 for invalid path or the real path of the source. |
webmaster@1
|
180 */ |
webmaster@1
|
181 function file_check_location($source, $directory = '') { |
webmaster@1
|
182 $check = realpath($source); |
webmaster@1
|
183 if ($check) { |
webmaster@1
|
184 $source = $check; |
webmaster@1
|
185 } |
webmaster@1
|
186 else { |
webmaster@1
|
187 // This file does not yet exist |
webmaster@1
|
188 $source = realpath(dirname($source)) .'/'. basename($source); |
webmaster@1
|
189 } |
webmaster@1
|
190 $directory = realpath($directory); |
webmaster@1
|
191 if ($directory && strpos($source, $directory) !== 0) { |
webmaster@1
|
192 return 0; |
webmaster@1
|
193 } |
webmaster@1
|
194 return $source; |
webmaster@1
|
195 } |
webmaster@1
|
196 |
webmaster@1
|
197 /** |
webmaster@1
|
198 * Copies a file to a new location. This is a powerful function that in many ways |
webmaster@1
|
199 * performs like an advanced version of copy(). |
webmaster@1
|
200 * - Checks if $source and $dest are valid and readable/writable. |
webmaster@1
|
201 * - Performs a file copy if $source is not equal to $dest. |
webmaster@1
|
202 * - If file already exists in $dest either the call will error out, replace the |
webmaster@1
|
203 * file or rename the file based on the $replace parameter. |
webmaster@1
|
204 * |
webmaster@1
|
205 * @param $source A string specifying the file location of the original file. |
webmaster@1
|
206 * This parameter will contain the resulting destination filename in case of |
webmaster@1
|
207 * success. |
webmaster@1
|
208 * @param $dest A string containing the directory $source should be copied to. |
webmaster@1
|
209 * If this value is omitted, Drupal's 'files' directory will be used. |
webmaster@1
|
210 * @param $replace Replace behavior when the destination file already exists. |
webmaster@1
|
211 * - FILE_EXISTS_REPLACE - Replace the existing file |
webmaster@1
|
212 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique |
webmaster@1
|
213 * - FILE_EXISTS_ERROR - Do nothing and return FALSE. |
webmaster@1
|
214 * @return True for success, FALSE for failure. |
webmaster@1
|
215 */ |
webmaster@1
|
216 function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { |
webmaster@1
|
217 $dest = file_create_path($dest); |
webmaster@1
|
218 |
webmaster@1
|
219 $directory = $dest; |
webmaster@1
|
220 $basename = file_check_path($directory); |
webmaster@1
|
221 |
webmaster@1
|
222 // Make sure we at least have a valid directory. |
webmaster@1
|
223 if ($basename === FALSE) { |
webmaster@1
|
224 $source = is_object($source) ? $source->filepath : $source; |
webmaster@1
|
225 drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error'); |
webmaster@1
|
226 watchdog('file system', 'The selected file %file could not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => $source, '%directory' => $dest), WATCHDOG_ERROR); |
webmaster@1
|
227 return 0; |
webmaster@1
|
228 } |
webmaster@1
|
229 |
webmaster@1
|
230 // Process a file upload object. |
webmaster@1
|
231 if (is_object($source)) { |
webmaster@1
|
232 $file = $source; |
webmaster@1
|
233 $source = $file->filepath; |
webmaster@1
|
234 if (!$basename) { |
webmaster@1
|
235 $basename = $file->filename; |
webmaster@1
|
236 } |
webmaster@1
|
237 } |
webmaster@1
|
238 |
webmaster@1
|
239 $source = realpath($source); |
webmaster@1
|
240 if (!file_exists($source)) { |
webmaster@1
|
241 drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); |
webmaster@1
|
242 return 0; |
webmaster@1
|
243 } |
webmaster@1
|
244 |
webmaster@1
|
245 // If the destination file is not specified then use the filename of the source file. |
webmaster@1
|
246 $basename = $basename ? $basename : basename($source); |
webmaster@1
|
247 $dest = $directory .'/'. $basename; |
webmaster@1
|
248 |
webmaster@1
|
249 // Make sure source and destination filenames are not the same, makes no sense |
webmaster@1
|
250 // to copy it if they are. In fact copying the file will most likely result in |
webmaster@1
|
251 // a 0 byte file. Which is bad. Real bad. |
webmaster@1
|
252 if ($source != realpath($dest)) { |
webmaster@1
|
253 if (!$dest = file_destination($dest, $replace)) { |
webmaster@1
|
254 drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error'); |
webmaster@1
|
255 return FALSE; |
webmaster@1
|
256 } |
webmaster@1
|
257 |
webmaster@1
|
258 if (!@copy($source, $dest)) { |
webmaster@1
|
259 drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error'); |
webmaster@1
|
260 return 0; |
webmaster@1
|
261 } |
webmaster@1
|
262 |
webmaster@1
|
263 // Give everyone read access so that FTP'd users or |
webmaster@1
|
264 // non-webserver users can see/read these files, |
webmaster@1
|
265 // and give group write permissions so group members |
webmaster@1
|
266 // can alter files uploaded by the webserver. |
webmaster@1
|
267 @chmod($dest, 0664); |
webmaster@1
|
268 } |
webmaster@1
|
269 |
webmaster@1
|
270 if (isset($file) && is_object($file)) { |
webmaster@1
|
271 $file->filename = $basename; |
webmaster@1
|
272 $file->filepath = $dest; |
webmaster@1
|
273 $source = $file; |
webmaster@1
|
274 } |
webmaster@1
|
275 else { |
webmaster@1
|
276 $source = $dest; |
webmaster@1
|
277 } |
webmaster@1
|
278 |
webmaster@1
|
279 return 1; // Everything went ok. |
webmaster@1
|
280 } |
webmaster@1
|
281 |
webmaster@1
|
282 /** |
webmaster@1
|
283 * Determines the destination path for a file depending on how replacement of |
webmaster@1
|
284 * existing files should be handled. |
webmaster@1
|
285 * |
webmaster@1
|
286 * @param $destination A string specifying the desired path. |
webmaster@1
|
287 * @param $replace Replace behavior when the destination file already exists. |
webmaster@1
|
288 * - FILE_EXISTS_REPLACE - Replace the existing file |
webmaster@1
|
289 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is |
webmaster@1
|
290 * unique |
webmaster@1
|
291 * - FILE_EXISTS_ERROR - Do nothing and return FALSE. |
webmaster@1
|
292 * @return The destination file path or FALSE if the file already exists and |
webmaster@1
|
293 * FILE_EXISTS_ERROR was specified. |
webmaster@1
|
294 */ |
webmaster@1
|
295 function file_destination($destination, $replace) { |
webmaster@1
|
296 if (file_exists($destination)) { |
webmaster@1
|
297 switch ($replace) { |
webmaster@1
|
298 case FILE_EXISTS_RENAME: |
webmaster@1
|
299 $basename = basename($destination); |
webmaster@1
|
300 $directory = dirname($destination); |
webmaster@1
|
301 $destination = file_create_filename($basename, $directory); |
webmaster@1
|
302 break; |
webmaster@1
|
303 |
webmaster@1
|
304 case FILE_EXISTS_ERROR: |
webmaster@1
|
305 drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error'); |
webmaster@1
|
306 return FALSE; |
webmaster@1
|
307 } |
webmaster@1
|
308 } |
webmaster@1
|
309 return $destination; |
webmaster@1
|
310 } |
webmaster@1
|
311 |
webmaster@1
|
312 /** |
webmaster@1
|
313 * Moves a file to a new location. |
webmaster@1
|
314 * - Checks if $source and $dest are valid and readable/writable. |
webmaster@1
|
315 * - Performs a file move if $source is not equal to $dest. |
webmaster@1
|
316 * - If file already exists in $dest either the call will error out, replace the |
webmaster@1
|
317 * file or rename the file based on the $replace parameter. |
webmaster@1
|
318 * |
webmaster@1
|
319 * @param $source A string specifying the file location of the original file. |
webmaster@1
|
320 * This parameter will contain the resulting destination filename in case of |
webmaster@1
|
321 * success. |
webmaster@1
|
322 * @param $dest A string containing the directory $source should be copied to. |
webmaster@1
|
323 * If this value is omitted, Drupal's 'files' directory will be used. |
webmaster@1
|
324 * @param $replace Replace behavior when the destination file already exists. |
webmaster@1
|
325 * - FILE_EXISTS_REPLACE - Replace the existing file |
webmaster@1
|
326 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique |
webmaster@1
|
327 * - FILE_EXISTS_ERROR - Do nothing and return FALSE. |
webmaster@1
|
328 * @return True for success, FALSE for failure. |
webmaster@1
|
329 */ |
webmaster@1
|
330 function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { |
webmaster@1
|
331 $path_original = is_object($source) ? $source->filepath : $source; |
webmaster@1
|
332 |
webmaster@1
|
333 if (file_copy($source, $dest, $replace)) { |
webmaster@1
|
334 $path_current = is_object($source) ? $source->filepath : $source; |
webmaster@1
|
335 |
webmaster@1
|
336 if ($path_original == $path_current || file_delete($path_original)) { |
webmaster@1
|
337 return 1; |
webmaster@1
|
338 } |
webmaster@1
|
339 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error'); |
webmaster@1
|
340 } |
webmaster@1
|
341 return 0; |
webmaster@1
|
342 } |
webmaster@1
|
343 |
webmaster@1
|
344 /** |
webmaster@1
|
345 * Munge the filename as needed for security purposes. For instance the file |
webmaster@1
|
346 * name "exploit.php.pps" would become "exploit.php_.pps". |
webmaster@1
|
347 * |
webmaster@1
|
348 * @param $filename The name of a file to modify. |
webmaster@1
|
349 * @param $extensions A space separated list of extensions that should not |
webmaster@1
|
350 * be altered. |
webmaster@1
|
351 * @param $alerts Whether alerts (watchdog, drupal_set_message()) should be |
webmaster@1
|
352 * displayed. |
webmaster@1
|
353 * @return $filename The potentially modified $filename. |
webmaster@1
|
354 */ |
webmaster@1
|
355 function file_munge_filename($filename, $extensions, $alerts = TRUE) { |
webmaster@1
|
356 $original = $filename; |
webmaster@1
|
357 |
webmaster@1
|
358 // Allow potentially insecure uploads for very savvy users and admin |
webmaster@1
|
359 if (!variable_get('allow_insecure_uploads', 0)) { |
webmaster@1
|
360 $whitelist = array_unique(explode(' ', trim($extensions))); |
webmaster@1
|
361 |
webmaster@1
|
362 // Split the filename up by periods. The first part becomes the basename |
webmaster@1
|
363 // the last part the final extension. |
webmaster@1
|
364 $filename_parts = explode('.', $filename); |
webmaster@1
|
365 $new_filename = array_shift($filename_parts); // Remove file basename. |
webmaster@1
|
366 $final_extension = array_pop($filename_parts); // Remove final extension. |
webmaster@1
|
367 |
webmaster@1
|
368 // Loop through the middle parts of the name and add an underscore to the |
webmaster@1
|
369 // end of each section that could be a file extension but isn't in the list |
webmaster@1
|
370 // of allowed extensions. |
webmaster@1
|
371 foreach ($filename_parts as $filename_part) { |
webmaster@1
|
372 $new_filename .= '.'. $filename_part; |
webmaster@1
|
373 if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) { |
webmaster@1
|
374 $new_filename .= '_'; |
webmaster@1
|
375 } |
webmaster@1
|
376 } |
webmaster@1
|
377 $filename = $new_filename .'.'. $final_extension; |
webmaster@1
|
378 |
webmaster@1
|
379 if ($alerts && $original != $filename) { |
webmaster@1
|
380 drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename))); |
webmaster@1
|
381 } |
webmaster@1
|
382 } |
webmaster@1
|
383 |
webmaster@1
|
384 return $filename; |
webmaster@1
|
385 } |
webmaster@1
|
386 |
webmaster@1
|
387 /** |
webmaster@1
|
388 * Undo the effect of upload_munge_filename(). |
webmaster@1
|
389 * |
webmaster@1
|
390 * @param $filename string filename |
webmaster@1
|
391 * @return string |
webmaster@1
|
392 */ |
webmaster@1
|
393 function file_unmunge_filename($filename) { |
webmaster@1
|
394 return str_replace('_.', '.', $filename); |
webmaster@1
|
395 } |
webmaster@1
|
396 |
webmaster@1
|
397 /** |
webmaster@1
|
398 * Create a full file path from a directory and filename. If a file with the |
webmaster@1
|
399 * specified name already exists, an alternative will be used. |
webmaster@1
|
400 * |
webmaster@1
|
401 * @param $basename string filename |
webmaster@1
|
402 * @param $directory string directory |
webmaster@1
|
403 * @return |
webmaster@1
|
404 */ |
webmaster@1
|
405 function file_create_filename($basename, $directory) { |
webmaster@1
|
406 $dest = $directory .'/'. $basename; |
webmaster@1
|
407 |
webmaster@1
|
408 if (file_exists($dest)) { |
webmaster@1
|
409 // Destination file already exists, generate an alternative. |
webmaster@1
|
410 if ($pos = strrpos($basename, '.')) { |
webmaster@1
|
411 $name = substr($basename, 0, $pos); |
webmaster@1
|
412 $ext = substr($basename, $pos); |
webmaster@1
|
413 } |
webmaster@1
|
414 else { |
webmaster@1
|
415 $name = $basename; |
webmaster@1
|
416 } |
webmaster@1
|
417 |
webmaster@1
|
418 $counter = 0; |
webmaster@1
|
419 do { |
webmaster@1
|
420 $dest = $directory .'/'. $name .'_'. $counter++ . $ext; |
webmaster@1
|
421 } while (file_exists($dest)); |
webmaster@1
|
422 } |
webmaster@1
|
423 |
webmaster@1
|
424 return $dest; |
webmaster@1
|
425 } |
webmaster@1
|
426 |
webmaster@1
|
427 /** |
webmaster@1
|
428 * Delete a file. |
webmaster@1
|
429 * |
webmaster@1
|
430 * @param $path A string containing a file path. |
webmaster@1
|
431 * @return TRUE for success, FALSE for failure. |
webmaster@1
|
432 */ |
webmaster@1
|
433 function file_delete($path) { |
webmaster@1
|
434 if (is_file($path)) { |
webmaster@1
|
435 return unlink($path); |
webmaster@1
|
436 } |
webmaster@1
|
437 } |
webmaster@1
|
438 |
webmaster@1
|
439 /** |
webmaster@1
|
440 * Determine total disk space used by a single user or the whole filesystem. |
webmaster@1
|
441 * |
webmaster@1
|
442 * @param $uid |
webmaster@1
|
443 * An optional user id. A NULL value returns the total space used |
webmaster@1
|
444 * by all files. |
webmaster@1
|
445 */ |
webmaster@1
|
446 function file_space_used($uid = NULL) { |
webmaster@1
|
447 if (isset($uid)) { |
webmaster@1
|
448 return (int) db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid)); |
webmaster@1
|
449 } |
webmaster@1
|
450 return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}')); |
webmaster@1
|
451 } |
webmaster@1
|
452 |
webmaster@1
|
453 /** |
webmaster@1
|
454 * Saves a file upload to a new location. The source file is validated as a |
webmaster@1
|
455 * proper upload and handled as such. |
webmaster@1
|
456 * |
webmaster@1
|
457 * The file will be added to the files table as a temporary file. Temporary files |
webmaster@1
|
458 * are periodically cleaned. To make the file permanent file call |
webmaster@1
|
459 * file_set_status() to change its status. |
webmaster@1
|
460 * |
webmaster@1
|
461 * @param $source |
webmaster@1
|
462 * A string specifying the name of the upload field to save. |
webmaster@1
|
463 * @param $validators |
webmaster@1
|
464 * An optional, associative array of callback functions used to validate the |
webmaster@1
|
465 * file. The keys are function names and the values arrays of callback |
webmaster@1
|
466 * parameters which will be passed in after the user and file objects. The |
webmaster@1
|
467 * functions should return an array of error messages, an empty array |
webmaster@1
|
468 * indicates that the file passed validation. The functions will be called in |
webmaster@1
|
469 * the order specified. |
webmaster@1
|
470 * @param $dest |
webmaster@1
|
471 * A string containing the directory $source should be copied to. If this is |
webmaster@1
|
472 * not provided or is not writable, the temporary directory will be used. |
webmaster@1
|
473 * @param $replace |
webmaster@1
|
474 * A boolean indicating whether an existing file of the same name in the |
webmaster@1
|
475 * destination directory should overwritten. A false value will generate a |
webmaster@1
|
476 * new, unique filename in the destination directory. |
webmaster@1
|
477 * @return |
webmaster@1
|
478 * An object containing the file information, or 0 in the event of an error. |
webmaster@1
|
479 */ |
webmaster@1
|
480 function file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME) { |
webmaster@1
|
481 global $user; |
webmaster@1
|
482 static $upload_cache; |
webmaster@1
|
483 |
webmaster@1
|
484 // Add in our check of the the file name length. |
webmaster@1
|
485 $validators['file_validate_name_length'] = array(); |
webmaster@1
|
486 |
webmaster@1
|
487 // Return cached objects without processing since the file will have |
webmaster@1
|
488 // already been processed and the paths in _FILES will be invalid. |
webmaster@1
|
489 if (isset($upload_cache[$source])) { |
webmaster@1
|
490 return $upload_cache[$source]; |
webmaster@1
|
491 } |
webmaster@1
|
492 |
webmaster@1
|
493 // If a file was uploaded, process it. |
webmaster@1
|
494 if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source])) { |
webmaster@1
|
495 // Check for file upload errors and return FALSE if a |
webmaster@1
|
496 // lower level system error occurred. |
webmaster@1
|
497 switch ($_FILES['files']['error'][$source]) { |
webmaster@1
|
498 // @see http://php.net/manual/en/features.file-upload.errors.php |
webmaster@1
|
499 case UPLOAD_ERR_OK: |
webmaster@1
|
500 break; |
webmaster@1
|
501 |
webmaster@1
|
502 case UPLOAD_ERR_INI_SIZE: |
webmaster@1
|
503 case UPLOAD_ERR_FORM_SIZE: |
webmaster@1
|
504 drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(file_upload_max_size()))), 'error'); |
webmaster@1
|
505 return 0; |
webmaster@1
|
506 |
webmaster@1
|
507 case UPLOAD_ERR_PARTIAL: |
webmaster@1
|
508 case UPLOAD_ERR_NO_FILE: |
webmaster@1
|
509 drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error'); |
webmaster@1
|
510 return 0; |
webmaster@1
|
511 |
webmaster@1
|
512 // Unknown error |
webmaster@1
|
513 default: |
webmaster@1
|
514 drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error'); |
webmaster@1
|
515 return 0; |
webmaster@1
|
516 } |
webmaster@1
|
517 |
webmaster@1
|
518 // Build the list of non-munged extensions. |
webmaster@1
|
519 // @todo: this should not be here. we need to figure out the right place. |
webmaster@1
|
520 $extensions = ''; |
webmaster@1
|
521 foreach ($user->roles as $rid => $name) { |
webmaster@1
|
522 $extensions .= ' '. variable_get("upload_extensions_$rid", |
webmaster@1
|
523 variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp')); |
webmaster@1
|
524 } |
webmaster@1
|
525 |
webmaster@1
|
526 // Begin building file object. |
webmaster@1
|
527 $file = new stdClass(); |
webmaster@1
|
528 $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions); |
webmaster@1
|
529 $file->filepath = $_FILES['files']['tmp_name'][$source]; |
webmaster@1
|
530 $file->filemime = $_FILES['files']['type'][$source]; |
webmaster@1
|
531 |
webmaster@1
|
532 // Rename potentially executable files, to help prevent exploits. |
webmaster@1
|
533 if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { |
webmaster@1
|
534 $file->filemime = 'text/plain'; |
webmaster@1
|
535 $file->filepath .= '.txt'; |
webmaster@1
|
536 $file->filename .= '.txt'; |
webmaster@1
|
537 } |
webmaster@1
|
538 |
webmaster@1
|
539 // If the destination is not provided, or is not writable, then use the |
webmaster@1
|
540 // temporary directory. |
webmaster@1
|
541 if (empty($dest) || file_check_path($dest) === FALSE) { |
webmaster@1
|
542 $dest = file_directory_temp(); |
webmaster@1
|
543 } |
webmaster@1
|
544 |
webmaster@1
|
545 $file->source = $source; |
webmaster@1
|
546 $file->destination = file_destination(file_create_path($dest .'/'. $file->filename), $replace); |
webmaster@1
|
547 $file->filesize = $_FILES['files']['size'][$source]; |
webmaster@1
|
548 |
webmaster@1
|
549 // Call the validation functions. |
webmaster@1
|
550 $errors = array(); |
webmaster@1
|
551 foreach ($validators as $function => $args) { |
webmaster@1
|
552 array_unshift($args, $file); |
webmaster@1
|
553 $errors = array_merge($errors, call_user_func_array($function, $args)); |
webmaster@1
|
554 } |
webmaster@1
|
555 |
webmaster@1
|
556 // Check for validation errors. |
webmaster@1
|
557 if (!empty($errors)) { |
webmaster@1
|
558 $message = t('The selected file %name could not be uploaded.', array('%name' => $file->filename)); |
webmaster@1
|
559 if (count($errors) > 1) { |
webmaster@1
|
560 $message .= '<ul><li>'. implode('</li><li>', $errors) .'</li></ul>'; |
webmaster@1
|
561 } |
webmaster@1
|
562 else { |
webmaster@1
|
563 $message .= ' '. array_pop($errors); |
webmaster@1
|
564 } |
webmaster@1
|
565 form_set_error($source, $message); |
webmaster@1
|
566 return 0; |
webmaster@1
|
567 } |
webmaster@1
|
568 |
webmaster@1
|
569 // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory. |
webmaster@1
|
570 // This overcomes open_basedir restrictions for future file operations. |
webmaster@1
|
571 $file->filepath = $file->destination; |
webmaster@1
|
572 if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) { |
webmaster@1
|
573 form_set_error($source, t('File upload error. Could not move uploaded file.')); |
webmaster@1
|
574 watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->filepath)); |
webmaster@1
|
575 return 0; |
webmaster@1
|
576 } |
webmaster@1
|
577 |
webmaster@1
|
578 // If we made it this far it's safe to record this file in the database. |
webmaster@1
|
579 $file->uid = $user->uid; |
webmaster@1
|
580 $file->status = FILE_STATUS_TEMPORARY; |
webmaster@1
|
581 $file->timestamp = time(); |
webmaster@1
|
582 drupal_write_record('files', $file); |
webmaster@1
|
583 |
webmaster@1
|
584 // Add file to the cache. |
webmaster@1
|
585 $upload_cache[$source] = $file; |
webmaster@1
|
586 return $file; |
webmaster@1
|
587 } |
webmaster@1
|
588 return 0; |
webmaster@1
|
589 } |
webmaster@1
|
590 |
webmaster@1
|
591 /** |
webmaster@1
|
592 * Check for files with names longer than we can store in the database. |
webmaster@1
|
593 * |
webmaster@1
|
594 * @param $file |
webmaster@1
|
595 * A Drupal file object. |
webmaster@1
|
596 * @return |
webmaster@1
|
597 * An array. If the file name is too long, it will contain an error message. |
webmaster@1
|
598 */ |
webmaster@1
|
599 function file_validate_name_length($file) { |
webmaster@1
|
600 $errors = array(); |
webmaster@1
|
601 |
webmaster@1
|
602 if (strlen($file->filename) > 255) { |
webmaster@1
|
603 $errors[] = t('Its name exceeds the 255 characters limit. Please rename the file and try again.'); |
webmaster@1
|
604 } |
webmaster@1
|
605 return $errors; |
webmaster@1
|
606 } |
webmaster@1
|
607 |
webmaster@1
|
608 /** |
webmaster@1
|
609 * Check that the filename ends with an allowed extension. This check is not |
webmaster@1
|
610 * enforced for the user #1. |
webmaster@1
|
611 * |
webmaster@1
|
612 * @param $file |
webmaster@1
|
613 * A Drupal file object. |
webmaster@1
|
614 * @param $extensions |
webmaster@1
|
615 * A string with a space separated |
webmaster@1
|
616 * @return |
webmaster@1
|
617 * An array. If the file extension is not allowed, it will contain an error message. |
webmaster@1
|
618 */ |
webmaster@1
|
619 function file_validate_extensions($file, $extensions) { |
webmaster@1
|
620 global $user; |
webmaster@1
|
621 |
webmaster@1
|
622 $errors = array(); |
webmaster@1
|
623 |
webmaster@1
|
624 // Bypass validation for uid = 1. |
webmaster@1
|
625 if ($user->uid != 1) { |
webmaster@1
|
626 $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i'; |
webmaster@1
|
627 if (!preg_match($regex, $file->filename)) { |
webmaster@1
|
628 $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)); |
webmaster@1
|
629 } |
webmaster@1
|
630 } |
webmaster@1
|
631 return $errors; |
webmaster@1
|
632 } |
webmaster@1
|
633 |
webmaster@1
|
634 /** |
webmaster@1
|
635 * Check that the file's size is below certain limits. This check is not |
webmaster@1
|
636 * enforced for the user #1. |
webmaster@1
|
637 * |
webmaster@1
|
638 * @param $file |
webmaster@1
|
639 * A Drupal file object. |
webmaster@1
|
640 * @param $file_limit |
webmaster@1
|
641 * An integer specifying the maximum file size in bytes. Zero indicates that |
webmaster@1
|
642 * no limit should be enforced. |
webmaster@1
|
643 * @param $$user_limit |
webmaster@1
|
644 * An integer specifying the maximum number of bytes the user is allowed. Zero |
webmaster@1
|
645 * indicates that no limit should be enforced. |
webmaster@1
|
646 * @return |
webmaster@1
|
647 * An array. If the file size exceeds limits, it will contain an error message. |
webmaster@1
|
648 */ |
webmaster@1
|
649 function file_validate_size($file, $file_limit = 0, $user_limit = 0) { |
webmaster@1
|
650 global $user; |
webmaster@1
|
651 |
webmaster@1
|
652 $errors = array(); |
webmaster@1
|
653 |
webmaster@1
|
654 // Bypass validation for uid = 1. |
webmaster@1
|
655 if ($user->uid != 1) { |
webmaster@1
|
656 if ($file_limit && $file->filesize > $file_limit) { |
webmaster@1
|
657 $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit))); |
webmaster@1
|
658 } |
webmaster@1
|
659 |
webmaster@1
|
660 $total_size = file_space_used($user->uid) + $file->filesize; |
webmaster@1
|
661 if ($user_limit && $total_size > $user_limit) { |
webmaster@1
|
662 $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit))); |
webmaster@1
|
663 } |
webmaster@1
|
664 } |
webmaster@1
|
665 return $errors; |
webmaster@1
|
666 } |
webmaster@1
|
667 |
webmaster@1
|
668 /** |
webmaster@1
|
669 * Check that the file is recognized by image_get_info() as an image. |
webmaster@1
|
670 * |
webmaster@1
|
671 * @param $file |
webmaster@1
|
672 * A Drupal file object. |
webmaster@1
|
673 * @return |
webmaster@1
|
674 * An array. If the file is not an image, it will contain an error message. |
webmaster@1
|
675 */ |
webmaster@1
|
676 function file_validate_is_image(&$file) { |
webmaster@1
|
677 $errors = array(); |
webmaster@1
|
678 |
webmaster@1
|
679 $info = image_get_info($file->filepath); |
webmaster@1
|
680 if (!$info || empty($info['extension'])) { |
webmaster@1
|
681 $errors[] = t('Only JPEG, PNG and GIF images are allowed.'); |
webmaster@1
|
682 } |
webmaster@1
|
683 |
webmaster@1
|
684 return $errors; |
webmaster@1
|
685 } |
webmaster@1
|
686 |
webmaster@1
|
687 /** |
webmaster@1
|
688 * If the file is an image verify that its dimensions are within the specified |
webmaster@1
|
689 * maximum and minimum dimensions. Non-image files will be ignored. |
webmaster@1
|
690 * |
webmaster@1
|
691 * @param $file |
webmaster@1
|
692 * A Drupal file object. This function may resize the file affecting its size. |
webmaster@1
|
693 * @param $maximum_dimensions |
webmaster@1
|
694 * An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If |
webmaster@1
|
695 * an image toolkit is installed the image will be resized down to these |
webmaster@1
|
696 * dimensions. A value of 0 indicates no restriction on size, so resizing |
webmaster@1
|
697 * will be attempted. |
webmaster@1
|
698 * @param $minimum_dimensions |
webmaster@1
|
699 * An optional string in the form WIDTHxHEIGHT. This will check that the image |
webmaster@1
|
700 * meets a minimum size. A value of 0 indicates no restriction. |
webmaster@1
|
701 * @return |
webmaster@1
|
702 * An array. If the file is an image and did not meet the requirements, it |
webmaster@1
|
703 * will contain an error message. |
webmaster@1
|
704 */ |
webmaster@1
|
705 function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimum_dimensions = 0) { |
webmaster@1
|
706 $errors = array(); |
webmaster@1
|
707 |
webmaster@1
|
708 // Check first that the file is an image. |
webmaster@1
|
709 if ($info = image_get_info($file->filepath)) { |
webmaster@1
|
710 if ($maximum_dimensions) { |
webmaster@1
|
711 // Check that it is smaller than the given dimensions. |
webmaster@1
|
712 list($width, $height) = explode('x', $maximum_dimensions); |
webmaster@1
|
713 if ($info['width'] > $width || $info['height'] > $height) { |
webmaster@1
|
714 // Try to resize the image to fit the dimensions. |
webmaster@1
|
715 if (image_get_toolkit() && image_scale($file->filepath, $file->filepath, $width, $height)) { |
webmaster@1
|
716 drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions))); |
webmaster@1
|
717 |
webmaster@1
|
718 // Clear the cached filesize and refresh the image information. |
webmaster@1
|
719 clearstatcache(); |
webmaster@1
|
720 $info = image_get_info($file->filepath); |
webmaster@1
|
721 $file->filesize = $info['file_size']; |
webmaster@1
|
722 } |
webmaster@1
|
723 else { |
webmaster@1
|
724 $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions)); |
webmaster@1
|
725 } |
webmaster@1
|
726 } |
webmaster@1
|
727 } |
webmaster@1
|
728 |
webmaster@1
|
729 if ($minimum_dimensions) { |
webmaster@1
|
730 // Check that it is larger than the given dimensions. |
webmaster@1
|
731 list($width, $height) = explode('x', $minimum_dimensions); |
webmaster@1
|
732 if ($info['width'] < $width || $info['height'] < $height) { |
webmaster@1
|
733 $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions)); |
webmaster@1
|
734 } |
webmaster@1
|
735 } |
webmaster@1
|
736 } |
webmaster@1
|
737 |
webmaster@1
|
738 return $errors; |
webmaster@1
|
739 } |
webmaster@1
|
740 |
webmaster@1
|
741 /** |
webmaster@1
|
742 * Save a string to the specified destination. |
webmaster@1
|
743 * |
webmaster@1
|
744 * @param $data A string containing the contents of the file. |
webmaster@1
|
745 * @param $dest A string containing the destination location. |
webmaster@1
|
746 * @param $replace Replace behavior when the destination file already exists. |
webmaster@1
|
747 * - FILE_EXISTS_REPLACE - Replace the existing file |
webmaster@1
|
748 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique |
webmaster@1
|
749 * - FILE_EXISTS_ERROR - Do nothing and return FALSE. |
webmaster@1
|
750 * |
webmaster@1
|
751 * @return A string containing the resulting filename or 0 on error |
webmaster@1
|
752 */ |
webmaster@1
|
753 function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) { |
webmaster@1
|
754 $temp = file_directory_temp(); |
webmaster@1
|
755 // On Windows, tempnam() requires an absolute path, so we use realpath(). |
webmaster@1
|
756 $file = tempnam(realpath($temp), 'file'); |
webmaster@1
|
757 if (!$fp = fopen($file, 'wb')) { |
webmaster@1
|
758 drupal_set_message(t('The file could not be created.'), 'error'); |
webmaster@1
|
759 return 0; |
webmaster@1
|
760 } |
webmaster@1
|
761 fwrite($fp, $data); |
webmaster@1
|
762 fclose($fp); |
webmaster@1
|
763 |
webmaster@1
|
764 if (!file_move($file, $dest, $replace)) { |
webmaster@1
|
765 return 0; |
webmaster@1
|
766 } |
webmaster@1
|
767 |
webmaster@1
|
768 return $file; |
webmaster@1
|
769 } |
webmaster@1
|
770 |
webmaster@1
|
771 /** |
webmaster@1
|
772 * Set the status of a file. |
webmaster@1
|
773 * |
webmaster@1
|
774 * @param file A Drupal file object |
webmaster@1
|
775 * @param status A status value to set the file to. |
webmaster@1
|
776 * @return FALSE on failure, TRUE on success and $file->status will contain the |
webmaster@1
|
777 * status. |
webmaster@1
|
778 */ |
webmaster@1
|
779 function file_set_status(&$file, $status) { |
webmaster@1
|
780 if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) { |
webmaster@1
|
781 $file->status = $status; |
webmaster@1
|
782 return TRUE; |
webmaster@1
|
783 } |
webmaster@1
|
784 return FALSE; |
webmaster@1
|
785 } |
webmaster@1
|
786 |
webmaster@1
|
787 /** |
webmaster@1
|
788 * Transfer file using http to client. Pipes a file through Drupal to the |
webmaster@1
|
789 * client. |
webmaster@1
|
790 * |
webmaster@1
|
791 * @param $source File to transfer. |
webmaster@1
|
792 * @param $headers An array of http headers to send along with file. |
webmaster@1
|
793 */ |
webmaster@1
|
794 function file_transfer($source, $headers) { |
webmaster@1
|
795 ob_end_clean(); |
webmaster@1
|
796 |
webmaster@1
|
797 foreach ($headers as $header) { |
webmaster@1
|
798 // To prevent HTTP header injection, we delete new lines that are |
webmaster@1
|
799 // not followed by a space or a tab. |
webmaster@1
|
800 // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 |
webmaster@1
|
801 $header = preg_replace('/\r?\n(?!\t| )/', '', $header); |
webmaster@1
|
802 drupal_set_header($header); |
webmaster@1
|
803 } |
webmaster@1
|
804 |
webmaster@1
|
805 $source = file_create_path($source); |
webmaster@1
|
806 |
webmaster@1
|
807 // Transfer file in 1024 byte chunks to save memory usage. |
webmaster@1
|
808 if ($fd = fopen($source, 'rb')) { |
webmaster@1
|
809 while (!feof($fd)) { |
webmaster@1
|
810 print fread($fd, 1024); |
webmaster@1
|
811 } |
webmaster@1
|
812 fclose($fd); |
webmaster@1
|
813 } |
webmaster@1
|
814 else { |
webmaster@1
|
815 drupal_not_found(); |
webmaster@1
|
816 } |
webmaster@1
|
817 exit(); |
webmaster@1
|
818 } |
webmaster@1
|
819 |
webmaster@1
|
820 /** |
webmaster@1
|
821 * Call modules that implement hook_file_download() to find out if a file is |
webmaster@1
|
822 * accessible and what headers it should be transferred with. If a module |
webmaster@1
|
823 * returns -1 drupal_access_denied() will be returned. If one or more modules |
webmaster@1
|
824 * returned headers the download will start with the returned headers. If no |
webmaster@1
|
825 * modules respond drupal_not_found() will be returned. |
webmaster@1
|
826 */ |
webmaster@1
|
827 function file_download() { |
webmaster@1
|
828 // Merge remainder of arguments from GET['q'], into relative file path. |
webmaster@1
|
829 $args = func_get_args(); |
webmaster@1
|
830 $filepath = implode('/', $args); |
webmaster@1
|
831 |
webmaster@1
|
832 // Maintain compatibility with old ?file=paths saved in node bodies. |
webmaster@1
|
833 if (isset($_GET['file'])) { |
webmaster@1
|
834 $filepath = $_GET['file']; |
webmaster@1
|
835 } |
webmaster@1
|
836 |
webmaster@1
|
837 if (file_exists(file_create_path($filepath))) { |
webmaster@1
|
838 $headers = module_invoke_all('file_download', $filepath); |
webmaster@1
|
839 if (in_array(-1, $headers)) { |
webmaster@1
|
840 return drupal_access_denied(); |
webmaster@1
|
841 } |
webmaster@1
|
842 if (count($headers)) { |
webmaster@1
|
843 file_transfer($filepath, $headers); |
webmaster@1
|
844 } |
webmaster@1
|
845 } |
webmaster@1
|
846 return drupal_not_found(); |
webmaster@1
|
847 } |
webmaster@1
|
848 |
webmaster@1
|
849 |
webmaster@1
|
850 /** |
webmaster@1
|
851 * Finds all files that match a given mask in a given directory. |
webmaster@1
|
852 * Directories and files beginning with a period are excluded; this |
webmaster@1
|
853 * prevents hidden files and directories (such as SVN working directories) |
webmaster@1
|
854 * from being scanned. |
webmaster@1
|
855 * |
webmaster@1
|
856 * @param $dir |
webmaster@1
|
857 * The base directory for the scan, without trailing slash. |
webmaster@1
|
858 * @param $mask |
webmaster@1
|
859 * The regular expression of the files to find. |
webmaster@1
|
860 * @param $nomask |
webmaster@1
|
861 * An array of files/directories to ignore. |
webmaster@1
|
862 * @param $callback |
webmaster@1
|
863 * The callback function to call for each match. |
webmaster@1
|
864 * @param $recurse |
webmaster@1
|
865 * When TRUE, the directory scan will recurse the entire tree |
webmaster@1
|
866 * starting at the provided directory. |
webmaster@1
|
867 * @param $key |
webmaster@1
|
868 * The key to be used for the returned array of files. Possible |
webmaster@1
|
869 * values are "filename", for the path starting with $dir, |
webmaster@1
|
870 * "basename", for the basename of the file, and "name" for the name |
webmaster@1
|
871 * of the file without an extension. |
webmaster@1
|
872 * @param $min_depth |
webmaster@1
|
873 * Minimum depth of directories to return files from. |
webmaster@1
|
874 * @param $depth |
webmaster@1
|
875 * Current depth of recursion. This parameter is only used internally and should not be passed. |
webmaster@1
|
876 * |
webmaster@1
|
877 * @return |
webmaster@1
|
878 * An associative array (keyed on the provided key) of objects with |
webmaster@1
|
879 * "path", "basename", and "name" members corresponding to the |
webmaster@1
|
880 * matching files. |
webmaster@1
|
881 */ |
webmaster@1
|
882 function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) { |
webmaster@1
|
883 $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename'); |
webmaster@1
|
884 $files = array(); |
webmaster@1
|
885 |
webmaster@1
|
886 if (is_dir($dir) && $handle = opendir($dir)) { |
webmaster@1
|
887 while ($file = readdir($handle)) { |
webmaster@1
|
888 if (!in_array($file, $nomask) && $file[0] != '.') { |
webmaster@1
|
889 if (is_dir("$dir/$file") && $recurse) { |
webmaster@1
|
890 // Give priority to files in this folder by merging them in after any subdirectory files. |
webmaster@1
|
891 $files = array_merge(file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files); |
webmaster@1
|
892 } |
webmaster@1
|
893 elseif ($depth >= $min_depth && ereg($mask, $file)) { |
webmaster@1
|
894 // Always use this match over anything already set in $files with the same $$key. |
webmaster@1
|
895 $filename = "$dir/$file"; |
webmaster@1
|
896 $basename = basename($file); |
webmaster@1
|
897 $name = substr($basename, 0, strrpos($basename, '.')); |
webmaster@1
|
898 $files[$$key] = new stdClass(); |
webmaster@1
|
899 $files[$$key]->filename = $filename; |
webmaster@1
|
900 $files[$$key]->basename = $basename; |
webmaster@1
|
901 $files[$$key]->name = $name; |
webmaster@1
|
902 if ($callback) { |
webmaster@1
|
903 $callback($filename); |
webmaster@1
|
904 } |
webmaster@1
|
905 } |
webmaster@1
|
906 } |
webmaster@1
|
907 } |
webmaster@1
|
908 |
webmaster@1
|
909 closedir($handle); |
webmaster@1
|
910 } |
webmaster@1
|
911 |
webmaster@1
|
912 return $files; |
webmaster@1
|
913 } |
webmaster@1
|
914 |
webmaster@1
|
915 /** |
webmaster@1
|
916 * Determine the default temporary directory. |
webmaster@1
|
917 * |
webmaster@1
|
918 * @return A string containing a temp directory. |
webmaster@1
|
919 */ |
webmaster@1
|
920 function file_directory_temp() { |
webmaster@1
|
921 $temporary_directory = variable_get('file_directory_temp', NULL); |
webmaster@1
|
922 |
webmaster@1
|
923 if (is_null($temporary_directory)) { |
webmaster@1
|
924 $directories = array(); |
webmaster@1
|
925 |
webmaster@1
|
926 // Has PHP been set with an upload_tmp_dir? |
webmaster@1
|
927 if (ini_get('upload_tmp_dir')) { |
webmaster@1
|
928 $directories[] = ini_get('upload_tmp_dir'); |
webmaster@1
|
929 } |
webmaster@1
|
930 |
webmaster@1
|
931 // Operating system specific dirs. |
webmaster@1
|
932 if (substr(PHP_OS, 0, 3) == 'WIN') { |
webmaster@1
|
933 $directories[] = 'c:\\windows\\temp'; |
webmaster@1
|
934 $directories[] = 'c:\\winnt\\temp'; |
webmaster@1
|
935 $path_delimiter = '\\'; |
webmaster@1
|
936 } |
webmaster@1
|
937 else { |
webmaster@1
|
938 $directories[] = '/tmp'; |
webmaster@1
|
939 $path_delimiter = '/'; |
webmaster@1
|
940 } |
webmaster@1
|
941 |
webmaster@1
|
942 foreach ($directories as $directory) { |
webmaster@1
|
943 if (!$temporary_directory && is_dir($directory)) { |
webmaster@1
|
944 $temporary_directory = $directory; |
webmaster@1
|
945 } |
webmaster@1
|
946 } |
webmaster@1
|
947 |
webmaster@1
|
948 // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp'; |
webmaster@1
|
949 $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter .'tmp'; |
webmaster@1
|
950 variable_set('file_directory_temp', $temporary_directory); |
webmaster@1
|
951 } |
webmaster@1
|
952 |
webmaster@1
|
953 return $temporary_directory; |
webmaster@1
|
954 } |
webmaster@1
|
955 |
webmaster@1
|
956 /** |
webmaster@1
|
957 * Determine the default 'files' directory. |
webmaster@1
|
958 * |
webmaster@1
|
959 * @return A string containing the path to Drupal's 'files' directory. |
webmaster@1
|
960 */ |
webmaster@1
|
961 function file_directory_path() { |
webmaster@1
|
962 return variable_get('file_directory_path', conf_path() .'/files'); |
webmaster@1
|
963 } |
webmaster@1
|
964 |
webmaster@1
|
965 /** |
webmaster@1
|
966 * Determine the maximum file upload size by querying the PHP settings. |
webmaster@1
|
967 * |
webmaster@1
|
968 * @return |
webmaster@1
|
969 * A file size limit in bytes based on the PHP upload_max_filesize and post_max_size |
webmaster@1
|
970 */ |
webmaster@1
|
971 function file_upload_max_size() { |
webmaster@1
|
972 static $max_size = -1; |
webmaster@1
|
973 |
webmaster@1
|
974 if ($max_size < 0) { |
webmaster@1
|
975 $upload_max = parse_size(ini_get('upload_max_filesize')); |
webmaster@1
|
976 $post_max = parse_size(ini_get('post_max_size')); |
webmaster@1
|
977 $max_size = ($upload_max < $post_max) ? $upload_max : $post_max; |
webmaster@1
|
978 } |
webmaster@1
|
979 return $max_size; |
webmaster@1
|
980 } |
webmaster@1
|
981 |
webmaster@1
|
982 /** |
webmaster@1
|
983 * @} End of "defgroup file". |
webmaster@1
|
984 */ |