Code coverage for /20080809/includes/file.inc

Line #Times calledCode
1
<?php
2
// $Id: file.inc,v 1.127 2008/07/05 18:34:29 dries Exp $
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
/**
16
 * Flag to indicate that the 'public' file download method is enabled.
17
 *
18
 * When using this method, files are available from a regular HTTP
request,
19
 * which provides no additional access restrictions.
20
 */
212027
define('FILE_DOWNLOADS_PUBLIC', 1);
22
23
/**
24
 * Flag to indicate that the 'private' file download method is enabled.
25
 *
26
 * When using this method, all file requests are served by Drupal, during
which
27
 * access-control checking can be performed.
28
 */
292027
define('FILE_DOWNLOADS_PRIVATE', 2);
30
31
/**
32
 * Flag used by file_create_directory() -- create directory if not
present.
33
 */
342027
define('FILE_CREATE_DIRECTORY', 1);
35
36
/**
37
 * Flag used by file_create_directory() -- file permissions may be
changed.
38
 */
392027
define('FILE_MODIFY_PERMISSIONS', 2);
40
41
/**
42
 * Flag for dealing with existing files: Append number until filename is
unique.
43
 */
442027
define('FILE_EXISTS_RENAME', 0);
45
46
/**
47
 * Flag for dealing with existing files: Replace the existing file.
48
 */
492027
define('FILE_EXISTS_REPLACE', 1);
50
51
/**
52
 * Flag for dealing with existing files: Do nothing and return FALSE.
53
 */
542027
define('FILE_EXISTS_ERROR', 2);
55
56
/**
57
 * File status -- File has been temporarily saved to the {files} tables.
58
 *
59
 * Drupal's file garbage collection will delete the file and remove it from
the
60
 * files table after a set period of time.
61
 */
622027
define('FILE_STATUS_TEMPORARY', 0);
63
64
/**
65
 * File status -- File has been permanently saved to the {files} tables.
66
 *
67
 * If you wish to add custom statuses for use by contrib modules please
expand
68
 * as binary flags and consider the first 8 bits reserved.
69
 * (0,1,2,4,8,16,32,64,128).
70
 */
712027
define('FILE_STATUS_PERMANENT', 1);
72
73
/**
74
 * Create the download path to a file.
75
 *
76
 * @param $path A string containing the path of the file to generate URL
for.
77
 * @return A string containing a URL that can be used to download the
file.
78
 */
792027
function file_create_url($path) {
80
  // Strip file_directory_path from $path. We only include relative paths
in urls.
8114
  if (strpos($path, file_directory_path() . '/') === 0) {
8214
    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
8314
  }
8414
  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
8514
    case FILE_DOWNLOADS_PUBLIC:
8614
      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' .
str_replace('\\', '/', $path);
870
    case FILE_DOWNLOADS_PRIVATE:
880
      return url('system/files/' . $path, array('absolute' => TRUE));
890
  }
900
}
91
92
/**
93
 * Make sure the destination is a complete path and resides in the file
system
94
 * directory, if it is not prepend the file system directory.
95
 *
96
 * @param $dest A string containing the path to verify. If this value is
97
 *   omitted, Drupal's 'files' directory will be used.
98
 * @return A string containing the path to file, with file system
directory
99
 *   appended if necessary, or FALSE if the path is invalid (i.e. outside
the
100
 *   configured 'files' or temp directories).
101
 */
1022027
function file_create_path($dest = 0) {
10375
  $file_path = file_directory_path();
10475
  if (!$dest) {
1050
    return $file_path;
1060
  }
107
  // file_check_location() checks whether the destination is inside the
Drupal files directory.
10875
  if (file_check_location($dest, $file_path)) {
1096
    return $dest;
1100
  }
111
  // check if the destination is instead inside the Drupal temporary files
directory.
11269
  else if (file_check_location($dest, file_directory_temp())) {
1136
    return $dest;
1140
  }
115
  // Not found, try again with prefixed directory path.
11666
  else if (file_check_location($file_path . '/' . $dest, $file_path)) {
11766
    return $file_path . '/' . $dest;
1180
  }
119
  // File not found.
1200
  return FALSE;
1210
}
122
123
/**
124
 * Check that the directory exists and is writable. Directories need to
125
 * have execute permissions to be considered a directory by FTP servers,
etc.
126
 *
127
 * @param $directory A string containing the name of a directory path.
128
 * @param $mode A Boolean value to indicate if the directory should be
created
129
 *   if it does not exist or made writable if it is read-only.
130
 * @param $form_item An optional string containing the name of a form item
that
131
 *   any errors will be attached to. This is useful for settings forms
that
132
 *   require the user to specify a writable directory. If it can't be made
to
133
 *   work, a form error will be set preventing them from saving the
settings.
134
 * @return FALSE when directory not found, or TRUE when directory exists.
135
 */
1362027
function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
13778
  $directory = rtrim($directory, '/\\');
138
139
  // Check if directory exists.
14078
  if (!is_dir($directory)) {
14166
    if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
14262
      @chmod($directory, 0775); // Necessary for non-webserver users.
14362
    }
144
    else {
1455
      if ($form_item) {
1460
        form_set_error($form_item, t('The directory %directory does not
exist.', array('%directory' => $directory)));
1470
        watchdog('file system', 'The directory %directory does not exist.',
array('%directory' => $directory), WATCHDOG_ERROR);
1480
      }
1495
      return FALSE;
150
    }
15162
  }
152
153
  // Check to see if the directory is writable.
15478
  if (!is_writable($directory)) {
1550
    if (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775)) {
1560
      form_set_error($form_item, t('The directory %directory is not
writable', array('%directory' => $directory)));
1570
      watchdog('file system', 'The directory %directory is not writable,
because it does not have the correct permissions set.', array('%directory'
=> $directory), WATCHDOG_ERROR);
1580
      return FALSE;
1590
    }
1600
  }
161
16278
  if ((file_directory_path() == $directory || file_directory_temp() ==
$directory) && !is_file("$directory/.htaccess")) {
16362
    $htaccess_lines = "SetHandler
Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions
+FollowSymLinks";
16462
    if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp,
$htaccess_lines)) {
16562
      fclose($fp);
16662
      chmod($directory . '/.htaccess', 0664);
16762
    }
168
    else {
1690
      $variables = array('%directory' => $directory, '!htaccess' => '<br
/>' . nl2br(check_plain($htaccess_lines)));
1700
      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));
1710
      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);
172
    }
17362
  }
174
17578
  return TRUE;
1760
}
177
178
/**
179
 * Checks path to see if it is a directory, or a dir/file.
180
 *
181
 * @param $path A string containing a file path. This will be set to the
182
 *   directory's path.
183
 * @return If the directory is not in a Drupal writable directory, FALSE
is
184
 *   returned. Otherwise, the base name of the path is returned.
185
 */
1862027
function file_check_path(&$path) {
187
  // Check if path is a directory.
18810
  if (file_check_directory($path)) {
1895
    return '';
1900
  }
191
192
  // Check if path is a possible dir/file.
1935
  $filename = basename($path);
1945
  $path = dirname($path);
1955
  if (file_check_directory($path)) {
1965
    return $filename;
1970
  }
198
1990
  return FALSE;
2000
}
201
202
/**
203
 * Check if a file is really located inside $directory. Should be used to
make
204
 * sure a file specified is really located within the directory to prevent
205
 * exploits.
206
 *
207
 * @code
208
 *   // Returns FALSE:
209
 *   file_check_location('/www/example.com/files/../../../etc/passwd',
'/www/example.com/files');
210
 * @endcode
211
 *
212
 * @param $source A string set to the file to check.
213
 * @param $directory A string where the file should be located.
214
 * @return FALSE for invalid path or the real path of the source.
215
 */
2162027
function file_check_location($source, $directory = '') {
21775
  $check = realpath($source);
21875
  if ($check) {
21975
    $source = $check;
22075
  }
221
  else {
222
    // This file does not yet exist
2230
    $source = realpath(dirname($source)) . '/' . basename($source);
224
  }
22575
  $directory = realpath($directory);
22675
  if ($directory && strpos($source, $directory) !== 0) {
22769
    return FALSE;
2280
  }
22975
  return $source;
2300
}
231
232
/**
233
 * Copies a file to a new location. This is a powerful function that in
many ways
234
 * performs like an advanced version of copy().
235
 * - Checks if $source and $dest are valid and readable/writable.
236
 * - Performs a file copy if $source is not equal to $dest.
237
 * - If file already exists in $dest either the call will error out,
replace the
238
 *   file or rename the file based on the $replace parameter.
239
 *
240
 * @param $source A string specifying the file location of the original
file.
241
 *   This parameter will contain the resulting destination filename in case
of
242
 *   success.
243
 * @param $dest A string containing the directory $source should be copied
to.
244
 *   If this value is omitted, Drupal's 'files' directory will be used.
245
 * @param $replace Replace behavior when the destination file already
exists.
246
 *   - FILE_EXISTS_REPLACE - Replace the existing file
247
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is unique
248
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
249
 * @return True for success, FALSE for failure.
250
 */
2512027
function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
2525
  $dest = file_create_path($dest);
253
2545
  $directory = $dest;
2555
  $basename = file_check_path($directory);
256
257
  // Make sure we at least have a valid directory.
2585
  if ($basename === FALSE) {
2590
    $source = is_object($source) ? $source->filepath : $source;
2600
    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');
2610
    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);
2620
    return FALSE;
2630
  }
264
265
  // Process a file upload object.
2665
  if (is_object($source)) {
2673
    $file = $source;
2683
    $source = $file->filepath;
2693
    if (!$basename) {
2700
      $basename = $file->filename;
2710
    }
2723
  }
273
2745
  $source = realpath($source);
2755
  if (!file_exists($source)) {
2760
    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');
2770
    return FALSE;
2780
  }
279
280
  // If the destination file is not specified then use the filename of the
source file.
2815
  $basename = $basename ? $basename : basename($source);
2825
  $dest = $directory . '/' . $basename;
283
284
  // Make sure source and destination filenames are not the same, makes no
sense
285
  // to copy it if they are. In fact copying the file will most likely
result in
286
  // a 0 byte file. Which is bad. Real bad.
2875
  if ($source != realpath($dest)) {
2885
    if (!$dest = file_destination($dest, $replace)) {
2890
      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');
2900
      return FALSE;
2910
    }
292
2935
    if (!@copy($source, $dest)) {
2940
      drupal_set_message(t('The selected file %file could not be copied.',
array('%file' => $source)), 'error');
2950
      return FALSE;
2960
    }
297
298
    // Give everyone read access so that FTP'd users or
299
    // non-webserver users can see/read these files,
300
    // and give group write permissions so group members
301
    // can alter files uploaded by the webserver.
3025
    @chmod($dest, 0664);
3035
  }
304
3055
  if (isset($file) && is_object($file)) {
3063
    $file->filename = $basename;
3073
    $file->filepath = $dest;
3083
    $source = $file;
3093
  }
310
  else {
3112
    $source = $dest;
312
  }
313
3145
  return TRUE; // Everything went ok.
3150
}
316
317
/**
318
 * Determines the destination path for a file depending on how replacement
of
319
 * existing files should be handled.
320
 *
321
 * @param $destination A string specifying the desired path.
322
 * @param $replace Replace behavior when the destination file already
exists.
323
 *   - FILE_EXISTS_REPLACE - Replace the existing file
324
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
325
 *     unique
326
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
327
 * @return The destination file path or FALSE if the file already exists
and
328
 *   FILE_EXISTS_ERROR was specified.
329
 */
3302027
function file_destination($destination, $replace) {
33113
  if (file_exists($destination)) {
332
    switch ($replace) {
3334
      case FILE_EXISTS_RENAME:
3344
        $basename = basename($destination);
3354
        $directory = dirname($destination);
3364
        $destination = file_create_filename($basename, $directory);
3374
        break;
338
3390
      case FILE_EXISTS_ERROR:
3400
        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');
3410
        return FALSE;
3420
    }
3434
  }
34413
  return $destination;
3450
}
346
347
/**
348
 * Moves a file to a new location.
349
 * - Checks if $source and $dest are valid and readable/writable.
350
 * - Performs a file move if $source is not equal to $dest.
351
 * - If file already exists in $dest either the call will error out,
replace the
352
 *   file or rename the file based on the $replace parameter.
353
 *
354
 * @param $source A string specifying the file location of the original
file.
355
 *   This parameter will contain the resulting destination filename in case
of
356
 *   success.
357
 * @param $dest A string containing the directory $source should be copied
to.
358
 *   If this value is omitted, Drupal's 'files' directory will be used.
359
 * @param $replace Replace behavior when the destination file already
exists.
360
 *   - FILE_EXISTS_REPLACE - Replace the existing file
361
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is unique
362
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
363
 * @return TRUE for success, FALSE for failure.
364
 */
3652027
function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
3662
  $path_original = is_object($source) ? $source->filepath : $source;
367
3682
  if (file_copy($source, $dest, $replace)) {
3692
    $path_current = is_object($source) ? $source->filepath : $source;
370
3712
    if ($path_original == $path_current || file_delete($path_original)) {
3722
      return TRUE;
3730
    }
3740
    drupal_set_message(t('The removal of the original file %file has
failed.', array('%file' => $path_original)), 'error');
3750
  }
3760
  return FALSE;
3770
}
378
379
/**
380
 * Munge the filename as needed for security purposes. For instance the
file
381
 * name "exploit.php.pps" would become "exploit.php_.pps".
382
 *
383
 * @param $filename The name of a file to modify.
384
 * @param $extensions A space separated list of extensions that should not
385
 *   be altered.
386
 * @param $alerts Whether alerts (watchdog, drupal_set_message()) should
be
387
 *   displayed.
388
 * @return $filename The potentially modified $filename.
389
 */
3902027
function file_munge_filename($filename, $extensions, $alerts = TRUE) {
39111
  $original = $filename;
392
393
  // Allow potentially insecure uploads for very savvy users and admin
39411
  if (!variable_get('allow_insecure_uploads', 0)) {
39511
    $whitelist = array_unique(explode(' ', trim($extensions)));
396
397
    // Split the filename up by periods. The first part becomes the
basename
398
    // the last part the final extension.
39911
    $filename_parts = explode('.', $filename);
40011
    $new_filename = array_shift($filename_parts); // Remove file basename.
40111
    $final_extension = array_pop($filename_parts); // Remove final
extension.
402
403
    // Loop through the middle parts of the name and add an underscore to
the
404
    // end of each section that could be a file extension but isn't in the
list
405
    // of allowed extensions.
40611
    foreach ($filename_parts as $filename_part) {
4070
      $new_filename .= '.' . $filename_part;
4080
      if (!in_array($filename_part, $whitelist) &&
preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
4090
        $new_filename .= '_';
4100
      }
4110
    }
41211
    $filename = $new_filename . '.' . $final_extension;
413
41411
    if ($alerts && $original != $filename) {
4150
      drupal_set_message(t('For security reasons, your upload has been
renamed to %filename.', array('%filename' => $filename)));
4160
    }
41711
  }
418
41911
  return $filename;
4200
}
421
422
/**
423
 * Undo the effect of upload_munge_filename().
424
 *
425
 * @param $filename string filename
426
 * @return string
427
 */
4282027
function file_unmunge_filename($filename) {
4290
  return str_replace('_.', '.', $filename);
4300
}
431
432
/**
433
 * Create a full file path from a directory and filename. If a file with
the
434
 * specified name already exists, an alternative will be used.
435
 *
436
 * @param $basename string filename
437
 * @param $directory string directory
438
 * @return
439
 */
4402027
function file_create_filename($basename, $directory) {
4414
  $dest = $directory . '/' . $basename;
442
4434
  if (file_exists($dest)) {
444
    // Destination file already exists, generate an alternative.
4454
    if ($pos = strrpos($basename, '.')) {
4464
      $name = substr($basename, 0, $pos);
4474
      $ext = substr($basename, $pos);
4484
    }
449
    else {
4500
      $name = $basename;
451
    }
452
4534
    $counter = 0;
454
    do {
4554
      $dest = $directory . '/' . $name . '_' . $counter++ . $ext;
4564
    } while (file_exists($dest));
4574
  }
458
4594
  return $dest;
4600
}
461
462
/**
463
 * Delete a file.
464
 *
465
 * @param $path A string containing a file path.
466
 * @return TRUE for success, FALSE for failure.
467
 */
4682027
function file_delete($path) {
46964
  if (is_file($path)) {
47064
    return unlink($path);
4710
  }
4720
}
473
474
/**
475
 * Determine total disk space used by a single user or the whole
filesystem.
476
 *
477
 * @param $uid
478
 *   An optional user id. A NULL value returns the total space used
479
 *   by all files.
480
 */
4812027
function file_space_used($uid = NULL) {
4829
  if (isset($uid)) {
4839
    return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}
WHERE uid = %d', $uid));
4840
  }
4850
  return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}'));
4860
}
487
488
/**
489
 * Saves a file upload to a new location. The source file is validated as
a
490
 * proper upload and handled as such.
491
 *
492
 * The file will be added to the files table as a temporary file. Temporary
files
493
 * are periodically cleaned. To make the file permanent file call
494
 * file_set_status() to change its status.
495
 *
496
 * @param $source
497
 *   A string specifying the name of the upload field to save.
498
 * @param $validators
499
 *   An optional, associative array of callback functions used to validate
the
500
 *   file. The keys are function names and the values arrays of callback
501
 *   parameters which will be passed in after the user and file objects.
The
502
 *   functions should return an array of error messages, an empty array
503
 *   indicates that the file passed validation. The functions will be
called in
504
 *   the order specified.
505
 * @param $dest
506
 *   A string containing the directory $source should be copied to. If this
is
507
 *   not provided or is not writable, the temporary directory will be
used.
508
 * @param $replace
509
 *   A boolean indicating whether an existing file of the same name in the
510
 *   destination directory should overwritten. A false value will generate
a
511
 *   new, unique filename in the destination directory.
512
 * @return
513
 *   An object containing the file information, or FALSE in the event of an
error.
514
 */
5152027
function file_save_upload($source, $validators = array(), $dest = FALSE,
$replace = FILE_EXISTS_RENAME) {
51614
  global $user;
51714
  static $upload_cache;
518
519
  // Add in our check of the the file name length.
52014
  $validators['file_validate_name_length'] = array();
521
522
  // Return cached objects without processing since the file will have
523
  // already been processed and the paths in _FILES will be invalid.
52414
  if (isset($upload_cache[$source])) {
5250
    return $upload_cache[$source];
5260
  }
527
528
  // If a file was uploaded, process it.
52914
  if (isset($_FILES['files']) && $_FILES['files']['name'][$source] &&
is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
530
    // Check for file upload errors and return FALSE if a
531
    // lower level system error occurred.
53211
    switch ($_FILES['files']['error'][$source]) {
533
      // @see http://php.net/manual/en/features.file-upload.errors.php
53411
      case UPLOAD_ERR_OK:
53511
        break;
536
5370
      case UPLOAD_ERR_INI_SIZE:
5380
      case UPLOAD_ERR_FORM_SIZE:
5390
        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');
5400
        return FALSE;
541
5420
      case UPLOAD_ERR_PARTIAL:
5430
      case UPLOAD_ERR_NO_FILE:
5440
        drupal_set_message(t('The file %file could not be saved, because
the upload did not complete.', array('%file' => $source)), 'error');
5450
        return FALSE;
546
547
        // Unknown error
5480
      default:
5490
        drupal_set_message(t('The file %file could not be saved. An unknown
error has occurred.', array('%file' => $source)), 'error');
5500
        return FALSE;
5510
    }
552
553
    // Build the list of non-munged extensions.
554
    // @todo: this should not be here. we need to figure out the right
place.
55511
    $extensions = '';
55611
    foreach ($user->roles as $rid => $name) {
55711
      $extensions .= ' ' . variable_get("upload_extensions_$rid",
55811
      variable_get('upload_extensions_default', 'jpg jpeg gif png txt html
doc xls pdf ppt pps odt ods odp'));
55911
    }
560
561
    // Begin building file object.
56211
    $file = new stdClass();
56311
    $file->filename =
file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'),
$extensions);
56411
    $file->filepath = $_FILES['files']['tmp_name'][$source];
56511
    $file->filemime = $_FILES['files']['type'][$source];
566
567
    // Rename potentially executable files, to help prevent exploits.
56811
    if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) &&
(substr($file->filename, -4) != '.txt')) {
5690
      $file->filemime = 'text/plain';
5700
      $file->filepath .= '.txt';
5710
      $file->filename .= '.txt';
5720
    }
573
574
    // If the destination is not provided, or is not writable, then use
the
575
    // temporary directory.
57611
    if (empty($dest) || file_check_path($dest) === FALSE) {
5776
      $dest = file_directory_temp();
5786
    }
579
58011
    $file->source = $source;
58111
    $file->destination = file_destination(file_create_path($dest . '/' .
$file->filename), $replace);
58211
    $file->filesize = $_FILES['files']['size'][$source];
583
584
    // Call the validation functions.
58511
    $errors = array();
58611
    foreach ($validators as $function => $args) {
58711
      array_unshift($args, $file);
58811
      $errors = array_merge($errors, call_user_func_array($function,
$args));
58911
    }
590
591
    // Check for validation errors.
59211
    if (!empty($errors)) {
5933
      $message = t('The selected file %name could not be uploaded.',
array('%name' => $file->filename));
5943
      if (count($errors) > 1) {
5950
        $message .= '<ul><li>' . implode('</li><li>', $errors) .
'</li></ul>';
5960
      }
597
      else {
5983
        $message .= ' ' . array_pop($errors);
599
      }
6003
      form_set_error($source, $message);
6013
      return FALSE;
6020
    }
603
604
    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
directory.
605
    // This overcomes open_basedir restrictions for future file
operations.
6068
    $file->filepath = $file->destination;
6078
    if (!move_uploaded_file($_FILES['files']['tmp_name'][$source],
$file->filepath)) {
6080
      form_set_error($source, t('File upload error. Could not move uploaded
file.'));
6090
      watchdog('file', 'Upload error. Could not move uploaded file %file to
destination %destination.', array('%file' => $file->filename,
'%destination' => $file->filepath));
6100
      return FALSE;
6110
    }
612
613
    // If we made it this far it's safe to record this file in the
database.
6148
    $file->uid = $user->uid;
6158
    $file->status = FILE_STATUS_TEMPORARY;
6168
    $file->timestamp = time();
6178
    drupal_write_record('files', $file);
618
619
    // Add file to the cache.
6208
    $upload_cache[$source] = $file;
6218
    return $file;
6220
  }
6233
  return FALSE;
6240
}
625
626
/**
627
 * Check for files with names longer than we can store in the database.
628
 *
629
 * @param $file
630
 *   A Drupal file object.
631
 * @return
632
 *   An array. If the file name is too long, it will contain an error
message.
633
 */
6342027
function file_validate_name_length($file) {
63511
  $errors = array();
636
63711
  if (strlen($file->filename) > 255) {
6380
    $errors[] = t('Its name exceeds the 255 characters limit. Please rename
the file and try again.');
6390
  }
64011
  return $errors;
6410
}
642
643
/**
644
 * Check that the filename ends with an allowed extension. This check is
not
645
 * enforced for the user #1.
646
 *
647
 * @param $file
648
 *   A Drupal file object.
649
 * @param $extensions
650
 *   A string with a space separated
651
 * @return
652
 *   An array. If the file extension is not allowed, it will contain an
error message.
653
 */
6542027
function file_validate_extensions($file, $extensions) {
6555
  global $user;
656
6575
  $errors = array();
658
659
  // Bypass validation for uid  = 1.
6605
  if ($user->uid != 1) {
6615
    $regex = '/\.(' . ereg_replace(' +', '|', preg_quote($extensions)) .
')$/i';
6625
    if (!preg_match($regex, $file->filename)) {
6631
      $errors[] = t('Only files with the following extensions are allowed:
%files-allowed.', array('%files-allowed' => $extensions));
6641
    }
6655
  }
6665
  return $errors;
6670
}
668
669
/**
670
 * Check that the file's size is below certain limits. This check is not
671
 * enforced for the user #1.
672
 *
673
 * @param $file
674
 *   A Drupal file object.
675
 * @param $file_limit
676
 *   An integer specifying the maximum file size in bytes. Zero indicates
that
677
 *   no limit should be enforced.
678
 * @param $$user_limit
679
 *   An integer specifying the maximum number of bytes the user is allowed.
Zero
680
 *   indicates that no limit should be enforced.
681
 * @return
682
 *   An array. If the file size exceeds limits, it will contain an error
message.
683
 */
6842027
function file_validate_size($file, $file_limit = 0, $user_limit = 0) {
6859
  global $user;
686
6879
  $errors = array();
688
689
  // Bypass validation for uid  = 1.
6909
  if ($user->uid != 1) {
6919
    if ($file_limit && $file->filesize > $file_limit) {
6921
      $errors[] = t('The file is %filesize exceeding the maximum file size
of %maxsize.', array('%filesize' => format_size($file->filesize),
'%maxsize' => format_size($file_limit)));
6931
    }
694
6959
    $total_size = file_space_used($user->uid) + $file->filesize;
6969
    if ($user_limit && $total_size > $user_limit) {
6970
      $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)));
6980
    }
6999
  }
7009
  return $errors;
7010
}
702
703
/**
704
 * Check that the file is recognized by image_get_info() as an image.
705
 *
706
 * @param $file
707
 *   A Drupal file object.
708
 * @return
709
 *   An array. If the file is not an image, it will contain an error
message.
710
 */
7112027
function file_validate_is_image(&$file) {
7124
  $errors = array();
713
7144
  $info = image_get_info($file->filepath);
7154
  if (!$info || empty($info['extension'])) {
7161
    $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
7171
  }
718
7194
  return $errors;
7200
}
721
722
/**
723
 * If the file is an image verify that its dimensions are within the
specified
724
 * maximum and minimum dimensions. Non-image files will be ignored.
725
 *
726
 * @param $file
727
 *   A Drupal file object. This function may resize the file affecting its
size.
728
 * @param $maximum_dimensions
729
 *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'.
If
730
 *   an image toolkit is installed the image will be resized down to these
731
 *   dimensions. A value of 0 indicates no restriction on size, so
resizing
732
 *   will be attempted.
733
 * @param $minimum_dimensions
734
 *   An optional string in the form WIDTHxHEIGHT. This will check that the
image
735
 *   meets a minimum size. A value of 0 indicates no restriction.
736
 * @return
737
 *   An array. If the file is an image and did not meet the requirements,
it
738
 *   will contain an error message.
739
 */
7402027
function file_validate_image_resolution(&$file, $maximum_dimensions = 0,
$minimum_dimensions = 0) {
7419
  $errors = array();
742
743
  // Check first that the file is an image.
7449
  if ($info = image_get_info($file->filepath)) {
7453
    if ($maximum_dimensions) {
746
      // Check that it is smaller than the given dimensions.
7473
      list($width, $height) = explode('x', $maximum_dimensions);
7483
      if ($info['width'] > $width || $info['height'] > $height) {
749
        // Try to resize the image to fit the dimensions.
7501
        if (image_get_toolkit() && image_scale($file->filepath,
$file->filepath, $width, $height)) {
7511
          drupal_set_message(t('The image was resized to fit within the
maximum allowed dimensions of %dimensions pixels.', array('%dimensions' =>
$maximum_dimensions)));
752
753
          // Clear the cached filesize and refresh the image information.
7541
          clearstatcache();
7551
          $info = image_get_info($file->filepath);
7561
          $file->filesize = $info['file_size'];
7571
        }
758
        else {
7590
          $errors[] = t('The image is too large; the maximum dimensions are
%dimensions pixels.', array('%dimensions' => $maximum_dimensions));
760
        }
7611
      }
7623
    }
763
7643
    if ($minimum_dimensions) {
765
      // Check that it is larger than the given dimensions.
7660
      list($width, $height) = explode('x', $minimum_dimensions);
7670
      if ($info['width'] < $width || $info['height'] < $height) {
7680
        $errors[] = t('The image is too small; the minimum dimensions are
%dimensions pixels.', array('%dimensions' => $minimum_dimensions));
7690
      }
7700
    }
7713
  }
772
7739
  return $errors;
7740
}
775
776
/**
777
 * Save a string to the specified destination.
778
 *
779
 * @param $data A string containing the contents of the file.
780
 * @param $dest A string containing the destination location.
781
 * @param $replace Replace behavior when the destination file already
exists.
782
 *   - FILE_EXISTS_REPLACE - Replace the existing file
783
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is unique
784
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
785
 *
786
 * @return A string containing the resulting filename or FALSE on error
787
 */
7882027
function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
7892
  $temp = file_directory_temp();
790
  // On Windows, tempnam() requires an absolute path, so we use
realpath().
7912
  $file = tempnam(realpath($temp), 'file');
7922
  if (!$fp = fopen($file, 'wb')) {
7930
    drupal_set_message(t('The file could not be created.'), 'error');
7940
    return FALSE;
7950
  }
7962
  fwrite($fp, $data);
7972
  fclose($fp);
798
7992
  if (!file_move($file, $dest, $replace)) {
8000
    return FALSE;
8010
  }
802
8032
  return $file;
8040
}
805
806
/**
807
 * Set the status of a file.
808
 *
809
 * @param file A Drupal file object
810
 * @param status A status value to set the file to.
811
 * @return FALSE on failure, TRUE on success and $file->status will contain
the
812
 *     status.
813
 */
8142027
function file_set_status(&$file, $status) {
8155
  if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status,
$file->fid)) {
8165
    $file->status = $status;
8175
    return TRUE;
8180
  }
8190
  return FALSE;
8200
}
821
822
/**
823
 * Transfer file using http to client. Pipes a file through Drupal to the
824
 * client.
825
 *
826
 * @param $source File to transfer.
827
 * @param $headers An array of http headers to send along with file.
828
 */
8292027
function file_transfer($source, $headers) {
8300
  ob_end_clean();
831
8320
  foreach ($headers as $header) {
833
    // To prevent HTTP header injection, we delete new lines that are
834
    // not followed by a space or a tab.
835
    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
8360
    $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
8370
    drupal_set_header($header);
8380
  }
839
8400
  $source = file_create_path($source);
841
842
  // Transfer file in 1024 byte chunks to save memory usage.
8430
  if ($fd = fopen($source, 'rb')) {
8440
    while (!feof($fd)) {
8450
      print fread($fd, 1024);
8460
    }
8470
    fclose($fd);
8480
  }
849
  else {
8500
    drupal_not_found();
851
  }
8520
  exit();
8530
}
854
855
/**
856
 * Call modules that implement hook_file_download() to find out if a file
is
857
 * accessible and what headers it should be transferred with. If a module
858
 * returns -1 drupal_access_denied() will be returned. If one or more
modules
859
 * returned headers the download will start with the returned headers. If
no
860
 * modules respond drupal_not_found() will be returned.
861
 */
8622027
function file_download() {
863
  // Merge remainder of arguments from GET['q'], into relative file path.
8640
  $args = func_get_args();
8650
  $filepath = implode('/', $args);
866
867
  // Maintain compatibility with old ?file=paths saved in node bodies.
8680
  if (isset($_GET['file'])) {
8690
    $filepath =  $_GET['file'];
8700
  }
871
8720
  if (file_exists(file_create_path($filepath))) {
8730
    $headers = module_invoke_all('file_download', $filepath);
8740
    if (in_array(-1, $headers)) {
8750
      return drupal_access_denied();
8760
    }
8770
    if (count($headers)) {
8780
      file_transfer($filepath, $headers);
8790
    }
8800
  }
8810
  return drupal_not_found();
8820
}
883
884
885
/**
886
 * Finds all files that match a given mask in a given directory.
887
 * Directories and files beginning with a period are excluded; this
888
 * prevents hidden files and directories (such as SVN working directories)
889
 * from being scanned.
890
 *
891
 * @param $dir
892
 *   The base directory for the scan, without trailing slash.
893
 * @param $mask
894
 *   The regular expression of the files to find.
895
 * @param $nomask
896
 *   An array of files/directories to ignore.
897
 * @param $callback
898
 *   The callback function to call for each match.
899
 * @param $recurse
900
 *   When TRUE, the directory scan will recurse the entire tree
901
 *   starting at the provided directory.
902
 * @param $key
903
 *   The key to be used for the returned array of files. Possible
904
 *   values are "filename", for the path starting with $dir,
905
 *   "basename", for the basename of the file, and "name" for the name
906
 *   of the file without an extension.
907
 * @param $min_depth
908
 *   Minimum depth of directories to return files from.
909
 * @param $depth
910
 *   Current depth of recursion. This parameter is only used internally and
should not be passed.
911
 *
912
 * @return
913
 *   An associative array (keyed on the provided key) of objects with
914
 *   "path", "basename", and "name" members corresponding to the
915
 *   matching files.
916
 */
9172027
function file_scan_directory($dir, $mask, $nomask = array('.', '..',
'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0,
$depth = 0) {
918162
  $key = (in_array($key, array('filename', 'basename', 'name')) ? $key :
'filename');
919162
  $files = array();
920
921162
  if (is_dir($dir) && $handle = opendir($dir)) {
922160
    while (false !== ($file = readdir($handle))) {
923160
      if (!in_array($file, $nomask) && $file[0] != '.') {
924160
        if (is_dir("$dir/$file") && $recurse) {
925
          // Give priority to files in this folder by merging them in after
any subdirectory files.
926160
          $files = array_merge(file_scan_directory("$dir/$file", $mask,
$nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
927160
        }
928160
        elseif ($depth >= $min_depth && ereg($mask, $file)) {
929
          // Always use this match over anything already set in $files with
the same $$key.
930160
          $filename = "$dir/$file";
931160
          $basename = basename($file);
932160
          $name = substr($basename, 0, strrpos($basename, '.'));
933160
          $files[$$key] = new stdClass();
934160
          $files[$$key]->filename = $filename;
935160
          $files[$$key]->basename = $basename;
936160
          $files[$$key]->name = $name;
937160
          if ($callback) {
9380
            $callback($filename);
9390
          }
940160
        }
941160
      }
942160
    }
943
944160
    closedir($handle);
945160
  }
946
947162
  return $files;
9480
}
949
950
/**
951
 * Determine the default temporary directory.
952
 *
953
 * @return A string containing a temp directory.
954
 */
9552027
function file_directory_temp() {
95679
  $temporary_directory = variable_get('file_directory_temp', NULL);
957
95879
  if (is_null($temporary_directory)) {
95910
    $directories = array();
960
961
    // Has PHP been set with an upload_tmp_dir?
96210
    if (ini_get('upload_tmp_dir')) {
9630
      $directories[] = ini_get('upload_tmp_dir');
9640
    }
965
966
    // Operating system specific dirs.
96710
    if (substr(PHP_OS, 0, 3) == 'WIN') {
9680
      $directories[] = 'c:\\windows\\temp';
9690
      $directories[] = 'c:\\winnt\\temp';
9700
      $path_delimiter = '\\';
9710
    }
972
    else {
97310
      $directories[] = '/tmp';
97410
      $path_delimiter = '/';
975
    }
976
97710
    foreach ($directories as $directory) {
97810
      if (!$temporary_directory && is_dir($directory)) {
97910
        $temporary_directory = $directory;
98010
      }
98110
    }
982
983
    // if a directory has been found, use it, otherwise default to
'files/tmp' or 'files\\tmp';
98410
    $temporary_directory = $temporary_directory ? $temporary_directory :
file_directory_path() . $path_delimiter . 'tmp';
98510
    variable_set('file_directory_temp', $temporary_directory);
98610
  }
987
98879
  return $temporary_directory;
9890
}
990
991
/**
992
 * Determine the default 'files' directory.
993
 *
994
 * @return A string containing the path to Drupal's 'files' directory.
995
 */
9962027
function file_directory_path() {
9971568
  return variable_get('file_directory_path', conf_path() . '/files');
9980
}
999
1000
/**
1001
 * Determine the maximum file upload size by querying the PHP settings.
1002
 *
1003
 * @return
1004
 *   A file size limit in bytes based on the PHP upload_max_filesize and
post_max_size
1005
 */
10062027
function file_upload_max_size() {
10079
  static $max_size = -1;
1008
10099
  if ($max_size < 0) {
10109
    $upload_max = parse_size(ini_get('upload_max_filesize'));
10119
    $post_max = parse_size(ini_get('post_max_size'));
10129
    $max_size = ($upload_max < $post_max) ? $upload_max : $post_max;
10139
  }
10149
  return $max_size;
10150
}
1016
1017
/**
1018
 * @} End of "defgroup file".
1019
 */
10202027