Newer
Older
/**
* @file
* API for handling file uploads and server file management.
*/
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage;

Angie Byron
committed
use Drupal\Component\Utility\String;
use Drupal\Core\StreamWrapper\PublicStream;

Dries Buytaert
committed
/**
* Stream wrapper bit flags that are the basis for composite types.

Dries Buytaert
committed
*
* 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;
/**
* 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;
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.

Dries Buytaert
committed
*/
define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL);

Dries Buytaert
committed

Angie Byron
committed
*

Dries Buytaert
committed
* Fields on the file entity:

Dries Buytaert
committed
* - 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
* the basename of the filepath if the file is renamed to avoid overwriting
* an existing file.

Dries Buytaert
committed
* - 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
* bits are reserved for Drupal core. The least significant bit indicates

Dries Buytaert
committed
* temporary (0) or permanent (1). Temporary files older than
* DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs.

Dries Buytaert
committed
* - timestamp: UNIX timestamp for the date the file was added to the database.
* Flag used by file_prepare_directory() -- create directory if not present.
const FILE_CREATE_DIRECTORY = 1;
* Flag used by file_prepare_directory() -- file permissions may be changed.
const FILE_MODIFY_PERMISSIONS = 2;

Angie Byron
committed
* Flag for dealing with existing files: Appends number until name is unique.
const FILE_EXISTS_RENAME = 0;
/**
* Flag for dealing with existing files: Replace the existing file.
*/
const FILE_EXISTS_REPLACE = 1;
/**
* Flag for dealing with existing files: Do nothing and return FALSE.
*/
const FILE_EXISTS_ERROR = 2;
/**

Dries Buytaert
committed
* 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.
const FILE_STATUS_PERMANENT = 1;

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Provides Drupal stream wrapper registry.

Dries Buytaert
committed
*
* 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".
*

Dries Buytaert
committed
* 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

Angie Byron
committed
* $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);

Dries Buytaert
committed
* @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

Angie Byron
committed
* $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));

Dries Buytaert
committed
* @endcode
*

Dries Buytaert
committed
* @param $filter

Dries Buytaert
committed
* (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.

Dries Buytaert
committed
*

Dries Buytaert
committed
* @return

Dries Buytaert
committed
* 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.

Dries Buytaert
committed
* @see hook_stream_wrappers()
* @see hook_stream_wrappers_alter()
*/

Dries Buytaert
committed
function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
$wrappers_storage = &drupal_static(__FUNCTION__);

Dries Buytaert
committed

Dries Buytaert
committed
if (!isset($wrappers_storage)) {

Alex Pott
committed
$wrappers = array();
$container = \Drupal::getContainer();
if (is_object($container) && $container->has('module_handler')) {
$wrappers = \Drupal::moduleHandler()->invokeAll('stream_wrappers');
foreach ($wrappers as $scheme => $info) {
// Add defaults.
$wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
}
\Drupal::moduleHandler()->alter('stream_wrappers', $wrappers);

Dries Buytaert
committed
}

Dries Buytaert
committed
$existing = stream_get_wrappers();
foreach ($wrappers as $scheme => $info) {
// We only register classes that implement our interface.
if (in_array('Drupal\Core\StreamWrapper\StreamWrapperInterface', class_implements($info['class']), TRUE)) {

Dries Buytaert
committed
// 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;
}

Dries Buytaert
committed
if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
stream_wrapper_register($scheme, $info['class']);

Dries Buytaert
committed
stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL);

Dries Buytaert
committed
}

Dries Buytaert
committed
// 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];
}

Dries Buytaert
committed
}
}

Dries Buytaert
committed
if (!isset($wrappers_storage[$filter])) {
$wrappers_storage[$filter] = array();
foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) {
// Bit-wise filter.

Dries Buytaert
committed
if (($info['type'] & $filter) == $filter) {

Dries Buytaert
committed
$wrappers_storage[$filter][$scheme] = $info;
}
}
}
return $wrappers_storage[$filter];

Dries Buytaert
committed
}
/**
* Returns the stream wrapper class name for a given scheme.
*
* @param $scheme
* Stream scheme.

Dries Buytaert
committed
* @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".

Dries Buytaert
committed
* @return
* A string containing the name of the scheme, or FALSE if none. For example,
* the URI "public://example.txt" would return "public".

Dries Buytaert
committed
*
* @see file_uri_target()

Dries Buytaert
committed
*/
function file_uri_scheme($uri) {
$position = strpos($uri, '://');
return $position ? substr($uri, 0, $position) : FALSE;

Dries Buytaert
committed
}
/**

Dries Buytaert
committed
* Checks that the scheme of a stream URI is valid.

Dries Buytaert
committed
*
* 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".

Dries Buytaert
committed
* @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) {
return $scheme && class_exists(file_stream_wrapper_get_class($scheme));

Dries Buytaert
committed
}

Angie Byron
committed

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Returns the part of a URI after the schema.

Dries Buytaert
committed
*
* @param $uri
* A stream, referenced as "scheme://target".

Dries Buytaert
committed
* @return
* A string containing the target (path), or FALSE if none.
* For example, the URI "public://sample/test.txt" would return
* "sample/test.txt".

Dries Buytaert
committed
*
* @see file_uri_scheme()

Dries Buytaert
committed
*/
function file_uri_target($uri) {

Dries Buytaert
committed
$data = explode('://', $uri, 2);
// Remove erroneous leading or trailing, forward-slashes and backslashes.
return count($data) == 2 ? trim($data[1], '\/') : FALSE;

Dries Buytaert
committed
}

Dries Buytaert
committed
* Gets the default file stream implementation.
*
* @return
* 'public', 'private' or any other file scheme defined as the default.
*/
function file_default_scheme() {
return \Drupal::config('system.file')->get('default_scheme');
}

Dries Buytaert
committed
/**
* 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.
* @return
* The normalized URI.

Dries Buytaert
committed
*/
function file_stream_wrapper_uri_normalize($uri) {
$scheme = file_uri_scheme($uri);
if (file_stream_wrapper_valid_scheme($scheme)) {

Dries Buytaert
committed
$target = file_uri_target($uri);

Dries Buytaert
committed
if ($target !== FALSE) {
$uri = $scheme . '://' . $target;
}

Dries Buytaert
committed
}

Dries Buytaert
committed
return $uri;
}
/**

Dries Buytaert
committed
* Returns a reference to the stream wrapper class responsible for a given URI.

Dries Buytaert
committed
*
* 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".

Dries Buytaert
committed
* @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

Dries Buytaert
committed
*/
function file_stream_wrapper_get_instance_by_uri($uri) {
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;
}

Dries Buytaert
committed
}
return FALSE;

Dries Buytaert
committed
}
/**

Dries Buytaert
committed
* Returns a reference to the stream wrapper class responsible for a scheme.

Dries Buytaert
committed
*
* 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.
* @return \Drupal\Core\StreamWrapper\StreamWrapperInterface

Dries Buytaert
committed
* Returns a new stream wrapper object appropriate for the given $scheme.
* For example, for the public scheme a stream wrapper object

Dries Buytaert
committed
* 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)) {

Dries Buytaert
committed
$instance = new $class();

Dries Buytaert
committed
$instance->setUri($scheme . '://');
return $instance;
}
else {
return FALSE;
}
}

Dries Buytaert
committed
* Creates a web-accessible URL for a stream to an external or local file.
* Compatibility: normal paths and stream wrappers.

Dries Buytaert
committed
* There are two kinds of local files:
* - "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).

Dries Buytaert
committed
* - "shipped files", i.e. those outside of the files directory, which ship as
* part of Drupal core or contributed modules or themes.
*
* @param $uri

Dries Buytaert
committed
* The URI to a file for which we need an external URL, or the path to a
* shipped file.
* A string containing a URL that may be used to access the file.
* 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.

Dries Buytaert
committed
*
* @see http://drupal.org/node/515192
* @see file_url_transform_relative()
function file_create_url($uri) {

Dries Buytaert
committed
// Allow the URI to be altered, e.g. to serve a file from a CDN or static
// file server.
\Drupal::moduleHandler()->alter('file_url', $uri);

Dries Buytaert
committed
$scheme = file_uri_scheme($uri);
if (!$scheme) {
// 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.
// Therefore, return the urlencoded URI with the base URL prepended.
return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri);
}
elseif ($scheme == 'http' || $scheme == 'https') {

Angie Byron
committed
// Check for HTTP so that we don't have to implement getExternalUrl() for
// the HTTP wrapper.
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;
}
}
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
519
520
521
522
/**
* 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);
}

Dries Buytaert
committed
* Checks that the directory exists and is writable.
*
* Directories need to have execute permissions to be considered a directory by
* FTP servers, etc.
*
* @param $directory
* A string reference containing the name of a directory path or URI. A
* trailing slash will be trimmed from a path.
* @param $options

Dries Buytaert
committed
* 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).
* TRUE if the directory exists (or was created) and is writable. FALSE
* otherwise.
function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) {
// Only trim if we're not dealing with a stream.
$directory = rtrim($directory, '/\\');
}

Angie Byron
committed
// Let mkdir() recursively create directories and use the default directory
// permissions.

Angie Byron
committed
if ($options & FILE_CREATE_DIRECTORY) {
return @drupal_mkdir($directory, NULL, TRUE);
return FALSE;
// The directory exists, so check to see if it is writable.
$writable = is_writable($directory);
if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
return drupal_chmod($directory);
return $writable;

Dries Buytaert
committed
* Creates a .htaccess file in each Drupal files directory if it is missing.
function file_ensure_htaccess() {

Dries Buytaert
committed
file_save_htaccess('public://', FALSE);
$private_path = \Drupal::config('system.file')->get('path.private');
if (!empty($private_path)) {

Dries Buytaert
committed
file_save_htaccess('private://', TRUE);

Angie Byron
committed
}

Dries Buytaert
committed
file_save_htaccess('temporary://', TRUE);

Greg Dunlap
committed
file_save_htaccess(config_get_config_directory(), TRUE);
file_save_htaccess(config_get_config_directory(CONFIG_STAGING_DIRECTORY), TRUE);

Dries Buytaert
committed
* Creates a .htaccess file in the given directory.
* @param $directory
* 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.
*
* @return bool
* TRUE if the .htaccess file could be created or existed already, FALSE
* otherwise.

Dries Buytaert
committed
function file_save_htaccess($directory, $private = TRUE) {
if (file_uri_scheme($directory)) {
$htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
}
else {
$directory = rtrim($directory, '/\\');
$htaccess_path = $directory . '/.htaccess';
if (file_exists($htaccess_path)) {
// Short circuit if the .htaccess file already exists.
return TRUE;
}
if ($private) {
// Private .htaccess file.
$htaccess_lines = MTimeProtectedFastFileStorage::HTACCESS;
}
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_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) {
return drupal_chmod($htaccess_path, 0444);
}
else {

Angie Byron
committed
$variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(String::checkPlain($htaccess_lines)));

Angie Byron
committed
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);
return FALSE;

Angie Byron
committed
/**

Dries Buytaert
committed
* Determines whether the URI has a valid scheme for file API operations.

Angie Byron
committed
*
* 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);
if (!file_stream_wrapper_valid_scheme($uri_scheme)) {

Angie Byron
committed
return FALSE;
}
return TRUE;
}
/**

Dries Buytaert
committed
* Copies a file to a new location without invoking the file API.

Angie Byron
committed
* 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.
* - If the $source and $destination are equal, the behavior depends on the
* $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
* will rename the file until the $destination is unique.
* - 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

Angie Byron
committed
*
* @param $source

Angie Byron
committed
* A string specifying the filepath or URI of the source file.

Angie Byron
committed
* @param $destination

Angie Byron
committed
* A URI containing the destination that $source should be copied to. The
* URI may be a bare filepath (without a scheme). If this value is omitted,
* Drupal's default files scheme will be used, usually "public://".

Angie Byron
committed
* @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.

Angie Byron
committed
* @return
* The path to the new file, or FALSE in the event of an error.
* @see file_copy()
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {

Dries Buytaert
committed
$original_source = $source;
// Assert that the source file actually exists.

Angie Byron
committed
if (!file_exists($source)) {
// @todo Replace drupal_set_message() calls with exceptions instead.

Dries Buytaert
committed
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');
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));
}

Angie Byron
committed
return FALSE;
}
// Build a destination URI if necessary.
if (!isset($destination)) {
$destination = file_build_uri(drupal_basename($source));
}
// Prepare the destination directory.
if (file_prepare_directory($destination)) {
// The destination is already a directory, so append the source basename.
$destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
}
else {
// Perhaps $destination is a dir/file?
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.

Dries Buytaert
committed
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');
return FALSE;
}
}
// Determine whether we can perform this operation based on overwrite rules.
$destination = file_destination($destination, $replace);

Angie Byron
committed
if ($destination === FALSE) {

Angie Byron
committed
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');
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));

Angie Byron
committed
return FALSE;
// Assert that the source and destination filenames are not the same.
$real_source = drupal_realpath($source);
$real_destination = drupal_realpath($destination);
if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {

Angie Byron
committed
drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');

Angie Byron
committed
watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source));

Angie Byron
committed
return FALSE;
// Make sure the .htaccess files are present.
file_ensure_htaccess();
// Perform the copy operation.

Angie Byron
committed
if (!@copy($source, $destination)) {
// 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;
}
// Set the permissions on the new file.
drupal_chmod($destination);

Angie Byron
committed
return $destination;
/**

Dries Buytaert
committed
* Constructs a URI to Drupal's default files location given a relative path.
*/
function file_build_uri($path) {
$uri = file_default_scheme() . '://' . $path;
return file_stream_wrapper_uri_normalize($uri);
}
/**

Dries Buytaert
committed
* Determines the destination path for a file.
*
* @param $destination
* A string specifying the desired final URI or filepath.
* @param $replace
* Replace behavior when the destination file already exists.

Angie Byron
committed
* - 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.
* The destination filepath, or FALSE if the file already exists
* and FILE_EXISTS_ERROR is specified.
*/
function file_destination($destination, $replace) {
if (file_exists($destination)) {
switch ($replace) {

Angie Byron
committed
case FILE_EXISTS_REPLACE:
// Do nothing here, we want to overwrite the existing file.
break;
case FILE_EXISTS_RENAME:
$basename = drupal_basename($destination);
$directory = drupal_dirname($destination);
$destination = file_create_filename($basename, $directory);
break;
case FILE_EXISTS_ERROR:
// Error reporting handled by calling function.
return FALSE;
}
}
return $destination;
}
/**

Dries Buytaert
committed
* Moves a file to a new location without database changes or hook invocation.

Angie Byron
committed
* @param $source
* A string specifying the filepath or URI of the original file.

Angie Byron
committed
* @param $destination
* 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://".

Angie Byron
committed
* @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.

Angie Byron
committed
* @return
* The URI of the moved file, or FALSE in the event of an error.
* @see file_move()
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) {

Angie Byron
committed
return FALSE;

Angie Byron
committed
return $filepath;
/**

Dries Buytaert
committed
* Modifies a filename as needed for security purposes.

Dries Buytaert
committed
* 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

Dries Buytaert
committed
* between 2 and 5 characters in length, internal to the file name, and not

Dries Buytaert
committed
* included in $extensions.
*
* 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'. *
* @param $filename

Dries Buytaert
committed
* File name to modify.
* @param $extensions

Dries Buytaert
committed
* A space-separated list of extensions that should not be altered.
* @param $alerts

Dries Buytaert
committed
* If TRUE, drupal_set_message() will be called to display a message if the
* file name was changed.
*
* @return string

Dries Buytaert
committed
* The potentially modified $filename.
*/
function file_munge_filename($filename, $extensions, $alerts = TRUE) {
$original = $filename;
// Allow potentially insecure uploads for very savvy users and admin
if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
// Remove any null bytes. See http://php.net/manual/en/security.filesystem.nullbytes.php
$filename = str_replace(chr(0), '', $filename);
$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) {
$new_filename .= '.' . $filename_part;
if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
$new_filename .= '_';
}
}
$filename = $new_filename . '.' . $final_extension;
if ($alerts && $original != $filename) {
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
}
}
return $filename;
}
/**

Dries Buytaert
committed
* Undoes the effect of file_munge_filename().
*
* @param $filename
* String with the filename to be unmunged.
* @return
* An unmunged filename string.
*/
function file_unmunge_filename($filename) {
return str_replace('_.', '.', $filename);
}
/**

Dries Buytaert
committed
* Creates a full file path from a directory and filename.
*
* If a file with the specified name already exists, an alternative will be
* used.
*
* @param $basename
* String filename
* @param $directory
* String containing the directory or parent URI.
* @return
* File path consisting of $directory and a unique filename based off
* of $basename.
*/

Dries Buytaert
committed
// 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);
if (substr(PHP_OS, 0, 3) == 'WIN') {
// These characters are not allowed in Windows filenames
$basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
}

Dries Buytaert
committed
// 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;

Angie Byron
committed
if (file_exists($destination)) {

Angie Byron
committed
$pos = strrpos($basename, '.');
if ($pos !== FALSE) {
$name = substr($basename, 0, $pos);
$ext = substr($basename, $pos);
}
else {
$name = $basename;

Angie Byron
committed
$ext = '';
$destination = $directory . $separator . $name . '_' . $counter++ . $ext;

Angie Byron
committed
} while (file_exists($destination));

Angie Byron
committed
return $destination;
/**

Dries Buytaert
committed
* Deletes a file and its database record.

catch
committed
* 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.

Dries Buytaert
committed
* @param $fid
* The file id.
* @see file_unmanaged_delete()
* @see \Drupal\file\FileUsage\FileUsageBase::delete()
*/

Dries Buytaert
committed
function file_delete($fid) {
return file_delete_multiple(array($fid));
}

catch
committed

Dries Buytaert
committed
/**
* 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()
* @see \Drupal\file\FileUsage\FileUsageBase::delete()

Dries Buytaert
committed
*/
function file_delete_multiple(array $fids) {
entity_delete_multiple('file', $fids);
}
/**

Dries Buytaert
committed
* Deletes a file without database changes or hook invocations.
*
* This function should be used when the file to be deleted does not have an
* entry recorded in the files table.