initial import from onelab svn codebase
[plewww.git] / includes / file.inc
1 <?php
2 // $Id: file.inc 144 2007-03-28 07:52:20Z thierry $
3
4 /**
5  * @file
6  * API for handling file uploads and server file management.
7  */
8
9 /**
10  * @defgroup file File interface
11  * @{
12  * Common file handling functions.
13  */
14
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);
22
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   }
40 }
41
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;
71 }
72
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, '/\\');
88
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' => theme('placeholder', $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' => theme('placeholder', $directory))));
98       }
99       return false;
100     }
101   }
102
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' => theme('placeholder', $directory))));
107     }
108     else {
109       form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => theme('placeholder', $directory))));
110       watchdog('file system', t('The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => theme('placeholder', $directory))), WATCHDOG_ERROR);
111       return false;
112     }
113   }
114
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\n<IfModule mod_rewrite.c>\n  RewriteEngine off\n</IfModule>";
117     if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
118       fclose($fp);
119     }
120     else {
121       $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' => theme('placeholder', $directory), '%htaccess' => '<br />'. str_replace("\n", '<br />', check_plain($htaccess_lines))));
122       form_set_error($form_item, $message);
123       watchdog('security', $message, WATCHDOG_ERROR);
124     }
125   }
126
127   return true;
128 }
129
130 /**
131  * Checks path to see if it is a directory, or a dir/file.
132  *
133  * @param $path A string containing a file path. This will be set to the
134  *   directory's path.
135  * @return If the directory is not in a Drupal writable directory, FALSE is
136  *   returned. Otherwise, the base name of the path is returned.
137  */
138 function file_check_path(&$path) {
139   // Check if path is a directory.
140   if (file_check_directory($path)) {
141     return '';
142   }
143
144   // Check if path is a possible dir/file.
145   $filename = basename($path);
146   $path = dirname($path);
147   if (file_check_directory($path)) {
148     return $filename;
149   }
150
151   return false;
152 }
153
154
155 /**
156  * Check if $source is a valid file upload. If so, move the file to Drupal's tmp dir
157  * and return it as an object.
158  *
159  * The use of SESSION['file_uploads'] should probably be externalized to upload.module
160  *
161  * @todo Rename file_check_upload to file_prepare upload.
162  * @todo Refactor or merge file_save_upload.
163  * @todo Extenalize SESSION['file_uploads'] to modules.
164  *
165  * @param $source An upload source (the name of the upload form item), or a file
166  * @return false for an invalid file or upload. A file object for valid uploads/files.
167  *
168  */
169
170 function file_check_upload($source = 'upload') {
171   // Cache for uploaded files. Since the data in _FILES is modified
172   // by this function, we cache the result.
173   static $upload_cache;
174
175   // Test source to see if it is an object.
176   if (is_object($source)) {
177
178     // Validate the file path if an object was passed in instead of
179     // an upload key.
180     if (is_file($source->filepath)) {
181       return $source;
182     }
183     else {
184       return false;
185     }
186   }
187
188   // Return cached objects without processing since the file will have
189   // already been processed and the paths in _FILES will be invalid.
190   if (isset($upload_cache[$source])) {
191     return $upload_cache[$source];
192   }
193
194   // If a file was uploaded, process it.
195   if ($_FILES["edit"]["name"][$source] && is_uploaded_file($_FILES["edit"]["tmp_name"][$source])) {
196
197     // Check for file upload errors and return false if a
198     // lower level system error occurred.
199     switch ($_FILES["edit"]["error"][$source]) {
200
201       // @see http://php.net/manual/en/features.file-upload.errors.php
202       case UPLOAD_ERR_OK:
203         break;
204
205       case UPLOAD_ERR_INI_SIZE:
206       case UPLOAD_ERR_FORM_SIZE:
207         drupal_set_message(t('The file %file could not be saved, because it exceeds the maximum allowed size for uploads.', array('%file' => theme('placeholder', $source))), 'error');
208         return 0;
209
210       case UPLOAD_ERR_PARTIAL:
211       case UPLOAD_ERR_NO_FILE:
212         drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => theme('placeholder', $source))), 'error');
213         return 0;
214
215       // Unknown error
216       default:
217         drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => theme('placeholder', $source))),'error');
218         return 0;
219     }
220
221     // Begin building file object.
222     $file = new StdClass();
223     $file->filename = trim(basename($_FILES["edit"]["name"][$source]), '.');
224
225     // Create temporary name/path for newly uploaded files.
226     $file->filepath = tempnam(file_directory_temp(), 'tmp_');
227
228     $file->filemime = $_FILES["edit"]["type"][$source];
229
230     // Rename potentially executable files, to help prevent exploits.
231     if (((substr($file->filemime, 0, 5) == 'text/' || strpos($file->filemime, 'javascript')) && (substr($file->filename, -4) != '.txt')) || preg_match('/\.(php|pl|py|cgi|asp)$/i', $file->filename)) {
232       $file->filemime = 'text/plain';
233       $file->filepath .= '.txt';
234       $file->filename .= '.txt';
235     }
236
237     // Move uploaded files from php's upload_tmp_dir to Drupal's file temp.
238     // This overcomes open_basedir restrictions for future file operations.
239     if (!move_uploaded_file($_FILES["edit"]["tmp_name"][$source], $file->filepath)) {
240       drupal_set_message(t('File upload error. Could not move uploaded file.'));
241       watchdog('file', t('Upload Error. Could not move uploaded file(%file) to destination(%destination).', array('%file' => theme('placeholder', $_FILES["edit"]["tmp_name"][$source]), '%destination' => theme('placeholder', $file->filepath))));
242       return false;
243     }
244
245     $file->filesize = $_FILES["edit"]["size"][$source];
246     $file->source = $source;
247
248     // Add processed file to the cache.
249     $upload_cache[$source] = $file;
250     return $file;
251   }
252
253   else {
254     // In case of previews return previous file object.
255     if (file_exists($_SESSION['file_uploads'][$source]->filepath)) {
256       return $_SESSION['file_uploads'][$source];
257     }
258   }
259   // If nothing was done, return false.
260   return false;
261 }
262
263 /**
264  * Check if a file is really located inside $directory. Should be used to make
265  * sure a file specified is really located within the directory to prevent
266  * exploits.
267  *
268  * @code
269  *   // Returns false:
270  *   file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files');
271  * @endcode
272  *
273  * @param $source A string set to the file to check.
274  * @param $directory A string where the file should be located.
275  * @return 0 for invalid path or the real path of the source.
276  */
277 function file_check_location($source, $directory = '') {
278   $check = realpath($source);
279   if ($check) {
280     $source = $check;
281   }
282   else {
283     // This file does not yet exist
284     $source = realpath(dirname($source)) .'/'. basename($source);
285   }
286   $directory = realpath($directory);
287   if ($directory && strpos($source, $directory) !== 0) {
288     return 0;
289   }
290   return $source;
291 }
292
293 /**
294  * Copies a file to a new location. This is a powerful function that in many ways
295  * performs like an advanced version of copy().
296  * - Checks if $source and $dest are valid and readable/writable.
297  * - Performs a file copy if $source is not equal to $dest.
298  * - If file already exists in $dest either the call will error out, replace the
299  *   file or rename the file based on the $replace parameter.
300  *
301  * @param $source A string specifying the file location of the original file.
302  *   This parameter will contain the resulting destination filename in case of
303  *   success.
304  * @param $dest A string containing the directory $source should be copied to.
305  *   If this value is omitted, Drupal's 'files' directory will be used.
306  * @param $replace Replace behavior when the destination file already exists.
307  *   - FILE_EXISTS_REPLACE - Replace the existing file
308  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
309  *   - FILE_EXISTS_ERROR - Do nothing and return false.
310  * @return True for success, false for failure.
311  */
312 function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
313   $dest = file_create_path($dest);
314
315   $directory = $dest;
316   $basename = file_check_path($directory);
317
318   // Make sure we at least have a valid directory.
319   if ($basename === false) {
320     $source = is_object($source) ? $source->filepath : $source;
321     drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), 'error');
322     watchdog('file system', t('The selected file %file could not 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' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), WATCHDOG_ERROR);
323     return 0;
324   }
325
326   // Process a file upload object.
327   if (is_object($source)) {
328     $file = $source;
329     $source = $file->filepath;
330     if (!$basename) {
331       $basename = $file->filename;
332     }
333   }
334
335   $source = realpath($source);
336   if (!file_exists($source)) {
337     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' => theme('placeholder', $source))), 'error');
338     return 0;
339   }
340
341   // If the destination file is not specified then use the filename of the source file.
342   $basename = $basename ? $basename : basename($source);
343   $dest = $directory .'/'. $basename;
344
345   // Make sure source and destination filenames are not the same, makes no sense
346   // to copy it if they are. In fact copying the file will most likely result in
347   // a 0 byte file. Which is bad. Real bad.
348   if ($source != realpath($dest)) {
349     if (file_exists($dest)) {
350       switch ($replace) {
351         case FILE_EXISTS_RENAME:
352           // Destination file already exists and we can't replace is so we try and
353           // and find a new filename.
354           if ($pos = strrpos($basename, '.')) {
355             $name = substr($basename, 0, $pos);
356             $ext = substr($basename, $pos);
357           }
358           else {
359             $name = $basename;
360           }
361
362           $counter = 0;
363           do {
364             $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
365           } while (file_exists($dest));
366           break;
367
368         case FILE_EXISTS_ERROR:
369           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' => theme('placeholder', $source))), 'error');
370           return 0;
371       }
372     }
373
374     if (!@copy($source, $dest)) {
375       drupal_set_message(t('The selected file %file could not be copied.', array('%file' => theme('placeholder', $source))), 'error');
376       return 0;
377     }
378
379     // Give everyone read access so that FTP'd users or
380     // non-webserver users can see/read these files.
381     @chmod($dest, 0664);
382   }
383
384   if (is_object($file)) {
385     $file->filename = $basename;
386     $file->filepath = $dest;
387     $source = $file;
388   }
389   else {
390     $source = $dest;
391   }
392
393   return 1; // Everything went ok.
394 }
395
396 /**
397  * Moves a file to a new location.
398  * - Checks if $source and $dest are valid and readable/writable.
399  * - Performs a file move if $source is not equal to $dest.
400  * - If file already exists in $dest either the call will error out, replace the
401  *   file or rename the file based on the $replace parameter.
402  *
403  * @param $source A string specifying the file location of the original file.
404  *   This parameter will contain the resulting destination filename in case of
405  *   success.
406  * @param $dest A string containing the directory $source should be copied to.
407  *   If this value is omitted, Drupal's 'files' directory will be used.
408  * @param $replace Replace behavior when the destination file already exists.
409  *   - FILE_EXISTS_REPLACE - Replace the existing file
410  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
411  *   - FILE_EXISTS_ERROR - Do nothing and return false.
412  * @return True for success, false for failure.
413  */
414 function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
415
416   $path_original = is_object($source) ? $source->filepath : $source;
417
418   if (file_copy($source, $dest, $replace)) {
419     $path_current = is_object($source) ? $source->filepath : $source;
420
421     if ($path_original == $path_current || file_delete($path_original)) {
422       return 1;
423     }
424     drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => theme('placeholder', $path_original))), 'error');
425   }
426   return 0;
427 }
428
429 /**
430  * Create a full file path from a directory and filename. If a file with the
431  * specified name already exists, an alternative will be used.
432  *
433  * @param $basename string filename
434  * @param $directory string directory
435  * @return
436  */
437 function file_create_filename($basename, $directory) {
438   $dest = $directory .'/'. $basename;
439
440   if (file_exists($dest)) {
441     // Destination file already exists, generate an alternative.
442     if ($pos = strrpos($basename, '.')) {
443       $name = substr($basename, 0, $pos);
444       $ext = substr($basename, $pos);
445     }
446     else {
447       $name = $basename;
448     }
449
450     $counter = 0;
451     do {
452       $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
453     } while (file_exists($dest));
454   }
455
456   return $dest;
457 }
458
459 /**
460  * Delete a file.
461  *
462  * @param $path A string containing a file path.
463  * @return True for success, false for failure.
464  */
465 function file_delete($path) {
466   if (is_file($path)) {
467     return unlink($path);
468   }
469 }
470
471 /**
472  * Saves a file upload to a new location. The source file is validated as a
473  * proper upload and handled as such.
474  *
475  * @param $source A string specifying the name of the upload field to save.
476  *   This parameter will contain the resulting destination filename in case of
477  *   success.
478  * @param $dest A string containing the directory $source should be copied to,
479  *   will use the temporary directory in case no other value is set.
480  * @param $replace A boolean, set to true if the destination should be replaced
481  *   when in use, but when false append a _X to the filename.
482  * @return An object containing file info or 0 in case of error.
483  */
484 function file_save_upload($source, $dest = false, $replace = FILE_EXISTS_RENAME) {
485   // Make sure $source exists && is valid.
486   if ($file = file_check_upload($source)) {
487
488     // This should be refactored, file_check_upload has already
489     // moved the file to the temporary folder.
490     if (!$dest) {
491       $dest = file_directory_temp();
492       $temporary = 1;
493       if (is_file($file->filepath)) {
494         // If this file was uploaded by this user before replace the temporary copy.
495         $replace = FILE_EXISTS_REPLACE;
496       }
497     }
498
499     unset($_SESSION['file_uploads'][is_object($source) ? $source->source : $source]);
500     if (file_move($file, $dest, $replace)) {
501       if ($temporary) {
502         $_SESSION['file_uploads'][is_object($source) ? $source->source : $source] = $file;
503       }
504       return $file;
505     }
506     return 0;
507   }
508   return 0;
509 }
510
511 /**
512  * Save a string to the specified destination.
513  *
514  * @param $data A string containing the contents of the file.
515  * @param $dest A string containing the destination location.
516  * @param $replace Replace behavior when the destination file already exists.
517  *   - FILE_EXISTS_REPLACE - Replace the existing file
518  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
519  *   - FILE_EXISTS_ERROR - Do nothing and return false.
520  *
521  * @return A string containing the resulting filename or 0 on error
522  */
523 function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
524   $temp = file_directory_temp();
525   $file = tempnam($temp, 'file');
526   if (!$fp = fopen($file, 'wb')) {
527     drupal_set_message(t('The file could not be created.'), 'error');
528     return 0;
529   }
530   fwrite($fp, $data);
531   fclose($fp);
532
533   if (!file_move($file, $dest, $replace)) {
534     return 0;
535   }
536
537   return $file;
538 }
539
540 /**
541  * Transfer file using http to client. Pipes a file through Drupal to the
542  * client.
543  *
544  * @param $source File to transfer.
545  * @param $headers An array of http headers to send along with file.
546  */
547 function file_transfer($source, $headers) {
548   ob_end_clean();
549
550   foreach ($headers as $header) {
551     // To prevent HTTP header injection, we delete new lines that are
552     // not followed by a space or a tab.
553     // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
554     $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
555     header($header);
556   }
557
558   $source = file_create_path($source);
559
560   // Transfer file in 1024 byte chunks to save memory usage.
561   if ($fd = fopen($source, 'rb')) {
562     while (!feof($fd)) {
563       print fread($fd, 1024);
564     }
565     fclose($fd);
566   }
567   else {
568     drupal_not_found();
569   }
570   exit();
571 }
572
573 /**
574  * Call modules that implement hook_file_download() to find out if a file is
575  * accessible and what headers it should be transferred with. If a module
576  * returns -1 drupal_access_denied() will be returned. If one or more modules
577  * returned headers the download will start with the returned headers. If no
578  * modules respond drupal_not_found() will be returned.
579  */
580
581 function file_download() {
582   // Merge remainder of arguments from GET['q'], into relative file path.
583   $args = func_get_args();
584   $filepath = implode('/', $args);
585
586   // Maintain compatibility with old ?file=paths saved in node bodies.
587   if (isset($_GET['file'])) {
588     $filepath =  $_GET['file'];
589   }
590
591   if (file_exists(file_create_path($filepath))) {
592     $headers = module_invoke_all('file_download', $filepath);
593     if (in_array(-1, $headers)) {
594         return drupal_access_denied();
595     }
596     if (count($headers)) {
597         file_transfer($filepath, $headers);
598     }
599   }
600   return drupal_not_found();
601 }
602
603
604 /**
605  * Finds all files that match a given mask in a given
606  * directory.
607  *
608  * @param $dir
609  *   The base directory for the scan.
610  * @param $mask
611  *   The regular expression of the files to find.
612  * @param $nomask
613  *   An array of files/directories to ignore.
614  * @param $callback
615  *   The callback function to call for each match.
616  * @param $recurse
617  *   When TRUE, the directory scan will recurse the entire tree
618  *   starting at the provided directory.
619  * @param $key
620  *   The key to be used for the returned array of files.  Possible
621  *   values are "filename", for the path starting with $dir,
622  *   "basename", for the basename of the file, and "name" for the name
623  *   of the file without an extension.
624  * @param $min_depth
625  *   Minimum depth of directories to return files from.
626  * @param $depth
627  *   Current depth of recursion. This parameter is only used internally and should not be passed.
628  *
629  * @return
630  *   An associative array (keyed on the provided key) of objects with
631  *   "path", "basename", and "name" members corresponding to the
632  *   matching files.
633  */
634 function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) {
635   $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
636   $files = array();
637
638   if (is_dir($dir) && $handle = opendir($dir)) {
639     while ($file = readdir($handle)) {
640       if (!in_array($file, $nomask)) {
641         if (is_dir("$dir/$file") && $recurse) {
642           $files = array_merge($files, file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1));
643         }
644         elseif ($depth >= $min_depth && ereg($mask, $file)) {
645           $filename = "$dir/$file";
646           $basename = basename($file);
647           $name = substr($basename, 0, strrpos($basename, '.'));
648           $files[$$key] = new stdClass();
649           $files[$$key]->filename = $filename;
650           $files[$$key]->basename = $basename;
651           $files[$$key]->name = $name;
652           if ($callback) {
653             $callback($filename);
654           }
655         }
656       }
657     }
658
659     closedir($handle);
660   }
661
662   return $files;
663 }
664
665 /**
666  * Determine the default temporary directory.
667  *
668  * @return A string containing a temp directory.
669  */
670 function file_directory_temp() {
671   $temporary_directory = variable_get('file_directory_temp', NULL);
672
673   if (is_null($temporary_directory)) {
674     $directories = array();
675
676     // Has PHP been set with an upload_tmp_dir?
677     if (ini_get('upload_tmp_dir')) {
678       $directories[] = ini_get('upload_tmp_dir');
679     }
680
681     // Operating system specific dirs.
682     if (substr(PHP_OS, 0, 3) == 'WIN') {
683       $directories[] = 'c:\\windows\\temp';
684       $directories[] = 'c:\\winnt\\temp';
685       $path_delimiter = '\\';
686     }
687     else {
688       $directories[] = '/tmp';
689       $path_delimiter = '/';
690     }
691
692     foreach ($directories as $directory) {
693       if (!$temporary_directory && is_dir($directory)) {
694         $temporary_directory = $directory;
695       }
696     }
697
698     // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp';
699     $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter . 'tmp';
700     variable_set('file_directory_temp', $temporary_directory);
701   }
702
703   return $temporary_directory;
704 }
705
706 /**
707  * Determine the default 'files' directory.
708  *
709  * @return A string containing the path to Drupal's 'files' directory.
710  */
711 function file_directory_path() {
712   return variable_get('file_directory_path', 'files');
713 }
714
715