file.inc 56.1 KB
Newer Older
1
<?php
2

3 4 5 6 7
/**
 * @file
 * API for handling file uploads and server file management.
 */

8 9
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage;
10
use Drupal\Component\Utility\String;
11
use Drupal\Core\StreamWrapper\PublicStream;
12

13
/**
webchick's avatar
webchick committed
14
 * Stream wrapper bit flags that are the basis for composite types.
15
 *
webchick's avatar
webchick committed
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
 * Note that 0x0002 is skipped, because it was the value of a constant that has
 * since been removed.
 */

/**
 * Stream wrapper bit flag -- a filter that matches all wrappers.
 */
const STREAM_WRAPPERS_ALL = 0x0000;

/**
 * Stream wrapper bit flag -- refers to a local file system location.
 */
const STREAM_WRAPPERS_LOCAL = 0x0001;

/**
 * Stream wrapper bit flag -- wrapper is readable (almost always true).
 */
const STREAM_WRAPPERS_READ = 0x0004;

/**
 * Stream wrapper bit flag -- wrapper is writeable.
 */
const STREAM_WRAPPERS_WRITE = 0x0008;

/**
 * Stream wrapper bit flag -- exposed in the UI and potentially web accessible.
 */
const STREAM_WRAPPERS_VISIBLE = 0x0010;

45 46 47 48 49 50 51 52 53 54 55

/**
 * Default mode for new directories. See drupal_chmod().
 */
const FILE_CHMOD_DIRECTORY = 0775;

/**
 * Default mode for new files. See drupal_chmod().
 */
const FILE_CHMOD_FILE = 0664;

webchick's avatar
webchick committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
/**
 * Composite stream wrapper bit flags that are usually used as the types.
 */

/**
 * Stream wrapper type flag -- not visible in the UI or accessible via web,
 * but readable and writable. E.g. the temporary directory for uploads.
 */
define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE);

/**
 * Stream wrapper type flag -- hidden, readable and writeable using local files.
 */
define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN);

/**
 * Stream wrapper type flag -- visible, readable and writeable.
 */
define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE);

/**
 * Stream wrapper type flag -- visible and read-only.
 */
define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE);

/**
 * Stream wrapper type flag -- the default when 'type' is omitted from
 * hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL,
 * because PHP grants a greater trust level to local files (for example, they
 * can be used in an "include" statement, regardless of the "allow_url_include"
 * setting), so stream wrappers need to explicitly opt-in to this.
 */
define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE);

/**
 * Stream wrapper type flag -- visible, readable and writeable using local files.
92
 */
webchick's avatar
webchick committed
93
define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL);
94

95
/**
96
 * @defgroup file File interface
97
 * @{
98
 * Common file handling functions.
99
 *
100
 * Fields on the file entity:
101 102 103
 * - fid: File ID
 * - uid: The {users}.uid of the user who is associated with the file.
 * - filename: Name of the file with no path components. This may differ from
104 105
 *   the basename of the filepath if the file is renamed to avoid overwriting
 *   an existing file.
106 107 108 109
 * - uri: URI of the file.
 * - filemime: The file's MIME type.
 * - filesize: The size of the file in bytes.
 * - status: A bitmapped field indicating the status of the file. The first 8
110
 *   bits are reserved for Drupal core. The least significant bit indicates
111 112
 *   temporary (0) or permanent (1). Temporary files older than
 *   DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs.
113
 * - timestamp: UNIX timestamp for the date the file was added to the database.
114 115
 */

116
/**
117
 * Flag used by file_prepare_directory() -- create directory if not present.
118
 */
119
const FILE_CREATE_DIRECTORY = 1;
120 121

/**
122
 * Flag used by file_prepare_directory() -- file permissions may be changed.
123
 */
124
const FILE_MODIFY_PERMISSIONS = 2;
125 126

/**
127
 * Flag for dealing with existing files: Appends number until name is unique.
128
 */
129
const FILE_EXISTS_RENAME = 0;
130 131 132 133

/**
 * Flag for dealing with existing files: Replace the existing file.
 */
134
const FILE_EXISTS_REPLACE = 1;
135 136 137 138

/**
 * Flag for dealing with existing files: Do nothing and return FALSE.
 */
139
const FILE_EXISTS_ERROR = 2;
140

141
/**
142 143 144 145 146
 * Indicates that the file is permanent and should not be deleted.
 *
 * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
 * during cron runs, but permanent files will not be removed during the file
 * garbage collection process.
147
 */
148
const FILE_STATUS_PERMANENT = 1;
149

150
/**
151
 * Provides Drupal stream wrapper registry.
152 153 154 155 156 157 158 159 160 161 162 163 164 165
 *
 * A stream wrapper is an abstraction of a file system that allows Drupal to
 * use the same set of methods to access both local files and remote resources.
 *
 * Provide a facility for managing and querying user-defined stream wrappers
 * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
 * registered to handle a stream, which we need to be able to find the handler
 * for class instantiation.
 *
 * If a module registers a scheme that is already registered with PHP, the
 * existing scheme will be unregistered and replaced with the specified class.
 *
 * A stream is referenced as "scheme://target".
 *
166 167 168 169
 * The optional $filter parameter can be used to retrieve only the stream
 * wrappers that are appropriate for particular usage. For example, this returns
 * only stream wrappers that use local file storage:
 * @code
170
 *   $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
171 172 173 174 175 176 177 178 179
 * @endcode
 *
 * The $filter parameter can only filter to types containing a particular flag.
 * In some cases, you may want to filter to types that do not contain a
 * particular flag. For example, you may want to retrieve all stream wrappers
 * that are not writable, or all stream wrappers that are not local. PHP's
 * array_diff_key() function can be used to help with this. For example, this
 * returns only stream wrappers that do not use local file storage:
 * @code
180
 *   $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));
181 182
 * @endcode
 *
183
 * @param $filter
184 185 186 187 188 189
 *   (Optional) Filters out all types except those with an on bit for each on
 *   bit in $filter. For example, if $filter is STREAM_WRAPPERS_WRITE_VISIBLE,
 *   which is equal to (STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE |
 *   STREAM_WRAPPERS_VISIBLE), then only stream wrappers with all three of these
 *   bits set are returned. Defaults to STREAM_WRAPPERS_ALL, which returns all
 *   registered stream wrappers.
190
 *
191
 * @return
192 193 194 195 196
 *   An array keyed by scheme, with values containing an array of information
 *   about the stream wrapper, as returned by hook_stream_wrappers(). If $filter
 *   is omitted or set to STREAM_WRAPPERS_ALL, the entire Drupal stream wrapper
 *   registry is returned. Otherwise only the stream wrappers whose 'type'
 *   bitmask has an on bit for each bit specified in $filter are returned.
197
 *
198 199 200
 * @see hook_stream_wrappers()
 * @see hook_stream_wrappers_alter()
 */
201 202
function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
  $wrappers_storage = &drupal_static(__FUNCTION__);
203

204
  if (!isset($wrappers_storage)) {
205
    $wrappers = \Drupal::moduleHandler()->invokeAll('stream_wrappers');
206 207 208 209
    foreach ($wrappers as $scheme => $info) {
      // Add defaults.
      $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
    }
210 211 212 213
    drupal_alter('stream_wrappers', $wrappers);
    $existing = stream_get_wrappers();
    foreach ($wrappers as $scheme => $info) {
      // We only register classes that implement our interface.
webchick's avatar
webchick committed
214
      if (in_array('Drupal\Core\StreamWrapper\StreamWrapperInterface', class_implements($info['class']), TRUE)) {
215 216 217 218 219 220 221 222
        // Record whether we are overriding an existing scheme.
        if (in_array($scheme, $existing, TRUE)) {
          $wrappers[$scheme]['override'] = TRUE;
          stream_wrapper_unregister($scheme);
        }
        else {
          $wrappers[$scheme]['override'] = FALSE;
        }
223 224
        if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
          stream_wrapper_register($scheme, $info['class']);
225 226
        }
        else {
227
          stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);
228
        }
229
      }
230 231 232 233 234
      // Pre-populate the static cache with the filters most typically used.
      $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme];
      if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) {
        $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme];
      }
235 236
    }
  }
237 238 239 240 241

  if (!isset($wrappers_storage[$filter])) {
    $wrappers_storage[$filter] = array();
    foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
      // Bit-wise filter.
242
      if (($info['type'] & $filter) == $filter) {
243 244 245 246 247 248
        $wrappers_storage[$filter][$scheme] = $info;
      }
    }
  }

  return $wrappers_storage[$filter];
249 250 251 252 253 254 255
}

/**
 * Returns the stream wrapper class name for a given scheme.
 *
 * @param $scheme
 *   Stream scheme.
256
 *
257 258 259 260 261 262 263 264 265 266 267 268 269
 * @return
 *   Return string if a scheme has a registered handler, or FALSE.
 */
function file_stream_wrapper_get_class($scheme) {
  $wrappers = file_get_stream_wrappers();
  return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
}

/**
 * Returns the scheme of a URI (e.g. a stream).
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
270
 *
271 272 273
 * @return
 *   A string containing the name of the scheme, or FALSE if none. For example,
 *   the URI "public://example.txt" would return "public".
274 275
 *
 * @see file_uri_target()
276 277
 */
function file_uri_scheme($uri) {
278 279
  $position = strpos($uri, '://');
  return $position ? substr($uri, 0, $position) : FALSE;
280 281 282
}

/**
283
 * Checks that the scheme of a stream URI is valid.
284 285 286 287 288 289 290
 *
 * Confirms that there is a registered stream handler for the provided scheme
 * and that it is callable. This is useful if you want to confirm a valid
 * scheme without creating a new instance of the registered handler.
 *
 * @param $scheme
 *   A URI scheme, a stream is referenced as "scheme://target".
291
 *
292 293 294 295 296
 * @return
 *   Returns TRUE if the string is the name of a validated stream,
 *   or FALSE if the scheme does not have a registered handler.
 */
function file_stream_wrapper_valid_scheme($scheme) {
297
  return $scheme && class_exists(file_stream_wrapper_get_class($scheme));
298 299
}

300

301
/**
302
 * Returns the part of a URI after the schema.
303 304 305
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
306
 *
307 308 309 310
 * @return
 *   A string containing the target (path), or FALSE if none.
 *   For example, the URI "public://sample/test.txt" would return
 *   "sample/test.txt".
311 312
 *
 * @see file_uri_scheme()
313 314
 */
function file_uri_target($uri) {
315 316 317 318
  $data = explode('://', $uri, 2);

  // Remove erroneous leading or trailing, forward-slashes and backslashes.
  return count($data) == 2 ? trim($data[1], '\/') : FALSE;
319 320
}

321
/**
322
 * Gets the default file stream implementation.
323 324 325 326 327
 *
 * @return
 *   'public', 'private' or any other file scheme defined as the default.
 */
function file_default_scheme() {
328
  return \Drupal::config('system.file')->get('default_scheme');
329 330
}

331 332 333 334 335 336 337 338 339 340 341
/**
 * Normalizes a URI by making it syntactically correct.
 *
 * A stream is referenced as "scheme://target".
 *
 * The following actions are taken:
 * - Remove trailing slashes from target
 * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
 *
 * @param $uri
 *   String reference containing the URI to normalize.
342
 *
343 344
 * @return
 *   The normalized URI.
345 346 347 348
 */
function file_stream_wrapper_uri_normalize($uri) {
  $scheme = file_uri_scheme($uri);

349
  if (file_stream_wrapper_valid_scheme($scheme)) {
350 351
    $target = file_uri_target($uri);

352 353 354
    if ($target !== FALSE) {
      $uri = $scheme . '://' . $target;
    }
355
  }
356

357 358 359 360
  return $uri;
}

/**
361
 * Returns a reference to the stream wrapper class responsible for a given URI.
362 363 364 365 366 367
 *
 * The scheme determines the stream wrapper class that should be
 * used by consulting the stream wrapper registry.
 *
 * @param $uri
 *   A stream, referenced as "scheme://target".
368
 *
369 370 371 372
 * @return
 *   Returns a new stream wrapper object appropriate for the given URI or FALSE
 *   if no registered handler could be found. For example, a URI of
 *   "private://example.txt" would return a new private stream wrapper object
webchick's avatar
webchick committed
373
 *   (Drupal\Core\StreamWrapper\PrivateStream).
374 375
 */
function file_stream_wrapper_get_instance_by_uri($uri) {
376 377 378 379 380 381 382
  if ($scheme = file_uri_scheme($uri)) {
    $class = file_stream_wrapper_get_class($scheme);
    if (class_exists($class)) {
      $instance = new $class();
      $instance->setUri($uri);
      return $instance;
    }
383
  }
384
  return FALSE;
385 386 387
}

/**
388
 * Returns a reference to the stream wrapper class responsible for a scheme.
389 390 391 392 393 394 395 396 397 398 399
 *
 * This helper method returns a stream instance using a scheme. That is, the
 * passed string does not contain a "://". For example, "public" is a scheme
 * but "public://" is a URI (stream). This is because the later contains both
 * a scheme and target despite target being empty.
 *
 * Note: the instance URI will be initialized to "scheme://" so that you can
 * make the customary method calls as if you had retrieved an instance by URI.
 *
 * @param $scheme
 *   If the stream was "public://target", "public" would be the scheme.
400
 *
401
 * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface
402 403
 *   Returns a new stream wrapper object appropriate for the given $scheme.
 *   For example, for the public scheme a stream wrapper object
webchick's avatar
webchick committed
404
 *   (Drupal\Core\StreamWrapper\PublicStream).
405 406 407 408 409
 *   FALSE is returned if no registered handler could be found.
 */
function file_stream_wrapper_get_instance_by_scheme($scheme) {
  $class = file_stream_wrapper_get_class($scheme);
  if (class_exists($class)) {
410
    $instance = new $class();
411 412 413 414 415 416 417 418
    $instance->setUri($scheme . '://');
    return $instance;
  }
  else {
    return FALSE;
  }
}

419
/**
420
 * Creates a web-accessible URL for a stream to an external or local file.
421
 *
422
 * Compatibility: normal paths and stream wrappers.
423
 *
424
 * There are two kinds of local files:
425 426 427
 * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
 *   These are files that have either been uploaded by users or were generated
 *   automatically (for example through CSS aggregation).
428 429 430
 * - "shipped files", i.e. those outside of the files directory, which ship as
 *   part of Drupal core or contributed modules or themes.
 *
431
 * @param $uri
432 433
 *   The URI to a file for which we need an external URL, or the path to a
 *   shipped file.
434
 *
435
 * @return
436
 *   A string containing a URL that may be used to access the file.
437 438 439
 *   If the provided string already contains a preceding 'http', 'https', or
 *   '/', nothing is done and the same string is returned. If a stream wrapper
 *   could not be found to generate an external URL, then FALSE is returned.
440 441
 *
 * @see http://drupal.org/node/515192
442
 * @see file_url_transform_relative()
443
 */
444
function file_create_url($uri) {
445 446 447
  // Allow the URI to be altered, e.g. to serve a file from a CDN or static
  // file server.
  drupal_alter('file_url', $uri);
448

449 450 451
  $scheme = file_uri_scheme($uri);

  if (!$scheme) {
452 453 454 455 456 457 458 459 460 461 462 463
    // Allow for:
    // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
    // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
    //   http://example.com/bar.jpg by the browser when viewing a page over
    //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
    // Both types of relative URIs are characterized by a leading slash, hence
    // we can use a single check.
    if (drupal_substr($uri, 0, 1) == '/') {
      return $uri;
    }
    else {
      // If this is not a properly formatted stream, then it is a shipped file.
464 465
      // Therefore, return the urlencoded URI with the base URL prepended.
      return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
466
    }
467 468
  }
  elseif ($scheme == 'http' || $scheme == 'https') {
469 470
    // Check for HTTP so that we don't have to implement getExternalUrl() for
    // the HTTP wrapper.
471 472 473 474 475 476 477 478 479 480 481
    return $uri;
  }
  else {
    // Attempt to return an external URL using the appropriate wrapper.
    if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
      return $wrapper->getExternalUrl();
    }
    else {
      return FALSE;
    }
  }
482 483
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
/**
 * Transforms an absolute URL of a local file to a relative URL.
 *
 * May be useful to prevent problems on multisite set-ups and prevent mixed
 * content errors when using HTTPS + HTTP.
 *
 * @param string $file_url
 *   A file URL of a local file as generated by file_create_url().
 *
 * @return string
 *   If the file URL indeed pointed to a local file and was indeed absolute,
 *   then the transformed, relative URL to the local file. Otherwise: the
 *   original value of $file_url.
 *
 * @see file_create_url()
 */
function file_url_transform_relative($file_url) {
  // Unfortunately, we pretty much have to duplicate Symfony's
  // Request::getHttpHost() method because Request::getPort() may return NULL
  // instead of a port number.
  $http_host = '';
  $request = \Drupal::request();
  $host = $request->getHost();
  $scheme = $request->getScheme();
  $port = $request->getPort() ?: 80;
  if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
    $http_host = $host;
  }
  else {
    $http_host = $host . ':' . $port;
  }

  return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
}

519
/**
520
 * Checks that the directory exists and is writable.
521 522 523 524
 *
 * Directories need to have execute permissions to be considered a directory by
 * FTP servers, etc.
 *
525
 * @param $directory
526 527 528
 *   A string reference containing the name of a directory path or URI. A
 *   trailing slash will be trimmed from a path.
 * @param $options
529 530 531
 *   A bitmask to indicate if the directory should be created if it does
 *   not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
 *   (FILE_MODIFY_PERMISSIONS).
532
 *
533
 * @return
534 535
 *   TRUE if the directory exists (or was created) and is writable. FALSE
 *   otherwise.
536
 */
537
function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
538 539 540 541
  if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) {
    // Only trim if we're not dealing with a stream.
    $directory = rtrim($directory, '/\\');
  }
542 543 544

  // Check if directory exists.
  if (!is_dir($directory)) {
545 546
    // Let mkdir() recursively create directories and use the default directory
    // permissions.
547
    if ($options & FILE_CREATE_DIRECTORY) {
548
      return @drupal_mkdir($directory, NULL, TRUE);
549
    }
550
    return FALSE;
551
  }
552 553 554
  // The directory exists, so check to see if it is writable.
  $writable = is_writable($directory);
  if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
555
    return drupal_chmod($directory);
556 557
  }

558
  return $writable;
559 560 561
}

/**
562
 * Creates a .htaccess file in each Drupal files directory if it is missing.
563
 */
564
function file_ensure_htaccess() {
565
  file_save_htaccess('public://', FALSE);
566
  $private_path = \Drupal::config('system.file')->get('path.private');
567
  if (!empty($private_path)) {
568
    file_save_htaccess('private://', TRUE);
569
  }
570
  file_save_htaccess('temporary://', TRUE);
571
  file_save_htaccess(config_get_config_directory(), TRUE);
572
  file_save_htaccess(config_get_config_directory(CONFIG_STAGING_DIRECTORY), TRUE);
573 574 575
}

/**
576
 * Creates a .htaccess file in the given directory.
577
 *
578
 * @param $directory
579 580 581 582
 *   The directory.
 * @param $private
 *   FALSE indicates that $directory should be an open and public directory.
 *   The default is TRUE which indicates a private and protected directory.
583
 */
584
function file_save_htaccess($directory, $private = TRUE) {
585 586
  if (file_uri_scheme($directory)) {
    $directory = file_stream_wrapper_uri_normalize($directory);
587 588
  }
  else {
589
    $directory = rtrim($directory, '/\\');
590
  }
591 592 593 594 595 596 597 598 599
  $htaccess_path =  $directory . '/.htaccess';

  if (file_exists($htaccess_path)) {
    // Short circuit if the .htaccess file already exists.
    return;
  }

  if ($private) {
    // Private .htaccess file.
600
    $htaccess_lines = MTimeProtectedFastFileStorage::HTACCESS;
601 602 603 604 605 606 607 608 609 610 611
  }
  else {
    // Public .htaccess file.
    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
  }

  // Write the .htaccess file.
  if (file_put_contents($htaccess_path, $htaccess_lines)) {
    drupal_chmod($htaccess_path, 0444);
  }
  else {
612
    $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(String::checkPlain($htaccess_lines)));
613
    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);
614 615 616
  }
}

617
/**
618
 * Determines whether the URI has a valid scheme for file API operations.
619 620 621 622 623 624 625 626 627 628 629 630 631 632
 *
 * There must be a scheme and it must be a Drupal-provided scheme like
 * 'public', 'private', 'temporary', or an extension provided with
 * hook_stream_wrappers().
 *
 * @param $uri
 *   The URI to be tested.
 *
 * @return
 *   TRUE if the URI is allowed.
 */
function file_valid_uri($uri) {
  // Assert that the URI has an allowed scheme. Barepaths are not allowed.
  $uri_scheme = file_uri_scheme($uri);
633
  if (!file_stream_wrapper_valid_scheme($uri_scheme)) {
634 635 636 637 638
    return FALSE;
  }
  return TRUE;
}

639
/**
640
 * Copies a file to a new location without invoking the file API.
641
 *
642 643 644 645 646
 * This is a powerful function that in many ways performs like an advanced
 * version of copy().
 * - Checks if $source and $destination are valid and readable/writable.
 * - If file already exists in $destination either the call will error out,
 *   replace the file or rename the file based on the $replace parameter.
647
 * - If the $source and $destination are equal, the behavior depends on the
648
 *   $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
649
 *   will rename the file until the $destination is unique.
650 651 652 653
 * - Provides a fallback using realpaths if the move fails using stream
 *   wrappers. This can occur because PHP's copy() function does not properly
 *   support streams if safe_mode or open_basedir are enabled. See
 *   https://bugs.php.net/bug.php?id=60456
654 655
 *
 * @param $source
656
 *   A string specifying the filepath or URI of the source file.
657
 * @param $destination
658
 *   A URI containing the destination that $source should be copied to. The
659 660
 *   URI may be a bare filepath (without a scheme). If this value is omitted,
 *   Drupal's default files scheme will be used, usually "public://".
661 662 663 664
 * @param $replace
 *   Replace behavior when the destination file already exists:
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
665
 *       unique.
666
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
667
 *
668 669
 * @return
 *   The path to the new file, or FALSE in the event of an error.
670
 *
671
 * @see file_copy()
672
 */
673
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
674 675
  $original_source = $source;

676
  // Assert that the source file actually exists.
677
  if (!file_exists($source)) {
678
    // @todo Replace drupal_set_message() calls with exceptions instead.
679
    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' => $original_source)), 'error');
680 681 682 683 684 685
    if (($realpath = drupal_realpath($original_source)) !== FALSE) {
      watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
    }
    else {
      watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
    }
686 687
    return FALSE;
  }
688

689 690
  // Build a destination URI if necessary.
  if (!isset($destination)) {
691
    $destination = file_build_uri(drupal_basename($source));
692
  }
693 694


695 696 697
  // Prepare the destination directory.
  if (file_prepare_directory($destination)) {
    // The destination is already a directory, so append the source basename.
698
    $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
699 700 701 702 703 704
  }
  else {
    // Perhaps $destination is a dir/file?
    $dirname = drupal_dirname($destination);
    if (!file_prepare_directory($dirname)) {
      // The destination is not valid.
705 706
      watchdog('file', 'File %file could not be copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
      drupal_set_message(t('The specified file %file could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
707 708 709
      return FALSE;
    }
  }
710

711 712
  // Determine whether we can perform this operation based on overwrite rules.
  $destination = file_destination($destination, $replace);
713
  if ($destination === FALSE) {
714
    drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
715
    watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination));
716
    return FALSE;
717
  }
718 719

  // Assert that the source and destination filenames are not the same.
720 721 722
  $real_source = drupal_realpath($source);
  $real_destination = drupal_realpath($destination);
  if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
723
    drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
724
    watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));
725
    return FALSE;
726
  }
727 728 729
  // Make sure the .htaccess files are present.
  file_ensure_htaccess();
  // Perform the copy operation.
730
  if (!@copy($source, $destination)) {
731 732 733 734 735 736
    // If the copy failed and realpaths exist, retry the operation using them
    // instead.
    if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
      watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
      return FALSE;
    }
737
  }
738

739 740
  // Set the permissions on the new file.
  drupal_chmod($destination);
741 742

  return $destination;
743 744
}

745
/**
746
 * Constructs a URI to Drupal's default files location given a relative path.
747 748
 */
function file_build_uri($path) {
749
  $uri = file_default_scheme() . '://' . $path;
750 751 752
  return file_stream_wrapper_uri_normalize($uri);
}

753
/**
754
 * Determines the destination path for a file.
755
 *
756
 * @param $destination
757
 *   A string specifying the desired final URI or filepath.
758 759
 * @param $replace
 *   Replace behavior when the destination file already exists.
760
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
761
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
762
 *       unique.
763
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
764
 *
765
 * @return
766 767
 *   The destination filepath, or FALSE if the file already exists
 *   and FILE_EXISTS_ERROR is specified.
768 769 770 771
 */
function file_destination($destination, $replace) {
  if (file_exists($destination)) {
    switch ($replace) {
772 773 774 775
      case FILE_EXISTS_REPLACE:
        // Do nothing here, we want to overwrite the existing file.
        break;

776
      case FILE_EXISTS_RENAME:
777
        $basename = drupal_basename($destination);
778
        $directory = drupal_dirname($destination);
779 780 781 782
        $destination = file_create_filename($basename, $directory);
        break;

      case FILE_EXISTS_ERROR:
783
        // Error reporting handled by calling function.
784 785 786 787 788 789
        return FALSE;
    }
  }
  return $destination;
}

790
/**
791
 * Moves a file to a new location without database changes or hook invocation.
792
 *
793
 * @param $source
794
 *   A string specifying the filepath or URI of the original file.
795
 * @param $destination
796 797 798
 *   A string containing the destination that $source should be moved to.
 *   This must be a stream wrapper URI. If this value is omitted, Drupal's
 *   default files scheme will be used, usually "public://".
799 800 801 802
 * @param $replace
 *   Replace behavior when the destination file already exists:
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
803
 *       unique.
804
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
805
 *
806
 * @return
807
 *   The URI of the moved file, or FALSE in the event of an error.
808
 *
809
 * @see file_move()
810
 */
811 812 813
function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  $filepath = file_unmanaged_copy($source, $destination, $replace);
  if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) {
814
    return FALSE;
815
  }
816
  return $filepath;
817 818
}

819
/**
820
 * Modifies a filename as needed for security purposes.
821
 *
822 823 824 825 826 827 828 829 830 831 832
 * Munging a file name prevents unknown file extensions from masking exploit
 * files. When web servers such as Apache decide how to process a URL request,
 * they use the file extension. If the extension is not recognized, Apache
 * skips that extension and uses the previous file extension. For example, if
 * the file being requested is exploit.php.pps, and Apache does not recognize
 * the '.pps' extension, it treats the file as PHP and executes it. To make
 * this file name safe for Apache and prevent it from executing as PHP, the
 * .php extension is "munged" into .php_, making the safe file name
 * exploit.php_.pps.
 *
 * Specifically, this function adds an underscore to all extensions that are
833
 * between 2 and 5 characters in length, internal to the file name, and not
834 835
 * included in $extensions.
 *
836 837 838
 * Function behavior is also controlled by the configuration
 * 'system.file:allow_insecure_uploads'. If it evaluates to TRUE, no alterations
 * will be made, if it evaluates to FALSE, the filename is 'munged'. *
839
 * @param $filename
840
 *   File name to modify.
841
 * @param $extensions
842
 *   A space-separated list of extensions that should not be altered.
843
 * @param $alerts
844 845 846
 *   If TRUE, drupal_set_message() will be called to display a message if the
 *   file name was changed.
 *
847
 * @return string
848
 *   The potentially modified $filename.
849 850 851 852 853
 */
function file_munge_filename($filename, $extensions, $alerts = TRUE) {
  $original = $filename;

  // Allow potentially insecure uploads for very savvy users and admin
854
  if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
855 856 857
    // Remove any null bytes. See http://php.net/manual/en/security.filesystem.nullbytes.php
    $filename = str_replace(chr(0), '', $filename);

858 859 860 861 862 863 864 865 866 867 868 869
    $whitelist = array_unique(explode(' ', trim($extensions)));

    // Split the filename up by periods. The first part becomes the basename
    // the last part the final extension.
    $filename_parts = explode('.', $filename);
    $new_filename = array_shift($filename_parts); // Remove file basename.
    $final_extension = array_pop($filename_parts); // Remove final extension.

    // Loop through the middle parts of the name and add an underscore to the
    // end of each section that could be a file extension but isn't in the list
    // of allowed extensions.
    foreach ($filename_parts as $filename_part) {
870
      $new_filename .= '.' . $filename_part;
871 872 873 874
      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
        $new_filename .= '_';
      }
    }
875
    $filename = $new_filename . '.' . $final_extension;
876 877 878 879 880 881 882 883 884 885

    if ($alerts && $original != $filename) {
      drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
    }
  }

  return $filename;
}

/**
886
 * Undoes the effect of file_munge_filename().
887
 *
888 889
 * @param $filename
 *   String with the filename to be unmunged.
890
 *
891 892
 * @return
 *   An unmunged filename string.
893 894 895 896 897
 */
function file_unmunge_filename($filename) {
  return str_replace('_.', '.', $filename);
}

898
/**
899
 * Creates a full file path from a directory and filename.
900 901 902
 *
 * If a file with the specified name already exists, an alternative will be
 * used.
903
 *
904 905 906
 * @param $basename
 *   String filename
 * @param $directory
907
 *   String containing the directory or parent URI.
908
 *
909
 * @return
910 911
 *   File path consisting of $directory and a unique filename based off
 *   of $basename.
912
 */
913
function file_create_filename($basename, $directory) {
914 915 916
  // Strip control characters (ASCII value < 32). Though these are allowed in
  // some filesystems, not many applications handle them well.
  $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
917 918 919 920
  if (substr(PHP_OS, 0, 3) == 'WIN') {
    // These characters are not allowed in Windows filenames
    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
  }
921

922 923 924 925 926 927 928 929 930
  // A URI or path may already have a trailing slash or look like "public://".
  if (substr($directory, -1) == '/') {
    $separator = '';
  }
  else {
    $separator = '/';
  }

  $destination = $directory . $separator . $basename;
931

932
  if (file_exists($destination)) {
933
    // Destination file already exists, generate an alternative.
934 935
    $pos = strrpos($basename, '.');
    if ($pos !== FALSE) {
936 937 938 939 940
      $name = substr($basename, 0, $pos);
      $ext = substr($basename, $pos);
    }
    else {
      $name = $basename;
941
      $ext = '';
942 943 944 945
    }

    $counter = 0;
    do {
946
      $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
947
    } while (file_exists($destination));
948 949
  }

950
  return $destination;
951 952
}

953
/**
954
 * Deletes a file and its database record.
955
 *
956 957 958
 * Instead of directly deleting a file, it is strongly recommended to delete
 * file usages instead. That will automatically mark the file as temporary and
 * remove it during cleanup.
959
 *
960 961
 * @param $fid
 *   The file id.
962
 *
963
 * @see file_unmanaged_delete()
964
 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
965
 */
966 967 968
function file_delete($fid) {
  return file_delete_multiple(array($fid));
}
969

970 971 972 973 974 975 976 977 978 979 980
/**
 * Deletes files.
 *
 * Instead of directly deleting a file, it is strongly recommended to delete
 * file usages instead. That will automatically mark the file as temporary and
 * remove it during cleanup.
 *
 * @param $fid
 *   The file id.
 *
 * @see file_unmanaged_delete()
981
 * @see \Drupal\file\FileUsage\FileUsageBase::delete()
982 983 984
 */
function file_delete_multiple(array $fids) {
  entity_delete_multiple('file', $fids);
985 986 987
}

/**
988
 * Deletes a file without database changes or hook invocations.
989 990 991
 *
 * This function should be used when the file to be deleted does not have an
 * entry recorded in the files table.
992
 *
993
 * @param $path
994
 *   A string containing a file path or (streamwrapper) URI.
995
 *
996 997 998
 * @return
 *   TRUE for success or path does not exist, or FALSE in the event of an
 *   error.
999
 *
1000
 * @see file_delete()
1001
 * @see file_unmanaged_delete_recursive()
1002
 */
1003
function file_unmanaged_delete($path) {
1004
  if (is_dir($path)) {
1005
    watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR);
1006 1007
    return FALSE;
  }
1008
  if (is_file($path)) {
1009
    return drupal_unlink($path);
1010
  }
1011
  // Return TRUE for non-existent file, but log that nothing was actually
1012
  // deleted, as the current state is the intended result.
1013
  if (!file_exists($path)) {
1014
    watchdog('file', 'The file %path was not deleted because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE);
1015 1016
    return TRUE;
  }
1017 1018
  // We cannot handle anything other than files and directories. Log an error
  // for everything else (sockets, symbolic links, etc).
1019
  watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR);
1020
  return FALSE;
1021 1022
}

1023
/**
1024
 * Deletes all files and directories in the specified filepath recursively.
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
 *
 * If the specified path is a directory then the function will call itself
 * recursively to process the contents. Once the contents have been removed the
 * directory will also be removed.
 *
 * If the specified path is a file then it will be passed to
 * file_unmanaged_delete().
 *
 * Note that this only deletes visible files with write permission.
 *
 * @param $path
1036
 *   A string containing either an URI or a file or directory path.
1037 1038 1039 1040
 * @param $callback
 *   (optional) Callback function to run on each file prior to deleting it and
 *   on each directory prior to traversing it. For example, can be used to
 *   modify permissions.
1041
 *
1042
 * @return
1043
 *   TRUE for success or if path does not exist, FALSE in the event of an
1044 1045 1046 1047
 *   error.
 *
 * @see file_unmanaged_delete()
 */
1048 1049 1050 1051
function file_unmanaged_delete_recursive($path, $callback = NULL) {
  if (isset($callback)) {
    call_user_func($callback, $path);
  }
1052 1053 1054 1055 1056 1057 1058
  if (is_dir($path)) {
    $dir = dir($path);
    while (($entry = $dir->read()) !== FALSE) {
      if ($entry == '.' || $entry == '..') {
        continue;
      }
      $entry_path = $path . '/' . $entry;
1059
      file_unmanaged_delete_recursive($entry_path, $callback);
1060
    }
1061
    $dir->close();
1062 1063

    return drupal_rmdir($path);
1064 1065 1066 1067
  }
  return file_unmanaged_delete($path);
}

1068

1069

1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
/**
 * Moves an uploaded file to a new location.
 *
 * PHP's move_uploaded_file() does not properly support streams if safe_mode
 * or open_basedir are enabled, so this function fills that gap.
 *
 * Compatibility: normal paths and stream wrappers.
 *
 * @param $filename
 *   The filename of the uploaded file.
 * @param $uri
 *   A string containing the destination URI of the file.
 *
 * @return
 *   TRUE on success, or FALSE on failure.
 *
 * @see move_uploaded_file()
1087
 * @see http://drupal.org/node/515192
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
 * @ingroup php_wrappers
 */
function drupal_move_uploaded_file($filename, $uri) {
  $result = @move_uploaded_file($filename, $uri);
  // PHP's move_uploaded_file() does not properly support streams if safe_mode
  // or open_basedir are enabled so if the move failed, try finding a real path
  // and retry the move operation.
  if (!$result) {
    if ($realpath = drupal_realpath($uri)) {
      $result = move_uploaded_file($filename, $realpath);
    }
    else {
      $result = move_uploaded_file($filename, $uri);
    }
  }

  return $result;
}
1106

1107
/**
1108
 * Saves a file to the specified destination without invoking file API.
1109 1110
 *
 * This function is identical to file_save_data() except the file will not be
1111 1112
 * saved to the {file_managed} table and none of the file_* hooks will be
 * called.
1113 1114 1115 1116
 *
 * @param $data
 *   A string containing the contents of the file.
 * @param $destination
1117 1118
 *   A string containing the destination location. This must be a stream wrapper
 *   URI. If no value is provided, a randomized name will be generated and the
1119 1120
 *   file will be saved using Drupal's default files scheme, usually
 *   "public://".
1121 1122 1123 1124 1125 1126
 * @param $replace
 *   Replace behavior when the destination file already exists:
 *   - FILE_EXISTS_REPLACE - Replace the existing file.
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
 *                          unique.
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1127
 *
1128 1129
 * @return
 *   A string with the path of the resulting file, or FALSE on error.
1130
 *
1131 1132 1133
 * @see file_save_data()
 */
function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
1134
  // Write the data to a temporary file.
1135
  $temp_name = drupal_tempnam('temporary://', 'file');
1136
  if (file_put_contents($temp_name, $data) === FALSE) {
1137
    drupal_set_message(t('The file could not be created.'), 'error');
1138
    return FALSE;
1139 1140
  }

1141
  // Move the file to its final destination.
1142
  return file_unmanaged_move($temp_name, $destination, $replace);
1143 1144
}

1145
/**
1146
 * Finds all files that match a given mask in a given directory.
1147
 *
1148 1149 1150
 * Directories and files beginning with a period are excluded; this
 * prevents hidden files and directories (such as SVN working directories)
 * from being scanned.
1151
 *
1152
 * @param $dir
1153
 *   The base directory or URI to scan, without trailing slash.
1154
 * @param $mask
1155
 *   The preg_match() regular expression of the files to find.
1156
 * @param $options
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
 *   An associative array of additional options, with the following elements:
 *   - 'nomask': The preg_match() regular expression of the files to ignore.
 *     Defaults to '/(\.\.?|CVS)$/'.
 *   - 'callback': The callback function to call for each match. There is no
 *     default callback.
 *   - 'recurse': When TRUE, the directory scan will recurse the entire tree
 *     starting at the provided directory. Defaults to TRUE.
 *   - 'key': The key to be used for the returned associative array of files.
 *     Possible values are 'uri', for the file's URI; 'filename', for the
 *     basename of the file; and 'name' for the name of the file without the
 *     extension. Defaults to 'uri'.
 *   - 'min_depth': Minimum depth of directories to return files from. Defaults
 *     to 0.
1170
 * @param $depth
1171
 *   Current depth of recursion. This parameter is only used internally and
1172 1173
 *   should not be passed in.
 *
1174
 * @return
1175 1176
 *   An associative array (keyed on the chosen key) of objects with 'uri',
 *   'filename', and 'name' members corresponding to the matching files.
1177
 */
1178 1179 1180
function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
  // Merge in defaults.
  $options += array(
1181
    'nomask' => '/^CVS$/',
1182 1183
    'callback' => 0,
    'recurse' => TRUE,
1184
    'key' => 'uri',
1185 1186
    'min_depth' => 0,
  );
1187 1188 1189 1190 1191
  // Normalize $dir only once.
  if ($depth == 0) {
    $dir = file_stream_wrapper_uri_normalize($dir);
    $dir_has_slash = (substr($dir, -1) === '/');
  }
1192

1193
  $options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri';
1194
  $files = array();
1195 1196 1197
  // Avoid warnings when opendir does not have the permissions to open a
  // directory.
  if (is_dir($dir)) {
1198
    if ($handle = @opendir($dir)) {
1199
      while (FALSE !== ($filename = readdir($handle))) {
1200 1201 1202 1203 1204 1205 1206 1207 1208
        // Skip this file if it matches the nomask or starts with a dot.
        if ($filename[0] != '.' && !preg_match($options['nomask'], $filename)) {
          if ($depth == 0 && $dir_has_slash) {
            $uri = "$dir$filename";
          }
          else {
            $uri = "$dir/$filename";
          }
          if ($options['recurse'] && is_dir($uri)) {
1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
            // Give priority to files in this folder by merging them in after
            // any subdirectory files.
            $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
          }
          elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
            // Always use this match over anything already set in $files with
            // the same $options['key'].
            $file = new stdClass();
            $file->uri = $uri;
            $file->filename = $filename;
            $file->name = pathinfo($filename, PATHINFO_FILENAME);
            $key = $options['key'];
            $files[$file->$key] = $file;
            if ($options['callback']) {
              $options['callback']($uri);
            }
1225 1226 1227
          }
        }
      }
1228

1229 1230 1231 1232 1233
      closedir($handle);
    }
    else {
      watchdog('file', '@dir can not be opened', array('@dir' => $dir), WATCHDOG_ERROR);
    }
1234
  }
1235

1236 1237 1238
  return $files;
}

1239
/**
1240
 * Determines the maximum file upload size by querying the PHP settings.
1241 1242
 *
 * @return
1243 1244
 *   A file size limit in bytes based on the PHP upload_max_filesize and
 *   post_max_size
1245 1246 1247
 */
function file_upload_max_size() {
  static $max_size = -1;
1248

1249
  if ($max_size < 0) {
1250 1251 1252 1253 1254
    // Start with post_max_size.
    $max_size = parse_size(ini_get('post_max_size'));

    // If upload_max_size is less, then reduce. Except if upload_max_size is
    // zero, which indicates no limit.
1255
    $upload_max = parse_size(ini_get('upload_max_filesize'));
1256 1257 1258
    if ($upload_max > 0 && $upload_max < $max_size) {
      $max_size = $upload_max;
    }
1259 1260 1261
  }
  return $max_size;
}
1262

1263
/**
1264
 * Determines an Internet Media Type or MIME type from a filename.
1265
 *
1266 1267
 * @param $uri
 *   A string containing the URI, path, or filename.
1268
 * @param $mapping
1269 1270 1271
 *   An optional map of extensions to their mimetypes, in the form:
 *    - 'mimetypes': a list of mimetypes, keyed by an identifier,
 *    - 'extensions': the mapping itself, an associative array in which
1272 1273
 *      the key is the extension (lowercase) and the value is the mimetype
 *      identifier. If $mapping is NULL file_mimetype_mapping() is called.
1274 1275
 *
 * @return
1276 1277
 *   The internet media type registered for the extension or
 *   application/octet-stream for unknown extensions.
1278 1279
 *
 * @see file_default_mimetype_mapping()
1280
 */
1281 1282 1283
function file_get_mimetype($uri, $mapping = NULL) {
  if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) {
    return $wrapper->getMimeType($uri, $mapping);
1284
  }
1285 1286 1287
  else {
    // getMimeType() is not implementation specific, so we can directly
    // call it without an instance.
webchick's avatar
webchick committed
1288
    return LocalStream::getMimeType($uri, $mapping);
1289 1290
  }
}
1291

1292
/**
1293
 * Sets the permissions on a file or directory.
1294
 *
1295 1296
 * This function will use the file_chmod_directory and
 * file_chmod_file settings for the default modes for directories
1297 1298 1299 1300 1301
 * and uploaded/generated files. By default these will give everyone read access
 * so that users accessing the files with a user account without the webserver
 * group (e.g. via FTP) can read these files, and give group write permissions
 * so webserver group members (e.g. a vhost account) can alter files uploaded
 * and owned by the webserver.