Code coverage for /20081101/includes/file.inc

Line #Times calledCode
1
<?php
2
// $Id: file.inc,v 1.140 2008/10/19 20:18:58 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
 * Fields on the file object:
15
 * - fid - File ID
16
 * - uid - The {users}.uid of the user who is associated with the file.
17
 * - filename - Name of the file with no path components. This may differ
from
18
 *   the basename of the filepath if the file is renamed to avoid
overwriting
19
 *   an existing file.
20
 * - filepath - Path of the file relative to Drupal root.
21
 * - filemime - The file's MIME type.
22
 * - filesize - The size of the file in bytes.
23
 * - status - A bitmapped field indicating the status of the file the least
24
 *   sigifigant bit indicates temporary (1) or permanent (0). Temporary
files
25
 *   older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron
run.
26
 * - timestamp - UNIX timestamp for the date the file was added to the
database.
27
 */
28
29
/**
30
 * Flag to indicate that the 'public' file download method is enabled.
31
 *
32
 * When using this method, files are available from a regular HTTP request,
33
 * which provides no additional access restrictions.
34
 */
352366
define('FILE_DOWNLOADS_PUBLIC', 1);
36
37
/**
38
 * Flag to indicate that the 'private' file download method is enabled.
39
 *
40
 * When using this method, all file requests are served by Drupal, during
which
41
 * access-control checking can be performed.
42
 */
432366
define('FILE_DOWNLOADS_PRIVATE', 2);
44
45
/**
46
 * Flag used by file_check_directory() -- create directory if not present.
47
 */
482366
define('FILE_CREATE_DIRECTORY', 1);
49
50
/**
51
 * Flag used by file_check_directory() -- file permissions may be changed.
52
 */
532366
define('FILE_MODIFY_PERMISSIONS', 2);
54
55
/**
56
 * Flag for dealing with existing files: Appends number until name is
unique.
57
 */
582366
define('FILE_EXISTS_RENAME', 0);
59
60
/**
61
 * Flag for dealing with existing files: Replace the existing file.
62
 */
632366
define('FILE_EXISTS_REPLACE', 1);
64
65
/**
66
 * Flag for dealing with existing files: Do nothing and return FALSE.
67
 */
682366
define('FILE_EXISTS_ERROR', 2);
69
70
/**
71
 * File status -- File has been temporarily saved to the {files} tables.
72
 *
73
 * Drupal's file garbage collection will delete the file and remove it from
the
74
 * files table after a set period of time.
75
 */
762366
define('FILE_STATUS_TEMPORARY', 0);
77
78
/**
79
 * File status -- File has been permanently saved to the {files} tables.
80
 *
81
 * If you wish to add custom statuses for use by contrib modules please
expand
82
 * as binary flags and consider the first 8 bits reserved.
83
 * (0,1,2,4,8,16,32,64,128).
84
 */
852366
define('FILE_STATUS_PERMANENT', 1);
86
87
/**
88
 * Create the download path to a file.
89
 *
90
 * @param $path A string containing the path of the file to generate URL
for.
91
 * @return A string containing a URL that can be used to download the file.
92
 */
932366
function file_create_url($path) {
94
  // Strip file_directory_path from $path. We only include relative paths
in
95
  // URLs.
9614
  if (strpos($path, file_directory_path() . '/') === 0) {
9714
    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
9814
  }
9914
  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
10014
    case FILE_DOWNLOADS_PUBLIC:
10114
      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' .
str_replace('\\', '/', $path);
1020
    case FILE_DOWNLOADS_PRIVATE:
1030
      return url('system/files/' . $path, array('absolute' => TRUE));
1040
  }
1050
}
106
107
/**
108
 * Make sure the destination is a complete path and resides in the file
system
109
 * directory, if it is not prepend the file system directory.
110
 *
111
 * @param $destination
112
 *   A string containing the path to verify. If this value is omitted,
Drupal's
113
 *   'files' directory will be used.
114
 * @return
115
 *   A string containing the path to file, with file system directory
appended
116
 *   if necessary, or FALSE if the path is invalid (i.e. outside the
configured
117
 *   'files' or temp directories).
118
 */
1192366
function file_create_path($destination = NULL) {
12099
  $file_path = file_directory_path();
12199
  if (is_null($destination)) {
1222
    return $file_path;
1230
  }
124
  // file_check_location() checks whether the destination is inside the
Drupal
125
  // files directory.
12699
  if (file_check_location($destination, $file_path)) {
12714
    return $destination;
1280
  }
129
  // Check if the destination is instead inside the Drupal temporary files
130
  // directory.
13187
  elseif (file_check_location($destination, file_directory_temp())) {
1327
    return $destination;
1330
  }
134
  // Not found, try again with prefixed directory path.
13582
  elseif (file_check_location($file_path . '/' . $destination, $file_path))
{
13682
    return $file_path . '/' . $destination;
1370
  }
138
  // File not found.
1390
  return FALSE;
1400
}
141
142
/**
143
 * Check that the directory exists and is writable.
144
 *
145
 * Directories need to have execute permissions to be considered a
directory by
146
 * FTP servers, etc.
147
 *
148
 * @param $directory
149
 *   A string containing the name of a directory path.
150
 * @param $mode
151
 *   A bitmask to indicate if the directory should be created if it does
152
 *   not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
153
 *   (FILE_MODIFY_PERMISSIONS).
154
 * @param $form_item
155
 *   An optional string containing the name of a form item that any errors
will
156
 *   be attached to. This is useful for settings forms that require the
user to
157
 *   specify a writable directory. If it can't be made to work, a form
error
158
 *   will be set preventing them from saving the settings.
159
 * @return
160
 *   FALSE when directory not found, or TRUE when directory exists.
161
 */
1622366
function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
163149
  $directory = rtrim($directory, '/\\');
164
165
  // Check if directory exists.
166149
  if (!is_dir($directory)) {
167137
    if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
168134
      @chmod($directory, 0775); // Necessary for non-webserver users.
169134
    }
170
    else {
17113
      if ($form_item) {
1721
        form_set_error($form_item, t('The directory %directory does not
exist.', array('%directory' => $directory)));
1731
        watchdog('file system', 'The directory %directory does not exist.',
array('%directory' => $directory), WATCHDOG_ERROR);
1741
      }
17513
      return FALSE;
176
    }
177134
  }
178
179
  // Check to see if the directory is writable.
180149
  if (!is_writable($directory)) {
181
    // If not able to modify permissions, or if able to, but chmod
182
    // fails, return false.
1831
    if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory,
0775))) {
1841
      if ($form_item) {
1851
        form_set_error($form_item, t('The directory %directory is not
writable', array('%directory' => $directory)));
1861
        watchdog('file system', 'The directory %directory is not writable,
because it does not have the correct permissions set.', array('%directory'
=> $directory), WATCHDOG_ERROR);
1871
      }
1881
      return FALSE;
1890
    }
1901
  }
191
192149
  if ((file_directory_path() == $directory || file_directory_temp() ==
$directory) && !is_file("$directory/.htaccess")) {
193135
    $htaccess_lines = "SetHandler
Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions
+FollowSymLinks";
194135
    if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp,
$htaccess_lines)) {
195135
      fclose($fp);
196135
      chmod($directory . '/.htaccess', 0664);
197135
    }
198
    else {
1990
      $variables = array('%directory' => $directory, '!htaccess' => '<br
/>' . nl2br(check_plain($htaccess_lines)));
2000
      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));
2010
      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);
202
    }
203135
  }
204
205149
  return TRUE;
2060
}
207
208
/**
209
 * Checks path to see if it is a directory, or a directory/file.
210
 *
211
 * @param $path
212
 *   A string containing a file path. This will be set to the directory's
path.
213
 * @return
214
 *   If the directory is not in a Drupal writable directory, FALSE is
returned.
215
 *   Otherwise, the base name of the path is returned.
216
 */
2172366
function file_check_path(&$path) {
218
  // Check if path is a directory.
21917
  if (file_check_directory($path)) {
2208
    return '';
2210
  }
222
223
  // Check if path is a possible dir/file.
22412
  $filename = basename($path);
22512
  $path = dirname($path);
22612
  if (file_check_directory($path)) {
22712
    return $filename;
2280
  }
229
2300
  return FALSE;
2310
}
232
233
/**
234
 * Check if a file is really located inside $directory.
235
 *
236
 * This should be used to make sure a file specified is really located
within
237
 * the directory to prevent exploits. Note that the file or path being
checked
238
 * does not actually need to exist yet.
239
 *
240
 * @code
241
 *   // Returns FALSE:
242
 *   file_check_location('/www/example.com/files/../../../etc/passwd',
'/www/example.com/files');
243
 * @endcode
244
 *
245
 * @param $source
246
 *   A string set to the file to check.
247
 * @param $directory
248
 *   A string where the file should be located.
249
 * @return
250
 *   FALSE if the path does not exist in the directory; otherwise, the real
251
 *   path of the source.
252
 */
2532366
function file_check_location($source, $directory = '') {
254100
  $check = realpath($source);
255100
  if ($check) {
256100
    $source = $check;
257100
  }
258
  else {
259
    // This file does not yet exist.
2600
    $source = realpath(dirname($source)) . '/' . basename($source);
261
  }
262100
  $directory = realpath($directory);
263100
  if ($directory && strpos($source, $directory) !== 0) {
26488
    return FALSE;
2650
  }
266100
  return $source;
2670
}
268
269
/**
270
 * Load a file object from the database.
271
 *
272
 * @param $param
273
 *   Either the id of a file or an array of conditions to match against in
the
274
 *   database query.
275
 * @param $reset
276
 *   Whether to reset the internal file_load cache.
277
 * @return
278
 *   A file object.
279
 *
280
 * @see hook_file_load()
281
 */
2822366
function file_load($param, $reset = NULL) {
28318
  static $files = array();
284
28518
  if ($reset) {
2863
    $files = array();
2873
  }
288
28918
  if (is_numeric($param)) {
29017
    if (isset($files[(string) $param])) {
2911
      return is_object($files[$param]) ? clone $files[$param] :
$files[$param];
2920
    }
29317
    $result = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid',
array(':fid' => $param));
29417
  }
2952
  elseif (is_array($param)) {
296
    // Turn the conditions into a query.
2972
    $cond = array();
2982
    $arguments = array();
2992
    foreach ($param as $key => $value) {
3002
      $cond[] = 'f.' . db_escape_table($key) . " = '%s'";
3012
      $arguments[] = $value;
3022
    }
3032
    $result = db_query('SELECT f.* FROM {files} f WHERE ' . implode(' AND
', $cond), $arguments);
3042
  }
305
  else {
3060
    return FALSE;
307
  }
30818
  $file = $result->fetch(PDO::FETCH_OBJ);
309
31018
  if ($file && $file->fid) {
311
    // Allow modules to add or change the file object.
31217
    module_invoke_all('file_load', $file);
313
314
    // Cache the fully loaded value.
31517
    $files[(string) $file->fid] = clone $file;
31617
  }
317
31818
  return $file;
3190
}
320
321
/**
322
 * Save a file object to the database.
323
 *
324
 * If the $file->fid is not set a new record will be added. Re-saving an
325
 * existing file will not change its status.
326
 *
327
 * @param $file
328
 *   A file object returned by file_load().
329
 * @return
330
 *   The updated file object.
331
 *
332
 * @see hook_file_insert()
333
 * @see hook_file_update()
334
 */
3352366
function file_save($file) {
33615
  $file = (object)$file;
33715
  $file->timestamp = REQUEST_TIME;
33815
  $file->filesize = filesize($file->filepath);
339
34015
  if (empty($file->fid)) {
34114
    drupal_write_record('files', $file);
342
    // Inform modules about the newly added file.
34314
    module_invoke_all('file_insert', $file);
34414
  }
345
  else {
3462
    drupal_write_record('files', $file, 'fid');
347
    // Inform modules that the file has been updated.
3482
    module_invoke_all('file_update', $file);
349
  }
350
35115
  return $file;
3520
}
353
354
/**
355
 * Copy a file to a new location and adds a file record to the database.
356
 *
357
 * This function should be used when manipulating files that have records
358
 * stored in the database. This is a powerful function that in many ways
359
 * performs like an advanced version of copy().
360
 * - Checks if $source and $destination are valid and readable/writable.
361
 * - Checks that $source is not equal to $destination; if they are an error
362
 *   is reported.
363
 * - If file already exists in $destination either the call will error out,
364
 *   replace the file or rename the file based on the $replace parameter.
365
 * - Adds the new file to the files database. If the source file is a
366
 *   temporary file, the resulting file will also be a temporary file.
367
 *   @see file_save_upload about temporary files.
368
 *
369
 * @param $source
370
 *   A file object.
371
 * @param $destination
372
 *   A string containing the directory $source should be copied to. If this
373
 *   value is omitted, Drupal's 'files' directory will be used.
374
 * @param $replace
375
 *   Replace behavior when the destination file already exists:
376
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
377
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
378
 *                          unique.
379
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
380
 * @return
381
 *   File object if the copy is successful, or FALSE in the event of an
error.
382
 *
383
 * @see file_unmanaged_copy()
384
 * @see hook_file_copy()
385
 */
3862366
function file_copy($source, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
3871
  $source = (object)$source;
388
3891
  if ($filepath = file_unmanaged_copy($source->filepath, $destination,
$replace)) {
3901
    $file = clone $source;
3911
    $file->fid      = NULL;
3921
    $file->filename = basename($filepath);
3931
    $file->filepath = $filepath;
3941
    if ($file = file_save($file)) {
395
      // Inform modules that the file has been copied.
3961
      module_invoke_all('file_copy', $file, $source);
3971
      return $file;
3980
    }
3990
  }
4000
  return FALSE;
4010
}
402
403
/**
404
 * Copy a file to a new location without calling any hooks or making any
405
 * changes to the database.
406
 *
407
 * This is a powerful function that in many ways performs like an advanced
408
 * version of copy().
409
 * - Checks if $source and $destination are valid and readable/writable.
410
 * - Checks that $source is not equal to $destination; if they are an error
411
 *   is reported.
412
 * - If file already exists in $destination either the call will error out,
413
 *   replace the file or rename the file based on the $replace parameter.
414
 *
415
 * @param $source
416
 *   A string specifying the file location of the original file.
417
 * @param $destination
418
 *   A string containing the directory $source should be copied to. If this
419
 *   value is omitted, Drupal's 'files' directory will be used.
420
 * @param $replace
421
 *   Replace behavior when the destination file already exists:
422
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
423
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
424
 *                          unique.
425
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
426
 * @return
427
 *   The path to the new file, or FALSE in the event of an error.
428
 *
429
 * @see file_copy()
430
 */
4312366
function file_unmanaged_copy($source, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
43212
  $source = realpath($source);
43312
  if (!file_exists($source)) {
4342
    drupal_set_message(t('The specified 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');
4352
    return FALSE;
4360
  }
437
43812
  $destination = file_create_path($destination);
43912
  $directory = $destination;
44012
  $basename = file_check_path($directory);
441
442
  // Make sure we at least have a valid directory.
44312
  if ($basename === FALSE) {
4440
    drupal_set_message(t('The specified file %file could not be copied,
because the destination %directory is not properly configured.',
array('%file' => $source, '%directory' => $destination)), 'error');
4450
    return FALSE;
4460
  }
447
448
  // If the destination file is not specified then use the filename of the
449
  // source file.
45012
  $basename = $basename ? $basename : basename($source);
45112
  $destination = file_destination($directory . '/' . $basename, $replace);
452
45312
  if ($destination === FALSE) {
4542
    drupal_set_message(t('The specified file %file could not be copied
because a file by that name already exists in the destination.',
array('%file' => $source)), 'error');
4552
    return FALSE;
4560
  }
457
  // Make sure source and destination filenames are not the same, makes no
458
  // sense to copy it if they are. In fact copying the file will most
likely
459
  // result in a 0 byte file. Which is bad. Real bad.
46012
  if ($source == realpath($destination)) {
4611
    drupal_set_message(t('The specified file %file was not copied because
it would overwrite itself.', array('%file' => $source)), 'error');
4621
    return FALSE;
4630
  }
46412
  if (!@copy($source, $destination)) {
4650
    drupal_set_message(t('The specified file %file could not be copied.',
array('%file' => $source)), 'error');
4660
    return FALSE;
4670
  }
468
469
  // Give everyone read access so that FTP'd users or
470
  // non-webserver users can see/read these files,
471
  // and give group write permissions so group members
472
  // can alter files uploaded by the webserver.
47312
  @chmod($destination, 0664);
474
47512
  return $destination;
4760
}
477
478
/**
479
 * Determines the destination path for a file depending on how replacement
of
480
 * existing files should be handled.
481
 *
482
 * @param $destination
483
 *   A string specifying the desired path.
484
 * @param $replace
485
 *   Replace behavior when the destination file already exists.
486
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
487
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
488
 *                          unique.
489
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
490
 * @return
491
 *   The destination file path or FALSE if the file already exists and
492
 *   FILE_EXISTS_ERROR was specified.
493
 */
4942366
function file_destination($destination, $replace) {
49523
  if (file_exists($destination)) {
496
    switch ($replace) {
4978
      case FILE_EXISTS_REPLACE:
498
        // Do nothing here, we want to overwrite the existing file.
4992
        break;
500
5018
      case FILE_EXISTS_RENAME:
5027
        $basename = basename($destination);
5037
        $directory = dirname($destination);
5047
        $destination = file_create_filename($basename, $directory);
5057
        break;
506
5073
      case FILE_EXISTS_ERROR:
5083
        drupal_set_message(t('The specified file %file could not be copied,
because a file by that name already exists in the destination.',
array('%file' => $destination)), 'error');
5093
        return FALSE;
5100
    }
5117
  }
51223
  return $destination;
5130
}
514
515
/**
516
 * Move a file to a new location and update the file's database entry.
517
 *
518
 * Moving a file is performed by copying the file to the new location and
then
519
 * deleting the original.
520
 * - Checks if $source and $destination are valid and readable/writable.
521
 * - Performs a file move if $source is not equal to $destination.
522
 * - If file already exists in $destination either the call will error out,
523
 *   replace the file or rename the file based on the $replace parameter.
524
 * - Adds the new file to the files database.
525
 *
526
 * @param $source
527
 *   A file object.
528
 * @param $destination
529
 *   A string containing the directory $source should be copied to. If this
530
 *   value is omitted, Drupal's 'files' directory will be used.
531
 * @param $replace
532
 *   Replace behavior when the destination file already exists:
533
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
534
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
535
 *                          unique.
536
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
537
 * @return
538
 *   Resulting file object for success, or FALSE in the event of an error.
539
 *
540
 * @see file_unmanaged_move()
541
 * @see hook_file_move()
542
 */
5432366
function file_move($source, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
5441
  $source = (object)$source;
545
5461
  if ($filepath = file_unmanaged_move($source->filepath, $destination,
$replace)) {
5471
    $file = clone $source;
5481
    $file->filename = basename($filepath);
5491
    $file->filepath = $filepath;
5501
    if ($file = file_save($file)) {
551
      // Inform modules that the file has been moved.
5521
      module_invoke_all('file_move', $file, $source);
5531
      return $file;
5540
    }
5550
    drupal_set_message(t('The removal of the original file %file has
failed.', array('%file' => $source->filepath)), 'error');
5560
  }
5570
  return FALSE;
5580
}
559
560
/**
561
 * Move a file to a new location without calling any hooks or making any
562
 * changes to the database.
563
 *
564
 * @param $source
565
 *   A string specifying the file location of the original file.
566
 * @param $destination
567
 *   A string containing the directory $source should be copied to. If this
568
 *   value is omitted, Drupal's 'files' directory will be used.
569
 * @param $replace
570
 *   Replace behavior when the destination file already exists:
571
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
572
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
573
 *                          unique.
574
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
575
 * @return
576
 *   The filepath of the moved file, or FALSE in the event of an error.
577
 *
578
 * @see file_move()
579
 */
5802366
function file_unmanaged_move($source, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
5818
  $filepath = file_unmanaged_copy($source, $destination, $replace);
5828
  if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
5832
    return FALSE;
5840
  }
5858
  return $filepath;
5860
}
587
588
/**
589
 * Munge the filename as needed for security purposes.
590
 *
591
 * For instance the file name "exploit.php.pps" would become
"exploit.php_.pps".
592
 *
593
 * @param $filename
594
 *   The name of a file to modify.
595
 * @param $extensions
596
 *   A space separated list of extensions that should not be altered.
597
 * @param $alerts
598
 *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
599
 * @return
600
 *   $filename The potentially modified $filename.
601
 */
6022366
function file_munge_filename($filename, $extensions, $alerts = TRUE) {
60312
  $original = $filename;
604
605
  // Allow potentially insecure uploads for very savvy users and admin
60612
  if (!variable_get('allow_insecure_uploads', 0)) {
60712
    $whitelist = array_unique(explode(' ', trim($extensions)));
608
609
    // Split the filename up by periods. The first part becomes the
basename
610
    // the last part the final extension.
61112
    $filename_parts = explode('.', $filename);
61212
    $new_filename = array_shift($filename_parts); // Remove file basename.
61312
    $final_extension = array_pop($filename_parts); // Remove final
extension.
614
615
    // Loop through the middle parts of the name and add an underscore to
the
616
    // end of each section that could be a file extension but isn't in the
list
617
    // of allowed extensions.
61812
    foreach ($filename_parts as $filename_part) {
6190
      $new_filename .= '.' . $filename_part;
6200
      if (!in_array($filename_part, $whitelist) &&
preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
6210
        $new_filename .= '_';
6220
      }
6230
    }
62412
    $filename = $new_filename . '.' . $final_extension;
625
62612
    if ($alerts && $original != $filename) {
6270
      drupal_set_message(t('For security reasons, your upload has been
renamed to %filename.', array('%filename' => $filename)));
6280
    }
62912
  }
630
63112
  return $filename;
6320
}
633
634
/**
635
 * Undo the effect of upload_munge_filename().
636
 *
637
 * @param $filename
638
 *   String with the filename to be unmunged.
639
 * @return
640
 *   An unmunged filename string.
641
 */
6422366
function file_unmunge_filename($filename) {
6430
  return str_replace('_.', '.', $filename);
6440
}
645
646
/**
647
 * Create a full file path from a directory and filename.
648
 *
649
 * If a file with the specified name already exists, an alternative will be
650
 * used.
651
 *
652
 * @param $basename
653
 *   String filename
654
 * @param $directory
655
 *   String directory
656
 * @return
657
 *   File path consisting of $directory and a unique filename based off
658
 *   of $basename.
659
 */
6602366
function file_create_filename($basename, $directory) {
6617
  $destination = $directory . '/' . $basename;
662
6637
  if (file_exists($destination)) {
664
    // Destination file already exists, generate an alternative.
6657
    $pos = strrpos($basename, '.');
6667
    if ($pos !== FALSE) {
6675
      $name = substr($basename, 0, $pos);
6685
      $ext = substr($basename, $pos);
6695
    }
670
    else {
6712
      $name = $basename;
6722
      $ext = '';
673
    }
674
6757
    $counter = 0;
676
    do {
6777
      $destination = $directory . '/' . $name . '_' . $counter++ . $ext;
6787
    } while (file_exists($destination));
6797
  }
680
6817
  return $destination;
6820
}
683
684
/**
685
 * Delete a file and its database record.
686
 *
687
 * If the $force parameter is not TRUE hook_file_references() will be
called
688
 * to determine if the file is being used by any modules. If the file is
being
689
 * used is the delete will be canceled.
690
 *
691
 * @param $file
692
 *   A file object.
693
 * @param $force
694
 *   Boolean indicating that the file should be deleted even if
695
 *   hook_file_references() reports that the file is in use.
696
 * @return mixed
697
 *   TRUE for success, FALSE in the event of an error, or an array if the
file
698
 *   is being used by another module. The array keys are the module's name
and
699
 *   the values are the number of references.
700
 *
701
 * @see file_unmanaged_delete()
702
 * @see hook_file_references()
703
 * @see hook_file_delete()
704
 */
7052366
function file_delete($file, $force = FALSE) {
7062
  $file = (object)$file;
707
708
  // If any module returns a value from the reference hook, the file will
not
709
  // be deleted from Drupal, but file_delete will return a populated array
that
710
  // tests as TRUE.
7112
  if (!$force && ($references = module_invoke_all('file_references',
$file))) {
7120
    return $references;
7130
  }
714
715
  // Let other modules clean up any references to the deleted file.
7162
  module_invoke_all('file_delete', $file);
717
718
  // Make sure the file is deleted before removing its row from the
719
  // database, so UIs can still find the file in the database.
7202
  if (file_unmanaged_delete($file->filepath)) {
7212
    db_delete('files')->condition('fid', $file->fid)->execute();
7222
    return TRUE;
7230
  }
7240
  return FALSE;
7250
}
726
727
/**
728
 * Delete a file without calling any hooks or making any changes to the
729
 * database.
730
 *
731
 * This function should be used when the file to be deleted does not have
an
732
 * entry recorded in the files table.
733
 *
734
 * @param $path
735
 *   A string containing a file path.
736
 * @return
737
 *   TRUE for success or path does not exist, or FALSE in the event of an
738
 *   error.
739
 *
740
 * @see file_delete()
741
 */
7422366
function file_unmanaged_delete($path) {
743136
  if (is_dir($path)) {
7441
    watchdog('file', '%path is a directory and cannot be removed using
file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
7451
    return FALSE;
7460
  }
747136
  if (is_file($path)) {
748136
    return unlink($path);
7490
  }
750
  // Return TRUE for non-existant file, but log that nothing was actually
751
  // deleted, as the current state is the indended result.
7521
  if (!file_exists($path)) {
7531
    watchdog('file', 'The file %path was not deleted, because it does not
exist.', array('%path' => $path), WATCHDOG_NOTICE);
7541
    return TRUE;
7550
  }
756
  // Catch all for everything else: sockets, symbolic links, etc.
7570
  return FALSE;
7580
}
759
760
/**
761
 * Determine total disk space used by a single user or the whole
filesystem.
762
 *
763
 * @param $uid
764
 *   Optional. A user id, specifying NULL returns the total space used by
all
765
 *   non-temporary files.
766
 * @param $status
767
 *   Optional. File Status to return. Combine with a bitwise OR(|) to
return
768
 *   multiple statuses. The default status is FILE_STATUS_PERMANENT.
769
 * @return
770
 *   An integer containing the number of bytes used.
771
 */
7722366
function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
7736
  if (!is_null($uid)) {
7746
    return db_query('SELECT SUM(filesize) FROM {files} WHERE uid = :uid AND
status & :status', array(':uid' => $uid, ':status' =>
$status))->fetchField();
7750
  }
7760
  return db_query('SELECT SUM(filesize) FROM {files} WHERE status &
:status', array(':status' => $status))->fetchField();
7770
}
778
779
/**
780
 * Saves a file upload to a new location.
781
 *
782
 * The file will be added to the files table as a temporary file. Temporary
783
 * files are periodically cleaned. To make the file permanent file call
784
 * file_set_status() to change its status.
785
 *
786
 * @param $source
787
 *   A string specifying the name of the upload field to save.
788
 * @param $validators
789
 *   An optional, associative array of callback functions used to validate
the
790
 *   file. See @file_validate for a full discussion of the array format.
791
 * @param $destination
792
 *   A string containing the directory $source should be copied to. If this
is
793
 *   not provided or is not writable, the temporary directory will be used.
794
 * @param $replace
795
 *   A boolean indicating whether an existing file of the same name in the
796
 *   destination directory should overwritten. A false value will generate
a
797
 *   new, unique filename in the destination directory.
798
 * @return
799
 *   An object containing the file information, or FALSE in the event of an
800
 *   error.
801
 */
8022366
function file_save_upload($source, $validators = array(), $destination =
FALSE, $replace = FILE_EXISTS_RENAME) {
80315
  global $user;
80415
  static $upload_cache;
805
806
  // Return cached objects without processing since the file will have
807
  // already been processed and the paths in _FILES will be invalid.
80815
  if (isset($upload_cache[$source])) {
8090
    return $upload_cache[$source];
8100
  }
811
812
  // Add in our check of the the file name length.
81315
  $validators['file_validate_name_length'] = array();
814
815
816
  // If a file was uploaded, process it.
81715
  if (isset($_FILES['files']) && $_FILES['files']['name'][$source] &&
is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
818
    // Check for file upload errors and return FALSE if a lower level
system
819
    // error occurred.
82012
    switch ($_FILES['files']['error'][$source]) {
821
      // @see http://php.net/manual/en/features.file-upload.errors.php
82212
      case UPLOAD_ERR_OK:
82312
        break;
824
8250
      case UPLOAD_ERR_INI_SIZE:
8260
      case UPLOAD_ERR_FORM_SIZE:
8270
        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');
8280
        return FALSE;
829
8300
      case UPLOAD_ERR_PARTIAL:
8310
      case UPLOAD_ERR_NO_FILE:
8320
        drupal_set_message(t('The file %file could not be saved, because
the upload did not complete.', array('%file' => $source)), 'error');
8330
        return FALSE;
834
835
        // Unknown error
8360
      default:
8370
        drupal_set_message(t('The file %file could not be saved. An unknown
error has occurred.', array('%file' => $source)), 'error');
8380
        return FALSE;
8390
    }
840
841
    // Build the list of non-munged extensions.
842
    // @todo: this should not be here. we need to figure out the right
place.
84312
    $extensions = '';
84412
    foreach ($user->roles as $rid => $name) {
84512
      $extensions .= ' ' . variable_get("upload_extensions_$rid",
84612
      variable_get('upload_extensions_default', 'jpg jpeg gif png txt html
doc xls pdf ppt pps odt ods odp'));
84712
    }
848
849
    // Begin building file object.
85012
    $file = new stdClass();
85112
    $file->uid      = $user->uid;
85212
    $file->status   = FILE_STATUS_TEMPORARY;
85312
    $file->filename =
file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'),
$extensions);
85412
    $file->filepath = $_FILES['files']['tmp_name'][$source];
85512
    $file->filemime = file_get_mimetype($file->filename);
85612
    $file->filesize = $_FILES['files']['size'][$source];
857
858
    // Rename potentially executable files, to help prevent exploits.
85912
    if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) &&
(substr($file->filename, -4) != '.txt')) {
8600
      $file->filemime = 'text/plain';
8610
      $file->filepath .= '.txt';
8620
      $file->filename .= '.txt';
8630
    }
864
865
    // If the destination is not provided, or is not writable, then use the
866
    // temporary directory.
86712
    if (empty($destination) || file_check_path($destination) === FALSE) {
8687
      $destination = file_directory_temp();
8697
    }
870
87112
    $file->source = $source;
87212
    $file->destination = file_destination(file_create_path($destination .
'/' . $file->filename), $replace);
873
874
    // Call the validation functions specified by this function's caller.
87512
    $errors = file_validate($file, $validators);
876
877
    // Check for errors.
87812
    if (!empty($errors)) {
8794
      $message = t('The specified file %name could not be uploaded.',
array('%name' => $file->filename));
8804
      if (count($errors) > 1) {
8810
        $message .= theme('item_list', $errors);
8820
      }
883
      else {
8844
        $message .= ' ' . array_pop($errors);
885
      }
8864
      form_set_error($source, $message);
8874
      return FALSE;
8880
    }
889
890
    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
891
    // directory. This overcomes open_basedir restrictions for future file
892
    // operations.
8938
    $file->filepath = $file->destination;
8948
    if (!move_uploaded_file($_FILES['files']['tmp_name'][$source],
$file->filepath)) {
8950
      form_set_error($source, t('File upload error. Could not move uploaded
file.'));
8960
      watchdog('file', 'Upload error. Could not move uploaded file %file to
destination %destination.', array('%file' => $file->filename,
'%destination' => $file->filepath));
8970
      return FALSE;
8980
    }
899
900
    // If we made it this far it's safe to record this file in the
database.
9018
    if ($file = file_save($file)) {
902
      // Add file to the cache.
9038
      $upload_cache[$source] = $file;
9048
      return $file;
9050
    }
9060
  }
9073
  return FALSE;
9080
}
909
910
911
/**
912
 * Check that a file meets the criteria specified by the validators.
913
 *
914
 * After executing the validator callbacks specified hook_file_validate()
will
915
 * also be called to allow other modules to report errors about the file.
916
 *
917
 * @param $file
918
 *   A Drupal file object.
919
 * @param $validators
920
 *   An optional, associative array of callback functions used to validate
the
921
 *   file. The keys are function names and the values arrays of callback
922
 *   parameters which will be passed in after the user and file objects.
The
923
 *   functions should return an array of error messages, an empty array
924
 *   indicates that the file passed validation. The functions will be
called in
925
 *   the order specified.
926
 * @return
927
 *   An array contaning validation error messages.
928
 *
929
 * @see hook_file_validate()
930
 */
9312366
function file_validate(&$file, $validators = array()) {
932
  // Call the validation functions specified by this function's caller.
93313
  $errors = array();
93413
  foreach ($validators as $function => $args) {
93513
    array_unshift($args, $file);
93613
    $errors = array_merge($errors, call_user_func_array($function, $args));
93713
  }
938
939
  // Let other modules perform validation on the new file.
94013
  return array_merge($errors, module_invoke_all('file_validate', $file));
9410
}
942
943
/**
944
 * Check for files with names longer than we can store in the database.
945
 *
946
 * @param $file
947
 *   A Drupal file object.
948
 * @return
949
 *   An array. If the file name is too long, it will contain an error
message.
950
 */
9512366
function file_validate_name_length($file) {
95213
  $errors = array();
953
95413
  if (empty($file->filename)) {
9551
    $errors[] = t("The file's name is empty. Please give a name to the
file.");
9561
  }
95713
  if (strlen($file->filename) > 255) {
9581
    $errors[] = t("The file's name exceeds the 255 characters limit. Please
rename the file and try again.");
9591
  }
96013
  return $errors;
9610
}
962
963
/**
964
 * Check that the filename ends with an allowed extension.
965
 *
966
 * @param $file
967
 *   A Drupal file object.
968
 * @param $extensions
969
 *   A string with a space separated
970
 * @return
971
 *   An array. If the file extension is not allowed, it will contain an
error
972
 *   message.
973
 *
974
 * @see hook_file_validate()
975
 */
9762366
function file_validate_extensions($file, $extensions) {
9776
  global $user;
978
9796
  $errors = array();
980
9816
  $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) .
')$/i';
9826
  if (!preg_match($regex, $file->filename)) {
9832
    $errors[] = t('Only files with the following extensions are allowed:
%files-allowed.', array('%files-allowed' => $extensions));
9842
  }
9856
  return $errors;
9860
}
987
988
/**
989
 * Check that the file's size is below certain limits.
990
 *
991
 * This check is not enforced for the user #1.
992
 *
993
 * @param $file
994
 *   A Drupal file object.
995
 * @param $file_limit
996
 *   An integer specifying the maximum file size in bytes. Zero indicates
that
997
 *   no limit should be enforced.
998
 * @param $user_limit
999
 *   An integer specifying the maximum number of bytes the user is allowed.
1000
 *   Zero indicates that no limit should be enforced.
1001
 * @return
1002
 *   An array. If the file size exceeds limits, it will contain an error
1003
 *   message.
1004
 *
1005
 * @see hook_file_validate()
1006
 */
10072366
function file_validate_size($file, $file_limit = 0, $user_limit = 0) {
100810
  global $user;
1009
101010
  $errors = array();
1011
1012
  // Bypass validation for uid  = 1.
101310
  if ($user->uid != 1) {
101410
    if ($file_limit && $file->filesize > $file_limit) {
10153
      $errors[] = t('The file is %filesize exceeding the maximum file size
of %maxsize.', array('%filesize' => format_size($file->filesize),
'%maxsize' => format_size($file_limit)));
10163
    }
1017
101810
    if ($user_limit && (file_space_used($user->uid) + $file->filesize) >
$user_limit) {
10191
      $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)));
10201
    }
102110
  }
102210
  return $errors;
10230
}
1024
1025
/**
1026
 * Check that the file is recognized by image_get_info() as an image.
1027
 *
1028
 * @param $file
1029
 *   A Drupal file object.
1030
 * @return
1031
 *   An array. If the file is not an image, it will contain an error
message.
1032
 *
1033
 * @see hook_file_validate()
1034
 */
10352366
function file_validate_is_image(&$file) {
10366
  $errors = array();
1037
10386
  $info = image_get_info($file->filepath);
10396
  if (!$info || empty($info['extension'])) {
10402
    $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
10412
  }
1042
10436
  return $errors;
10440
}
1045
1046
/**
1047
 * If the file is an image verify that its dimensions are within the
specified
1048
 * maximum and minimum dimensions.
1049
 *
1050
 * Non-image files will be ignored. If a image toolkit is available the
image
1051
 * will be scalled to fit within the desired maximum dimensions.
1052
 *
1053
 * @param $file
1054
 *   A Drupal file object. This function may resize the file affecting its
1055
 *   size.
1056
 * @param $maximum_dimensions
1057
 *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'.
If
1058
 *   an image toolkit is installed the image will be resized down to these
1059
 *   dimensions. A value of 0 indicates no restriction on size, so resizing
1060
 *   will be attempted.
1061
 * @param $minimum_dimensions
1062
 *   An optional string in the form WIDTHxHEIGHT. This will check that the
1063
 *   image meets a minimum size. A value of 0 indicates no restriction.
1064
 * @return
1065
 *   An array. If the file is an image and did not meet the requirements,
it
1066
 *   will contain an error message.
1067
 *
1068
 * @see hook_file_validate()
1069
 */
10702366
function file_validate_image_resolution(&$file, $maximum_dimensions = 0,
$minimum_dimensions = 0) {
107110
  $errors = array();
1072
1073
  // Check first that the file is an image.
107410
  if ($info = image_get_info($file->filepath)) {
10754
    if ($maximum_dimensions) {
1076
      // Check that it is smaller than the given dimensions.
10774
      list($width, $height) = explode('x', $maximum_dimensions);
10784
      if ($info['width'] > $width || $info['height'] > $height) {
1079
        // Try to resize the image to fit the dimensions.
10802
        if (image_get_toolkit() && image_scale($file->filepath,
$file->filepath, $width, $height)) {
10812
          drupal_set_message(t('The image was resized to fit within the
maximum allowed dimensions of %dimensions pixels.', array('%dimensions' =>
$maximum_dimensions)));
1082
1083
          // Clear the cached filesize and refresh the image information.
10842
          clearstatcache();
10852
          $info = image_get_info($file->filepath);
10862
          $file->filesize = $info['file_size'];
10872
        }
1088
        else {
10890
          $errors[] = t('The image is too large; the maximum dimensions are
%dimensions pixels.', array('%dimensions' => $maximum_dimensions));
1090
        }
10912
      }
10924
    }
1093
10944
    if ($minimum_dimensions) {
1095
      // Check that it is larger than the given dimensions.
10961
      list($width, $height) = explode('x', $minimum_dimensions);
10971
      if ($info['width'] < $width || $info['height'] < $height) {
10981
        $errors[] = t('The image is too small; the minimum dimensions are
%dimensions pixels.', array('%dimensions' => $minimum_dimensions));
10991
      }
11001
    }
11014
  }
1102
110310
  return $errors;
11040
}
1105
1106
/**
1107
 * Save a string to the specified destination and create a database file
entry.
1108
 *
1109
 * @param $data
1110
 *   A string containing the contents of the file.
1111
 * @param $destination
1112
 *   A string containing the destination location. If no value is provided
1113
 *   then a randomly name will be generated and the file saved in Drupal's
1114
 *   files directory.
1115
 * @param $replace
1116
 *   Replace behavior when the destination file already exists:
1117
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
1118
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
1119
 *                          unique.
1120
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1121
 * @return
1122
 *   A file object, or FALSE on error.
1123
 *
1124
 * @see file_unmanaged_save_data()
1125
 */
11262366
function file_save_data($data, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
11272
  global $user;
1128
11292
  if ($filepath = file_unmanaged_save_data($data, $destination, $replace))
{
1130
    // Create a file object.
11312
    $file = new stdClass();
11322
    $file->filepath = $filepath;
11332
    $file->filename = basename($file->filepath);
11342
    $file->filemime = file_get_mimetype($file->filepath);
11352
    $file->uid      = $user->uid;
11362
    $file->status   = FILE_STATUS_PERMANENT;
11372
    return file_save($file);
11380
  }
11391
  return FALSE;
11400
}
1141
1142
/**
1143
 * Save a string to the specified destination without calling any hooks or
1144
 * making any changes to the database.
1145
 *
1146
 * This function is identical to file_save_data() except the file will not
be
1147
 * saved to the files table and none of the file_* hooks will be called.
1148
 *
1149
 * @param $data
1150
 *   A string containing the contents of the file.
1151
 * @param $destination
1152
 *   A string containing the destination location. If no value is provided
1153
 *   then a randomly name will be generated and the file saved in Drupal's
1154
 *   files directory.
1155
 * @param $replace
1156
 *   Replace behavior when the destination file already exists:
1157
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
1158
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the
filename is
1159
 *                          unique.
1160
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1161
 * @return
1162
 *   A string with the path of the resulting file, or FALSE on error.
1163
 *
1164
 * @see file_save_data()
1165
 */
11662366
function file_unmanaged_save_data($data, $destination = NULL, $replace =
FILE_EXISTS_RENAME) {
1167
  // Write the data to a temporary file.
11686
  $temp_name = tempnam(file_directory_temp(), 'file');
11696
  if (file_put_contents($temp_name, $data) === FALSE) {
11700
    drupal_set_message(t('The file could not be created.'), 'error');
11710
    return FALSE;
11720
  }
1173
1174
  // Move the file to its final destination.
11756
  return file_unmanaged_move($temp_name, $destination, $replace);
11760
}
1177
1178
/**
1179
 * Set the status of a file.
1180
 *
1181
 * @param $file
1182
 *   A Drupal file object.
1183
 * @param $status
1184
 *   A status value to set the file to.
1185
 *   - FILE_STATUS_TEMPORARY - A temporary file that Drupal's garbage
1186
 *                             collection will remove.
1187
 *   - FILE_STATUS_PERMANENT - A permanent file that Drupal's garbage
1188
 *                             collection will not remove.
1189
 * @return
1190
 *   File object if the change is successful, or FALSE in the event of an
1191
 *   error.
1192
 *
1193
 * @see hook_file_status()
1194
 */
11952366
function file_set_status($file, $status = FILE_STATUS_PERMANENT) {
11966
  $file = (object)$file;
1197
11986
  $num_updated = db_update('files')
11996
    ->fields(array('status' => $status))
12006
    ->condition('fid', $file->fid)
12016
    ->execute();
1202
12036
  if ($num_updated) {
12044
    $file->status = $status;
1205
    // Notify other modules that the file's status has changed.
12064
    module_invoke_all('file_status', $file);
12074
    return $file;
12080
  }
12094
  return FALSE;
12100
}
1211
1212
/**
1213
 * Transfer file using HTTP to client. Pipes a file through Drupal to the
1214
 * client.
1215
 *
1216
 * @param $source
1217
 *   String specifying the file path to transfer.
1218
 * @param $headers
1219
 *   An array of HTTP headers to send along with file.
1220
 */
12212366
function file_transfer($source, $headers) {
12220
  if (ob_get_level()) {
12230
    ob_end_clean();
12240
  }
1225
12260
  foreach ($headers as $header) {
1227
    // To prevent HTTP header injection, we delete new lines that are
1228
    // not followed by a space or a tab.
1229
    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
12300
    $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
12310
    drupal_set_header($header);
12320
  }
1233
12340
  $source = file_create_path($source);
1235
1236
  // Transfer file in 1024 byte chunks to save memory usage.
12370
  if ($fd = fopen($source, 'rb')) {
12380
    while (!feof($fd)) {
12390
      print fread($fd, 1024);
12400
    }
12410
    fclose($fd);
12420
  }
1243
  else {
12440
    drupal_not_found();
1245
  }
12460
  exit();
12470
}
1248
1249
/**
1250
 * Menu handler for private file transfers.
1251
 *
1252
 * Call modules that implement hook_file_download() to find out if a file
is
1253
 * accessible and what headers it should be transferred with. If a module
1254
 * returns -1 drupal_access_denied() will be returned. If one or more
modules
1255
 * returned headers the download will start with the returned headers. If
no
1256
 * modules respond drupal_not_found() will be returned.
1257
 *
1258
 * @see hook_file_download()
1259
 */
12602366
function file_download() {
1261
  // Merge remainder of arguments from GET['q'], into relative file path.
12620
  $args = func_get_args();
12630
  $filepath = implode('/', $args);
1264
1265
  // Maintain compatibility with old ?file=paths saved in node bodies.
12660
  if (isset($_GET['file'])) {
12670
    $filepath =  $_GET['file'];
12680
  }
1269
12700
  if (file_exists(file_create_path($filepath))) {
1271
    // Let other modules provide headers and controls access to the file.
12720
    $headers = module_invoke_all('file_download', $filepath);
12730
    if (in_array(-1, $headers)) {
12740
      return drupal_access_denied();
12750
    }
12760
    if (count($headers)) {
12770
      file_transfer($filepath, $headers);
12780
    }
12790
  }
12800
  return drupal_not_found();
12810
}
1282
1283
1284
/**
1285
 * Finds all files that match a given mask in a given directory.
1286
 *
1287
 * Directories and files beginning with a period are excluded; this
1288
 * prevents hidden files and directories (such as SVN working directories)
1289
 * from being scanned.
1290
 *
1291
 * @param $dir
1292
 *   The base directory for the scan, without trailing slash.
1293
 * @param $mask
1294
 *   The preg_match() regular expression of the files to find.
1295
 * @param $nomask
1296
 *   An array of files/directories to ignore.
1297
 * @param $callback
1298
 *   The callback function to call for each match.
1299
 * @param $recurse
1300
 *   When TRUE, the directory scan will recurse the entire tree
1301
 *   starting at the provided directory.
1302
 * @param $key
1303
 *   The key to be used for the returned array of files. Possible
1304
 *   values are "filename", for the path starting with $dir,
1305
 *   "basename", for the basename of the file, and "name" for the name
1306
 *   of the file without an extension.
1307
 * @param $min_depth
1308
 *   Minimum depth of directories to return files from.
1309
 * @param $depth
1310
 *   Current depth of recursion. This parameter is only used internally and
1311
 *   should not be passed.
1312
 * @return
1313
 *   An associative array (keyed on the provided key) of objects with
1314
 *   "path", "basename", and "name" members corresponding to the
1315
 *   matching files.
1316
 */
13172366
function file_scan_directory($dir, $mask, $nomask = array('.', '..',
'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0,
$depth = 0) {
1318196
  $key = (in_array($key, array('filename', 'basename', 'name')) ? $key :
'filename');
1319196
  $files = array();
1320
1321196
  if (is_dir($dir) && $handle = opendir($dir)) {
1322193
    while (FALSE !== ($file = readdir($handle))) {
1323193
      if (!in_array($file, $nomask) && $file[0] != '.') {
1324193
        if (is_dir("$dir/$file") && $recurse) {
1325
          // Give priority to files in this folder by merging them in after
any subdirectory files.
1326192
          $files = array_merge(file_scan_directory("$dir/$file", $mask,
$nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
1327192
        }
1328193
        elseif ($depth >= $min_depth && preg_match($mask, $file)) {
1329
          // Always use this match over anything already set in $files with
the
1330
          // same $$key.
1331193
          $filename = "$dir/$file";
1332193
          $basename = basename($file);
1333193
          $name = substr($basename, 0, strrpos($basename, '.'));
1334193
          $files[$$key] = new stdClass();
1335193
          $files[$$key]->filename = $filename;
1336193
          $files[$$key]->basename = $basename;
1337193
          $files[$$key]->name = $name;
1338193
          if ($callback) {
13390
            $callback($filename);
13400
          }
1341193
        }
1342193
      }
1343193
    }
1344
1345193
    closedir($handle);
1346193
  }
1347
1348196
  return $files;
13490
}
1350
1351
/**
1352
 * Determine the default temporary directory.
1353
 *
1354
 * @return
1355
 *   A string containing a temp directory.
1356
 */
13572366
function file_directory_temp() {
1358102
  $temporary_directory = variable_get('file_directory_temp', NULL);
1359
1360102
  if (is_null($temporary_directory)) {
136120
    $directories = array();
1362
1363
    // Has PHP been set with an upload_tmp_dir?
136420
    if (ini_get('upload_tmp_dir')) {
13650
      $directories[] = ini_get('upload_tmp_dir');
13660
    }
1367
1368
    // Operating system specific dirs.
136920
    if (substr(PHP_OS, 0, 3) == 'WIN') {
13700
      $directories[] = 'c:/windows/temp';
13710
      $directories[] = 'c:/winnt/temp';
13720
    }
1373
    else {
137420
      $directories[] = '/tmp';
1375
    }
1376
137720
    foreach ($directories as $directory) {
137820
      if (!$temporary_directory && is_dir($directory)) {
137920
        $temporary_directory = $directory;
138020
      }
138120
    }
1382
1383
    // if a directory has been found, use it, otherwise default to
'files/tmp'
138420
    $temporary_directory = $temporary_directory ? $temporary_directory :
file_directory_path() . '/tmp';
138520
    variable_set('file_directory_temp', $temporary_directory);
138620
  }
1387
1388102
  return $temporary_directory;
13890
}
1390
1391
/**
1392
 * Determine the default 'files' directory.
1393
 *
1394
 * @return
1395
 *   A string containing the path to Drupal's 'files' directory.
1396
 */
13972366
function file_directory_path() {
13981891
  return variable_get('file_directory_path', conf_path() . '/files');
13990
}
1400
1401
/**
1402
 * Determine the maximum file upload size by querying the PHP settings.
1403
 *
1404
 * @return
1405
 *   A file size limit in bytes based on the PHP upload_max_filesize and
1406
 *   post_max_size
1407
 */
14082366
function file_upload_max_size() {
14099
  static $max_size = -1;
1410
14119
  if ($max_size < 0) {
14129
    $upload_max = parse_size(ini_get('upload_max_filesize'));
14139
    $post_max = parse_size(ini_get('post_max_size'));
14149
    $max_size = ($upload_max < $post_max) ? $upload_max : $post_max;
14159
  }
14169
  return $max_size;
14170
}
1418
1419
/**
1420
 * Determine an Internet Media Type, or MIME type from a filename.
1421
 *
1422
 * @param $filename
1423
 *   Name of the file, including extension.
1424
 * @param $mapping
1425
 *   An optional array of extension to media type mappings in the form
1426
 *   'extension1|extension2|...' => 'type'.
1427
 *
1428
 * @return
1429
 *   The internet media type registered for the extension or
application/octet-stream for unknown extensions.
1430
 */
14312366
function file_get_mimetype($filename, $mapping = NULL) {
143214
  if (!is_array($mapping)) {
143314
    $mapping = variable_get('mime_extension_mapping', array(
143414
      'ez' => 'application/andrew-inset',
143514
      'atom' => 'application/atom',
143614
      'atomcat' => 'application/atomcat+xml',
143714
      'atomsrv' => 'application/atomserv+xml',
143814
      'cap|pcap' => 'application/cap',
143914
      'cu' => 'application/cu-seeme',
144014
      'tsp' => 'application/dsptype',
144114
      'spl' => 'application/x-futuresplash',
144214
      'hta' => 'application/hta',
144314
      'jar' => 'application/java-archive',
144414
      'ser' => 'application/java-serialized-object',
144514
      'class' => 'application/java-vm',
144614
      'hqx' => 'application/mac-binhex40',
144714
      'cpt' => 'image/x-corelphotopaint',
144814
      'nb' => 'application/mathematica',
144914
      'mdb' => 'application/msaccess',
145014
      'doc|dot' => 'application/msword',
145114
      'bin' => 'application/octet-stream',
145214
      'oda' => 'application/oda',
145314
      'ogg|ogx' => 'application/ogg',
145414
      'pdf' => 'application/pdf',
145514
      'key' => 'application/pgp-keys',
145614
      'pgp' => 'application/pgp-signature',
145714
      'prf' => 'application/pics-rules',
145814
      'ps|ai|eps' => 'application/postscript',
145914
      'rar' => 'application/rar',
146014
      'rdf' => 'application/rdf+xml',
146114
      'rss' => 'application/rss+xml',
146214
      'rtf' => 'application/rtf',
146314
      'smi|smil' => 'application/smil',
146414
      'wpd' => 'application/wordperfect',
146514
      'wp5' => 'application/wordperfect5.1',
146614
      'xhtml|xht' => 'application/xhtml+xml',
146714
      'xml|xsl' => 'application/xml',
146814
      'zip' => 'application/zip',
146914
      'cdy' => 'application/vnd.cinderella',
147014
      'kml' => 'application/vnd.google-earth.kml+xml',
147114
      'kmz' => 'application/vnd.google-earth.kmz',
147214
      'xul' => 'application/vnd.mozilla.xul+xml',
147314
      'xls|xlb|xlt' => 'application/vnd.ms-excel',
147414
      'cat' => 'application/vnd.ms-pki.seccat',
147514
      'stl' => 'application/vnd.ms-pki.stl',
147614
      'ppt|pps' => 'application/vnd.ms-powerpoint',
147714
      'odc' => 'application/vnd.oasis.opendocument.chart',
147814
      'odb' => 'application/vnd.oasis.opendocument.database',
147914
      'odf' => 'application/vnd.oasis.opendocument.formula',
148014
      'odg' => 'application/vnd.oasis.opendocument.graphics',
148114
      'otg' => 'application/vnd.oasis.opendocument.graphics-template',
148214
      'odi' => 'application/vnd.oasis.opendocument.image',
148314
      'odp' => 'application/vnd.oasis.opendocument.presentation',
148414
      'otp' => 'application/vnd.oasis.opendocument.presentation-template',
148514
      'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
148614
      'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
148714
      'odt' => 'application/vnd.oasis.opendocument.text',
148814
      'odm' => 'application/vnd.oasis.opendocument.text-master',
148914
      'ott' => 'application/vnd.oasis.opendocument.text-template',
149014
      'oth' => 'application/vnd.oasis.opendocument.text-web',
149114
      'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
149214
      'docx' =>
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
149314
      'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
149414
      'dotx' =>
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
149514
      'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
149614
      'potx' =>
'application/vnd.openxmlformats-officedocument.presentationml.template',
149714
      'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
149814
      'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
149914
      'ppsx' =>
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
150014
      'pptm' =>
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
150114
      'pptx' =>
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
150214
      'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
150314
      'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
150414
      'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
150514
      'xlsx' =>
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
150614
      'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
150714
      'xltx' =>
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
150814
      'cod' => 'application/vnd.rim.cod',
150914
      'mmf' => 'application/vnd.smaf',
151014
      'sdc' => 'application/vnd.stardivision.calc',
151114
      'sds' => 'application/vnd.stardivision.chart',
151214
      'sda' => 'application/vnd.stardivision.draw',
151314
      'sdd' => 'application/vnd.stardivision.impress',
151414
      'sdf' => 'application/vnd.stardivision.math',
151514
      'sdw' => 'application/vnd.stardivision.writer',
151614
      'sgl' => 'application/vnd.stardivision.writer-global',
151714
      'sxc' => 'application/vnd.sun.xml.calc',
151814
      'stc' => 'application/vnd.sun.xml.calc.template',
151914
      'sxd' => 'application/vnd.sun.xml.draw',
152014
      'std' => 'application/vnd.sun.xml.draw.template',
152114
      'sxi' => 'application/vnd.sun.xml.impress',
152214
      'sti' => 'application/vnd.sun.xml.impress.template',
152314
      'sxm' => 'application/vnd.sun.xml.math',
152414
      'sxw' => 'application/vnd.sun.xml.writer',
152514
      'sxg' => 'application/vnd.sun.xml.writer.global',
152614
      'stw' => 'application/vnd.sun.xml.writer.template',
152714
      'sis' => 'application/vnd.symbian.install',
152814
      'vsd' => 'application/vnd.visio',
152914
      'wbxml' => 'application/vnd.wap.wbxml',
153014
      'wmlc' => 'application/vnd.wap.wmlc',
153114
      'wmlsc' => 'application/vnd.wap.wmlscriptc',
153214
      'wk' => 'application/x-123',
153314
      '7z' => 'application/x-7z-compressed',
153414
      'abw' => 'application/x-abiword',
153514
      'dmg' => 'application/x-apple-diskimage',
153614
      'bcpio' => 'application/x-bcpio',
153714
      'torrent' => 'application/x-bittorrent',
153814
      'cab' => 'application/x-cab',
153914
      'cbr' => 'application/x-cbr',
154014
      'cbz' => 'application/x-cbz',
154114
      'cdf' => 'application/x-cdf',
154214
      'vcd' => 'application/x-cdlink',
154314
      'pgn' => 'application/x-chess-pgn',
154414
      'cpio' => 'application/x-cpio',
154514
      'csh' => 'text/x-csh',
154614
      'deb|udeb' => 'application/x-debian-package',
154714
      'dcr|dir|dxr' => 'application/x-director',
154814
      'dms' => 'application/x-dms',
154914
      'wad' => 'application/x-doom',
155014
      'dvi' => 'application/x-dvi',
155114
      'rhtml' => 'application/x-httpd-eruby',
155214
      'flac' => 'application/x-flac',
155314
      'pfa|pfb|gsf|pcf|pcf.Z' => 'application/x-font',
155414
      'mm' => 'application/x-freemind',
155514
      'gnumeric' => 'application/x-gnumeric',
155614
      'sgf' => 'application/x-go-sgf',
155714
      'gcf' => 'application/x-graphing-calculator',
155814
      'gtar|tgz|taz' => 'application/x-gtar',
155914
      'hdf' => 'application/x-hdf',
156014
      'phtml|pht|php' => 'application/x-httpd-php',
156114
      'phps' => 'application/x-httpd-php-source',
156214
      'php3' => 'application/x-httpd-php3',
156314
      'php3p' => 'application/x-httpd-php3-preprocessed',
156414
      'php4' => 'application/x-httpd-php4',
156514
      'ica' => 'application/x-ica',
156614
      'ins|isp' => 'application/x-internet-signup',
156714
      'iii' => 'application/x-iphone',
156814
      'iso' => 'application/x-iso9660-image',
156914
      'jnlp' => 'application/x-java-jnlp-file',
157014
      'js' => 'application/x-javascript',
157114
      'jmz' => 'application/x-jmol',
157214
      'chrt' => 'application/x-kchart',
157314
      'kil' => 'application/x-killustrator',
157414
      'skp|skd|skt|skm' => 'application/x-koan',
157514
      'kpr|kpt' => 'application/x-kpresenter',
157614
      'ksp' => 'application/x-kspread',
157714
      'kwd|kwt' => 'application/x-kword',
157814
      'latex' => 'application/x-latex',
157914
      'lha' => 'application/x-lha',
158014
      'lyx' => 'application/x-lyx',
158114
      'lzh' => 'application/x-lzh',
158214
      'lzx' => 'application/x-lzx',
158314
      'frm|maker|frame|fm|fb|book|fbdoc' => 'application/x-maker',
158414
      'mif' => 'application/x-mif',
158514
      'wmd' => 'application/x-ms-wmd',
158614
      'wmz' => 'application/x-ms-wmz',
158714
      'com|exe|bat|dll' => 'application/x-msdos-program',
158814
      'msi' => 'application/x-msi',
158914
      'nc' => 'application/x-netcdf',
159014
      'pac' => 'application/x-ns-proxy-autoconfig',
159114
      'nwc' => 'application/x-nwc',
159214
      'o' => 'application/x-object',
159314
      'oza' => 'application/x-oz-application',
159414
      'p7r' => 'application/x-pkcs7-certreqresp',
159514
      'crl' => 'application/x-pkcs7-crl',
159614
      'pyc|pyo' => 'application/x-python-code',
159714
      'qtl' => 'application/x-quicktimeplayer',
159814
      'rpm' => 'application/x-redhat-package-manager',
159914
      'sh' => 'text/x-sh',
160014
      'shar' => 'application/x-shar',
160114
      'swf|swfl' => 'application/x-shockwave-flash',
160214
      'sit|sitx' => 'application/x-stuffit',
160314
      'sv4cpio' => 'application/x-sv4cpio',
160414
      'sv4crc' => 'application/x-sv4crc',
160514
      'tar' => 'application/x-tar',
160614
      'tcl' => 'application/x-tcl',
160714
      'gf' => 'application/x-tex-gf',
160814
      'pk' => 'application/x-tex-pk',
160914
      'texinfo|texi' => 'application/x-texinfo',
161014
      '~|%|bak|old|sik' => 'application/x-trash',
161114
      't|tr|roff' => 'application/x-troff',
161214
      'man' => 'application/x-troff-man',
161314
      'me' => 'application/x-troff-me',
161414
      'ms' => 'application/x-troff-ms',
161514
      'ustar' => 'application/x-ustar',
161614
      'src' => 'application/x-wais-source',
161714
      'wz' => 'application/x-wingz',
161814
      'crt' => 'application/x-x509-ca-cert',
161914
      'xcf' => 'application/x-xcf',
162014
      'fig' => 'application/x-xfig',
162114
      'xpi' => 'application/x-xpinstall',
162214
      'au|snd' => 'audio/basic',
162314
      'mid|midi|kar' => 'audio/midi',
162414
      'mpga|mpega|mp2|mp3|m4a' => 'audio/mpeg',
162514
      'm3u' => 'audio/x-mpegurl',
162614
      'oga|spx' => 'audio/ogg',
162714
      'sid' => 'audio/prs.sid',
162814
      'aif|aiff|aifc' => 'audio/x-aiff',
162914
      'gsm' => 'audio/x-gsm',
163014
      'wma' => 'audio/x-ms-wma',
163114
      'wax' => 'audio/x-ms-wax',
163214
      'ra|rm|ram' => 'audio/x-pn-realaudio',
163314
      'ra' => 'audio/x-realaudio',
163414
      'pls' => 'audio/x-scpls',
163514
      'sd2' => 'audio/x-sd2',
163614
      'wav' => 'audio/x-wav',
163714
      'alc' => 'chemical/x-alchemy',
163814
      'cac|cache' => 'chemical/x-cache',
163914
      'csf' => 'chemical/x-cache-csf',
164014
      'cbin|cascii|ctab' => 'chemical/x-cactvs-binary',
164114
      'cdx' => 'chemical/x-cdx',
164214
      'cer' => 'chemical/x-cerius',
164314
      'c3d' => 'chemical/x-chem3d',
164414
      'chm' => 'chemical/x-chemdraw',
164514
      'cif' => 'chemical/x-cif',
164614
      'cmdf' => 'chemical/x-cmdf',
164714
      'cml' => 'chemical/x-cml',
164814
      'cpa' => 'chemical/x-compass',
164914
      'bsd' => 'chemical/x-crossfire',
165014
      'csml|csm' => 'chemical/x-csml',
165114
      'ctx' => 'chemical/x-ctx',
165214
      'cxf|cef' => 'chemical/x-cxf',
165314
      'emb|embl' => 'chemical/x-embl-dl-nucleotide',
165414
      'spc' => 'chemical/x-galactic-spc',
165514
      'inp|gam|gamin' => 'chemical/x-gamess-input',
165614
      'fch|fchk' => 'chemical/x-gaussian-checkpoint',
165714
      'cub' => 'chemical/x-gaussian-cube',
165814
      'gau|gjc|gjf' => 'chemical/x-gaussian-input',
165914
      'gal' => 'chemical/x-gaussian-log',
166014
      'gcg' => 'chemical/x-gcg8-sequence',
166114
      'gen' => 'chemical/x-genbank',
166214
      'hin' => 'chemical/x-hin',
166314
      'istr|ist' => 'chemical/x-isostar',
166414
      'jdx|dx' => 'chemical/x-jcamp-dx',
166514
      'kin' => 'chemical/x-kinemage',
166614
      'mcm' => 'chemical/x-macmolecule',
166714
      'mmd|mmod' => 'chemical/x-macromodel-input',
166814
      'mol' => 'chemical/x-mdl-molfile',
166914
      'rd' => 'chemical/x-mdl-rdfile',
167014
      'rxn' => 'chemical/x-mdl-rxnfile',
167114
      'sd|sdf' => 'chemical/x-mdl-sdfile',
167214
      'tgf' => 'chemical/x-mdl-tgf',
167314
      'mcif' => 'chemical/x-mmcif',
167414
      'mol2' => 'chemical/x-mol2',
167514
      'b' => 'chemical/x-molconn-Z',
167614
      'gpt' => 'chemical/x-mopac-graph',
167714
      'mop|mopcrt|mpc|dat|zmt' => 'chemical/x-mopac-input',
167814
      'moo' => 'chemical/x-mopac-out',
167914
      'mvb' => 'chemical/x-mopac-vib',
168014
      'asn' => 'chemical/x-ncbi-asn1-spec',
168114
      'prt|ent' => 'chemical/x-ncbi-asn1-ascii',
168214
      'val|aso' => 'chemical/x-ncbi-asn1-binary',
168314
      'pdb|ent' => 'chemical/x-pdb',
168414
      'ros' => 'chemical/x-rosdal',
168514
      'sw' => 'chemical/x-swissprot',
168614
      'vms' => 'chemical/x-vamas-iso14976',
168714
      'vmd' => 'chemical/x-vmd',
168814
      'xtel' => 'chemical/x-xtel',
168914
      'xyz' => 'chemical/x-xyz',
169014
      'gif' => 'image/gif',
169114
      'ief' => 'image/ief',
169214
      'jpeg|jpg|jpe' => 'image/jpeg',
169314
      'pcx' => 'image/pcx',
169414
      'png' => 'image/png',
169514
      'svg|svgz' => 'image/svg+xml',
169614
      'tiff|tif' => 'image/tiff',
169714
      'djvu|djv' => 'image/vnd.djvu',
169814
      'wbmp' => 'image/vnd.wap.wbmp',
169914
      'ras' => 'image/x-cmu-raster',
170014
      'cdr' => 'image/x-coreldraw',
170114
      'pat' => 'image/x-coreldrawpattern',
170214
      'cdt' => 'image/x-coreldrawtemplate',
170314
      'ico' => 'image/x-icon',
170414
      'art' => 'image/x-jg',
170514
      'jng' => 'image/x-jng',
170614
      'bmp' => 'image/x-ms-bmp',
170714
      'psd' => 'image/x-photoshop',
170814
      'pnm' => 'image/x-portable-anymap',
170914
      'pbm' => 'image/x-portable-bitmap',
171014
      'pgm' => 'image/x-portable-graymap',
171114
      'ppm' => 'image/x-portable-pixmap',
171214
      'rgb' => 'image/x-rgb',
171314
      'xbm' => 'image/x-xbitmap',
171414
      'xpm' => 'image/x-xpixmap',
171514
      'xwd' => 'image/x-xwindowdump',
171614
      'eml' => 'message/rfc822',
171714
      'igs|iges' => 'model/iges',
171814
      'msh|mesh|silo' => 'model/mesh',
171914
      'wrl|vrml' => 'model/vrml',
172014
      'ics|icz' => 'text/calendar',
172114
      'css' => 'text/css',
172214
      'csv' => 'text/csv',
172314
      '323' => 'text/h323',
172414
      'html|htm|shtml' => 'text/html',
172514
      'uls' => 'text/iuls',
172614
      'mml' => 'text/mathml',
172714
      'asc|txt|text|pot' => 'text/plain',
172814
      'rtx' => 'text/richtext',
172914
      'sct|wsc' => 'text/scriptlet',
173014
      'tm|ts' => 'text/texmacs',
173114
      'tsv' => 'text/tab-separated-values',
173214
      'jad' => 'text/vnd.sun.j2me.app-descriptor',
173314
      'wml' => 'text/vnd.wap.wml',
173414
      'wmls' => 'text/vnd.wap.wmlscript',
173514
      'bib' => 'text/x-bibtex',
173614
      'boo' => 'text/x-boo',
173714
      'h++|hpp|hxx|hh' => 'text/x-c++hdr',
173814
      'c++|cpp|cxx|cc' => 'text/x-c++src',
173914
      'h' => 'text/x-chdr',
174014
      'htc' => 'text/x-component',
174114
      'c' => 'text/x-csrc',
174214
      'd' => 'text/x-dsrc',
174314
      'diff|patch' => 'text/x-diff',
174414
      'hs' => 'text/x-haskell',
174514
      'java' => 'text/x-java',
174614
      'lhs' => 'text/x-literate-haskell',
174714
      'moc' => 'text/x-moc',
174814
      'p|pas' => 'text/x-pascal',
174914
      'gcd' => 'text/x-pcs-gcd',
175014
      'pl|pm' => 'text/x-perl',
175114
      'py' => 'text/x-python',
175214
      'etx' => 'text/x-setext',
175314
      'tcl|tk' => 'text/x-tcl',
175414
      'tex|ltx|sty|cls' => 'text/x-tex',
175514
      'vcs' => 'text/x-vcalendar',
175614
      'vcf' => 'text/x-vcard',
175714
      '3gp' => 'video/3gpp',
175814
      'dl' => 'video/dl',
175914
      'dif|dv' => 'video/dv',
176014
      'fli' => 'video/fli',
176114
      'gl' => 'video/gl',
176214
      'mpeg|mpg|mpe' => 'video/mpeg',
176314
      'mp4' => 'video/mp4',
176414
      'ogv' => 'video/ogg',
176514
      'qt|mov' => 'video/quicktime',
176614
      'mxu' => 'video/vnd.mpegurl',
176714
      'lsf|lsx' => 'video/x-la-asf',
176814
      'mng' => 'video/x-mng',
176914
      'asf|asx' => 'video/x-ms-asf',
177014
      'wm' => 'video/x-ms-wm',
177114
      'wmv' => 'video/x-ms-wmv',
177214
      'wmx' => 'video/x-ms-wmx',
177314
      'wvx' => 'video/x-ms-wvx',
177414
      'avi' => 'video/x-msvideo',
177514
      'movie' => 'video/x-sgi-movie',
177614
      'ice' => 'x-conference/x-cooltalk',
177714
      'sisx' => 'x-epoc/x-sisx-app',
177814
      'vrm|vrml|wrl' => 'x-world/x-vrml',
177914
      'xps' => 'application/vnd.ms-xpsdocument',
178014
    ));
178114
  }
178214
  foreach ($mapping as $ext_preg => $mime_match) {
178314
    if (preg_match('!\.('. $ext_preg .')$!i', $filename)) {
178413
      return $mime_match;
17850
    }
178614
  }
1787
17882
  return 'application/octet-stream';
17890
}
1790
1791
/**
1792
 * @} End of "defgroup file".
1793
 */
17942366