Imported drupal-5.3
[drupal.git] / includes / file.inc
blobb7fe12012945478acf517882024626518dfe0b3c
1 <?php
2 // $Id: file.inc,v 1.90.2.1 2007/05/31 05:48:58 drumm Exp $
4 /**
5  * @file
6  * API for handling file uploads and server file management.
7  */
9 /**
10  * @defgroup file File interface
11  * @{
12  * Common file handling functions.
13  */
15 define('FILE_DOWNLOADS_PUBLIC', 1);
16 define('FILE_DOWNLOADS_PRIVATE', 2);
17 define('FILE_CREATE_DIRECTORY', 1);
18 define('FILE_MODIFY_PERMISSIONS', 2);
19 define('FILE_EXISTS_RENAME', 0);
20 define('FILE_EXISTS_REPLACE', 1);
21 define('FILE_EXISTS_ERROR', 2);
23 /**
24  * Create the download path to a file.
25  *
26  * @param $path A string containing the path of the file to generate URL for.
27  * @return A string containing a URL that can be used to download the file.
28  */
29 function file_create_url($path) {
30   // Strip file_directory_path from $path. We only include relative paths in urls.
31   if (strpos($path, file_directory_path() . '/') === 0) {
32     $path = trim(substr($path, strlen(file_directory_path())), '\\/');
33   }
34   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
35     case FILE_DOWNLOADS_PUBLIC:
36       return $GLOBALS['base_url'] .'/'. file_directory_path() .'/'. str_replace('\\', '/', $path);
37     case FILE_DOWNLOADS_PRIVATE:
38       return url('system/files/'. $path, NULL, NULL, TRUE);
39   }
42 /**
43  * Make sure the destination is a complete path and resides in the file system
44  * directory, if it is not prepend the file system directory.
45  *
46  * @param $dest A string containing the path to verify. If this value is
47  *   omitted, Drupal's 'files' directory will be used.
48  * @return A string containing the path to file, with file system directory
49  *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
50  *   configured 'files' or temp directories).
51  */
52 function file_create_path($dest = 0) {
53   $file_path = file_directory_path();
54   if (!$dest) {
55     return $file_path;
56   }
57   // file_check_location() checks whether the destination is inside the Drupal files directory.
58   if (file_check_location($dest, $file_path)) {
59     return $dest;
60   }
61   // check if the destination is instead inside the Drupal temporary files directory.
62   else if (file_check_location($dest, file_directory_temp())) {
63     return $dest;
64   }
65   // Not found, try again with prefixed directory path.
66   else if (file_check_location($file_path . '/' . $dest, $file_path)) {
67     return $file_path . '/' . $dest;
68   }
69   // File not found.
70   return FALSE;
73 /**
74  * Check that the directory exists and is writable. Directories need to
75  * have execute permissions to be considered a directory by FTP servers, etc.
76  *
77  * @param $directory A string containing the name of a directory path.
78  * @param $mode A Boolean value to indicate if the directory should be created
79  *   if it does not exist or made writable if it is read-only.
80  * @param $form_item An optional string containing the name of a form item that
81  *   any errors will be attached to. This is useful for settings forms that
82  *   require the user to specify a writable directory. If it can't be made to
83  *   work, a form error will be set preventing them from saving the settings.
84  * @return FALSE when directory not found, or TRUE when directory exists.
85  */
86 function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
87   $directory = rtrim($directory, '/\\');
89   // Check if directory exists.
90   if (!is_dir($directory)) {
91     if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
92       drupal_set_message(t('The directory %directory has been created.', array('%directory' => $directory)));
93       @chmod($directory, 0775); // Necessary for non-webserver users.
94     }
95     else {
96       if ($form_item) {
97         form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
98       }
99       return FALSE;
100     }
101   }
103   // Check to see if the directory is writable.
104   if (!is_writable($directory)) {
105     if (($mode & FILE_MODIFY_PERMISSIONS) && @chmod($directory, 0775)) {
106       drupal_set_message(t('The permissions of directory %directory have been changed to make it writable.', array('%directory' => $directory)));
107     }
108     else {
109       form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
110       watchdog('file system', t('The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory)), WATCHDOG_ERROR);
111       return FALSE;
112     }
113   }
115   if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
116     $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
117     if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
118       fclose($fp);
119       chmod($directory .'/.htaccess', 0664);
120     }
121     else {
122       $message = 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>", array('%directory' => $directory, '!htaccess' => '<br />'. nl2br(check_plain($htaccess_lines))));
123       form_set_error($form_item, $message);
124       watchdog('security', $message, WATCHDOG_ERROR);
125     }
126   }
128   return TRUE;
132  * Checks path to see if it is a directory, or a dir/file.
134  * @param $path A string containing a file path. This will be set to the
135  *   directory's path.
136  * @return If the directory is not in a Drupal writable directory, FALSE is
137  *   returned. Otherwise, the base name of the path is returned.
138  */
139 function file_check_path(&$path) {
140   // Check if path is a directory.
141   if (file_check_directory($path)) {
142     return '';
143   }
145   // Check if path is a possible dir/file.
146   $filename = basename($path);
147   $path = dirname($path);
148   if (file_check_directory($path)) {
149     return $filename;
150   }
152   return FALSE;
157  * Check if $source is a valid file upload. If so, move the file to Drupal's tmp dir
158  * and return it as an object.
160  * The use of SESSION['file_uploads'] should probably be externalized to upload.module
162  * @todo Rename file_check_upload to file_prepare upload.
163  * @todo Refactor or merge file_save_upload.
164  * @todo Extenalize SESSION['file_uploads'] to modules.
166  * @param $source An upload source (the name of the upload form item), or a file
167  * @return FALSE for an invalid file or upload. A file object for valid uploads/files.
169  */
171 function file_check_upload($source = 'upload') {
172   // Cache for uploaded files. Since the data in _FILES is modified
173   // by this function, we cache the result.
174   static $upload_cache;
176   // Test source to see if it is an object.
177   if (is_object($source)) {
179     // Validate the file path if an object was passed in instead of
180     // an upload key.
181     if (is_file($source->filepath)) {
182       return $source;
183     }
184     else {
185       return FALSE;
186     }
187   }
189   // Return cached objects without processing since the file will have
190   // already been processed and the paths in _FILES will be invalid.
191   if (isset($upload_cache[$source])) {
192     return $upload_cache[$source];
193   }
195   // If a file was uploaded, process it.
196   if ($_FILES["files"]["name"][$source] && is_uploaded_file($_FILES["files"]["tmp_name"][$source])) {
198     // Check for file upload errors and return FALSE if a
199     // lower level system error occurred.
200     switch ($_FILES["files"]["error"][$source]) {
202       // @see http://php.net/manual/en/features.file-upload.errors.php
203       case UPLOAD_ERR_OK:
204         break;
206       case UPLOAD_ERR_INI_SIZE:
207       case UPLOAD_ERR_FORM_SIZE:
208         drupal_set_message(t('The file %file could not be saved, because it exceeds the maximum allowed size for uploads.', array('%file' => $source)), 'error');
209         return 0;
211       case UPLOAD_ERR_PARTIAL:
212       case UPLOAD_ERR_NO_FILE:
213         drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
214         return 0;
216       // Unknown error
217       default:
218         drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)),'error');
219         return 0;
220     }
222     // Begin building file object.
223     $file = new stdClass();
224     $file->filename = trim(basename($_FILES["files"]["name"][$source]), '.');
226     // Create temporary name/path for newly uploaded files.
227     $file->filepath = tempnam(file_directory_temp(), 'tmp_');
229     $file->filemime = $_FILES["files"]["type"][$source];
231     // Rename potentially executable files, to help prevent exploits.
232     if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
233       $file->filemime = 'text/plain';
234       $file->filepath .= '.txt';
235       $file->filename .= '.txt';
236     }
238     // Move uploaded files from php's upload_tmp_dir to Drupal's file temp.
239     // This overcomes open_basedir restrictions for future file operations.
240     if (!move_uploaded_file($_FILES["files"]["tmp_name"][$source], $file->filepath)) {
241       drupal_set_message(t('File upload error. Could not move uploaded file.'));
242       watchdog('file', t('Upload Error. Could not move uploaded file (%file) to destination (%destination).', array('%file' => $_FILES["files"]["tmp_name"][$source], '%destination' => $file->filepath)));
243       return FALSE;
244     }
246     $file->filesize = $_FILES["files"]["size"][$source];
247     $file->source = $source;
249     // Add processed file to the cache.
250     $upload_cache[$source] = $file;
251     return $file;
252   }
254   else {
255     // In case of previews return previous file object.
256     if (file_exists($_SESSION['file_uploads'][$source]->filepath)) {
257       return $_SESSION['file_uploads'][$source];
258     }
259   }
260   // If nothing was done, return FALSE.
261   return FALSE;
265  * Check if a file is really located inside $directory. Should be used to make
266  * sure a file specified is really located within the directory to prevent
267  * exploits.
269  * @code
270  *   // Returns FALSE:
271  *   file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files');
272  * @endcode
274  * @param $source A string set to the file to check.
275  * @param $directory A string where the file should be located.
276  * @return 0 for invalid path or the real path of the source.
277  */
278 function file_check_location($source, $directory = '') {
279   $check = realpath($source);
280   if ($check) {
281     $source = $check;
282   }
283   else {
284     // This file does not yet exist
285     $source = realpath(dirname($source)) .'/'. basename($source);
286   }
287   $directory = realpath($directory);
288   if ($directory && strpos($source, $directory) !== 0) {
289     return 0;
290   }
291   return $source;
295  * Copies a file to a new location. This is a powerful function that in many ways
296  * performs like an advanced version of copy().
297  * - Checks if $source and $dest are valid and readable/writable.
298  * - Performs a file copy if $source is not equal to $dest.
299  * - If file already exists in $dest either the call will error out, replace the
300  *   file or rename the file based on the $replace parameter.
302  * @param $source A string specifying the file location of the original file.
303  *   This parameter will contain the resulting destination filename in case of
304  *   success.
305  * @param $dest A string containing the directory $source should be copied to.
306  *   If this value is omitted, Drupal's 'files' directory will be used.
307  * @param $replace Replace behavior when the destination file already exists.
308  *   - FILE_EXISTS_REPLACE - Replace the existing file
309  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
310  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
311  * @return True for success, FALSE for failure.
312  */
313 function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
314   $dest = file_create_path($dest);
316   $directory = $dest;
317   $basename = file_check_path($directory);
319   // Make sure we at least have a valid directory.
320   if ($basename === FALSE) {
321     $source = is_object($source) ? $source->filepath : $source;
322     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');
323     watchdog('file system', t('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);
324     return 0;
325   }
327   // Process a file upload object.
328   if (is_object($source)) {
329     $file = $source;
330     $source = $file->filepath;
331     if (!$basename) {
332       $basename = $file->filename;
333     }
334   }
336   $source = realpath($source);
337   if (!file_exists($source)) {
338     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');
339     return 0;
340   }
342   // If the destination file is not specified then use the filename of the source file.
343   $basename = $basename ? $basename : basename($source);
344   $dest = $directory .'/'. $basename;
346   // Make sure source and destination filenames are not the same, makes no sense
347   // to copy it if they are. In fact copying the file will most likely result in
348   // a 0 byte file. Which is bad. Real bad.
349   if ($source != realpath($dest)) {
350     if (file_exists($dest)) {
351       switch ($replace) {
352         case FILE_EXISTS_RENAME:
353           // Destination file already exists and we can't replace is so we try and
354           // and find a new filename.
355           if ($pos = strrpos($basename, '.')) {
356             $name = substr($basename, 0, $pos);
357             $ext = substr($basename, $pos);
358           }
359           else {
360             $name = $basename;
361           }
363           $counter = 0;
364           do {
365             $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
366           } while (file_exists($dest));
367           break;
369         case FILE_EXISTS_ERROR:
370           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');
371           return 0;
372       }
373     }
375     if (!@copy($source, $dest)) {
376       drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error');
377       return 0;
378     }
380     // Give everyone read access so that FTP'd users or
381     // non-webserver users can see/read these files.
382     @chmod($dest, 0664);
383   }
385   if (is_object($file)) {
386     $file->filename = $basename;
387     $file->filepath = $dest;
388     $source = $file;
389   }
390   else {
391     $source = $dest;
392   }
394   return 1; // Everything went ok.
398  * Moves a file to a new location.
399  * - Checks if $source and $dest are valid and readable/writable.
400  * - Performs a file move if $source is not equal to $dest.
401  * - If file already exists in $dest either the call will error out, replace the
402  *   file or rename the file based on the $replace parameter.
404  * @param $source A string specifying the file location of the original file.
405  *   This parameter will contain the resulting destination filename in case of
406  *   success.
407  * @param $dest A string containing the directory $source should be copied to.
408  *   If this value is omitted, Drupal's 'files' directory will be used.
409  * @param $replace Replace behavior when the destination file already exists.
410  *   - FILE_EXISTS_REPLACE - Replace the existing file
411  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
412  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
413  * @return True for success, FALSE for failure.
414  */
415 function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
417   $path_original = is_object($source) ? $source->filepath : $source;
419   if (file_copy($source, $dest, $replace)) {
420     $path_current = is_object($source) ? $source->filepath : $source;
422     if ($path_original == $path_current || file_delete($path_original)) {
423       return 1;
424     }
425     drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
426   }
427   return 0;
431  * Create a full file path from a directory and filename. If a file with the
432  * specified name already exists, an alternative will be used.
434  * @param $basename string filename
435  * @param $directory string directory
436  * @return
437  */
438 function file_create_filename($basename, $directory) {
439   $dest = $directory .'/'. $basename;
441   if (file_exists($dest)) {
442     // Destination file already exists, generate an alternative.
443     if ($pos = strrpos($basename, '.')) {
444       $name = substr($basename, 0, $pos);
445       $ext = substr($basename, $pos);
446     }
447     else {
448       $name = $basename;
449     }
451     $counter = 0;
452     do {
453       $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
454     } while (file_exists($dest));
455   }
457   return $dest;
461  * Delete a file.
463  * @param $path A string containing a file path.
464  * @return True for success, FALSE for failure.
465  */
466 function file_delete($path) {
467   if (is_file($path)) {
468     return unlink($path);
469   }
473  * Saves a file upload to a new location. The source file is validated as a
474  * proper upload and handled as such.
476  * @param $source A string specifying the name of the upload field to save.
477  *   This parameter will contain the resulting destination filename in case of
478  *   success.
479  * @param $dest A string containing the directory $source should be copied to,
480  *   will use the temporary directory in case no other value is set.
481  * @param $replace A boolean, set to TRUE if the destination should be replaced
482  *   when in use, but when FALSE append a _X to the filename.
483  * @return An object containing file info or 0 in case of error.
484  */
485 function file_save_upload($source, $dest = FALSE, $replace = FILE_EXISTS_RENAME) {
486   // Make sure $source exists && is valid.
487   if ($file = file_check_upload($source)) {
489     // This should be refactored, file_check_upload has already
490     // moved the file to the temporary folder.
491     if (!$dest) {
492       $dest = file_directory_temp();
493       $temporary = 1;
494       if (is_file($file->filepath)) {
495         // If this file was uploaded by this user before replace the temporary copy.
496         $replace = FILE_EXISTS_REPLACE;
497       }
498     }
500     unset($_SESSION['file_uploads'][is_object($source) ? $source->source : $source]);
501     if (file_move($file, $dest, $replace)) {
502       if ($temporary) {
503         $_SESSION['file_uploads'][is_object($source) ? $source->source : $source] = $file;
504       }
505       return $file;
506     }
507     return 0;
508   }
509   return 0;
513  * Save a string to the specified destination.
515  * @param $data A string containing the contents of the file.
516  * @param $dest A string containing the destination location.
517  * @param $replace Replace behavior when the destination file already exists.
518  *   - FILE_EXISTS_REPLACE - Replace the existing file
519  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
520  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
522  * @return A string containing the resulting filename or 0 on error
523  */
524 function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
525   $temp = file_directory_temp();
526   $file = tempnam($temp, 'file');
527   if (!$fp = fopen($file, 'wb')) {
528     drupal_set_message(t('The file could not be created.'), 'error');
529     return 0;
530   }
531   fwrite($fp, $data);
532   fclose($fp);
534   if (!file_move($file, $dest, $replace)) {
535     return 0;
536   }
538   return $file;
542  * Transfer file using http to client. Pipes a file through Drupal to the
543  * client.
545  * @param $source File to transfer.
546  * @param $headers An array of http headers to send along with file.
547  */
548 function file_transfer($source, $headers) {
549   ob_end_clean();
551   foreach ($headers as $header) {
552     // To prevent HTTP header injection, we delete new lines that are
553     // not followed by a space or a tab.
554     // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
555     $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
556     drupal_set_header($header);
557   }
559   $source = file_create_path($source);
561   // Transfer file in 1024 byte chunks to save memory usage.
562   if ($fd = fopen($source, 'rb')) {
563     while (!feof($fd)) {
564       print fread($fd, 1024);
565     }
566     fclose($fd);
567   }
568   else {
569     drupal_not_found();
570   }
571   exit();
575  * Call modules that implement hook_file_download() to find out if a file is
576  * accessible and what headers it should be transferred with. If a module
577  * returns -1 drupal_access_denied() will be returned. If one or more modules
578  * returned headers the download will start with the returned headers. If no
579  * modules respond drupal_not_found() will be returned.
580  */
582 function file_download() {
583   // Merge remainder of arguments from GET['q'], into relative file path.
584   $args = func_get_args();
585   $filepath = implode('/', $args);
587   // Maintain compatibility with old ?file=paths saved in node bodies.
588   if (isset($_GET['file'])) {
589     $filepath =  $_GET['file'];
590   }
592   if (file_exists(file_create_path($filepath))) {
593     $headers = module_invoke_all('file_download', $filepath);
594     if (in_array(-1, $headers)) {
595         return drupal_access_denied();
596     }
597     if (count($headers)) {
598         file_transfer($filepath, $headers);
599     }
600   }
601   return drupal_not_found();
606  * Finds all files that match a given mask in a given directory.
607  * Directories and files beginning with a period are excluded; this
608  * prevents hidden files and directories (such as SVN working directories)
609  * from being scanned.
611  * @param $dir
612  *   The base directory for the scan.
613  * @param $mask
614  *   The regular expression of the files to find.
615  * @param $nomask
616  *   An array of files/directories to ignore.
617  * @param $callback
618  *   The callback function to call for each match.
619  * @param $recurse
620  *   When TRUE, the directory scan will recurse the entire tree
621  *   starting at the provided directory.
622  * @param $key
623  *   The key to be used for the returned array of files. Possible
624  *   values are "filename", for the path starting with $dir,
625  *   "basename", for the basename of the file, and "name" for the name
626  *   of the file without an extension.
627  * @param $min_depth
628  *   Minimum depth of directories to return files from.
629  * @param $depth
630  *   Current depth of recursion. This parameter is only used internally and should not be passed.
632  * @return
633  *   An associative array (keyed on the provided key) of objects with
634  *   "path", "basename", and "name" members corresponding to the
635  *   matching files.
636  */
637 function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) {
638   $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
639   $files = array();
641   if (is_dir($dir) && $handle = opendir($dir)) {
642     while ($file = readdir($handle)) {
643       if (!in_array($file, $nomask) && $file[0] != '.') {
644         if (is_dir("$dir/$file") && $recurse) {
645           $files = array_merge($files, file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1));
646         }
647         elseif ($depth >= $min_depth && ereg($mask, $file)) {
648           $filename = "$dir/$file";
649           $basename = basename($file);
650           $name = substr($basename, 0, strrpos($basename, '.'));
651           $files[$$key] = new stdClass();
652           $files[$$key]->filename = $filename;
653           $files[$$key]->basename = $basename;
654           $files[$$key]->name = $name;
655           if ($callback) {
656             $callback($filename);
657           }
658         }
659       }
660     }
662     closedir($handle);
663   }
665   return $files;
669  * Determine the default temporary directory.
671  * @return A string containing a temp directory.
672  */
673 function file_directory_temp() {
674   $temporary_directory = variable_get('file_directory_temp', NULL);
676   if (is_null($temporary_directory)) {
677     $directories = array();
679     // Has PHP been set with an upload_tmp_dir?
680     if (ini_get('upload_tmp_dir')) {
681       $directories[] = ini_get('upload_tmp_dir');
682     }
684     // Operating system specific dirs.
685     if (substr(PHP_OS, 0, 3) == 'WIN') {
686       $directories[] = 'c:\\windows\\temp';
687       $directories[] = 'c:\\winnt\\temp';
688       $path_delimiter = '\\';
689     }
690     else {
691       $directories[] = '/tmp';
692       $path_delimiter = '/';
693     }
695     foreach ($directories as $directory) {
696       if (!$temporary_directory && is_dir($directory)) {
697         $temporary_directory = $directory;
698       }
699     }
701     // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp';
702     $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter . 'tmp';
703     variable_set('file_directory_temp', $temporary_directory);
704   }
706   return $temporary_directory;
710  * Determine the default 'files' directory.
712  * @return A string containing the path to Drupal's 'files' directory.
713  */
714 function file_directory_path() {
715   return variable_get('file_directory_path', 'files');
719  * Determine the maximum file upload size by querying the PHP settings.
721  * @return
722  *   A file size limit in MB based on the PHP upload_max_filesize and post_max_size
723  */
724 function file_upload_max_size() {
725   static $max_size = -1;
727   if ($max_size < 0) {
728     $upload_max = parse_size(ini_get('upload_max_filesize'));
729     // sanity check- a single upload should not be more than 50% the size limit of the total post
730     $post_max = parse_size(ini_get('post_max_size')) / 2;
731     $max_size = ($upload_max < $post_max) ? $upload_max : $post_max;
732   }
733   return $max_size;