Backup of db before drupal security update
[yaffs-website] / web / core / includes / file.inc
1 <?php
2
3 /**
4  * @file
5  * API for handling file uploads and server file management.
6  */
7
8 use Drupal\Component\FileSystem\FileSystem as ComponentFileSystem;
9 use Drupal\Component\Utility\Unicode;
10 use Drupal\Component\Utility\UrlHelper;
11 use Drupal\Component\PhpStorage\FileStorage;
12 use Drupal\Component\Utility\Bytes;
13 use Drupal\Core\File\FileSystem;
14 use Drupal\Core\Site\Settings;
15 use Drupal\Core\StreamWrapper\PublicStream;
16 use Drupal\Core\StreamWrapper\PrivateStream;
17
18 /**
19  * Default mode for new directories. See drupal_chmod().
20  *
21  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
22  *   Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY.
23  *
24  * @see https://www.drupal.org/node/2418133
25  */
26 const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY;
27
28 /**
29  * Default mode for new files. See drupal_chmod().
30  *
31  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
32  *   Use \Drupal\Core\File\FileSystem::CHMOD_FILE.
33  *
34  * @see https://www.drupal.org/node/2418133
35  */
36 const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE;
37
38 /**
39  * @defgroup file File interface
40  * @{
41  * Common file handling functions.
42  */
43
44 /**
45  * Flag used by file_prepare_directory() -- create directory if not present.
46  */
47 const FILE_CREATE_DIRECTORY = 1;
48
49 /**
50  * Flag used by file_prepare_directory() -- file permissions may be changed.
51  */
52 const FILE_MODIFY_PERMISSIONS = 2;
53
54 /**
55  * Flag for dealing with existing files: Appends number until name is unique.
56  */
57 const FILE_EXISTS_RENAME = 0;
58
59 /**
60  * Flag for dealing with existing files: Replace the existing file.
61  */
62 const FILE_EXISTS_REPLACE = 1;
63
64 /**
65  * Flag for dealing with existing files: Do nothing and return FALSE.
66  */
67 const FILE_EXISTS_ERROR = 2;
68
69 /**
70  * Indicates that the file is permanent and should not be deleted.
71  *
72  * Temporary files older than the system.file.temporary_maximum_age
73  * configuration value will be, if clean-up not disabled, removed during cron
74  * runs, but permanent files will not be removed during the file garbage
75  * collection process.
76  */
77 const FILE_STATUS_PERMANENT = 1;
78
79 /**
80  * Returns the scheme of a URI (e.g. a stream).
81  *
82  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
83  *   Use \Drupal\Core\File\FileSystem::uriScheme().
84  *
85  * @see https://www.drupal.org/node/2418133
86  */
87 function file_uri_scheme($uri) {
88   return \Drupal::service('file_system')->uriScheme($uri);
89 }
90
91 /**
92  * Checks that the scheme of a stream URI is valid.
93  *
94  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
95  *   Use \Drupal\Core\File\FileSystem::validScheme().
96  *
97  * @see https://www.drupal.org/node/2418133
98  */
99 function file_stream_wrapper_valid_scheme($scheme) {
100   return \Drupal::service('file_system')->validScheme($scheme);
101 }
102
103
104 /**
105  * Returns the part of a URI after the schema.
106  *
107  * @param string $uri
108  *   A stream, referenced as "scheme://target" or "data:target".
109  *
110  * @return string|bool
111  *   A string containing the target (path), or FALSE if none.
112  *   For example, the URI "public://sample/test.txt" would return
113  *   "sample/test.txt".
114  *
115  * @see file_uri_scheme()
116  */
117 function file_uri_target($uri) {
118   // Remove the scheme from the URI and remove erroneous leading or trailing,
119   // forward-slashes and backslashes.
120   $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/');
121
122   // If nothing was replaced, the URI doesn't have a valid scheme.
123   return $target !== $uri ? $target : FALSE;
124 }
125
126 /**
127  * Gets the default file stream implementation.
128  *
129  * @return string
130  *   'public', 'private' or any other file scheme defined as the default.
131  */
132 function file_default_scheme() {
133   return \Drupal::config('system.file')->get('default_scheme');
134 }
135
136 /**
137  * Normalizes a URI by making it syntactically correct.
138  *
139  * A stream is referenced as "scheme://target".
140  *
141  * The following actions are taken:
142  * - Remove trailing slashes from target
143  * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
144  *
145  * @param string $uri
146  *   String reference containing the URI to normalize.
147  *
148  * @return string
149  *   The normalized URI.
150  */
151 function file_stream_wrapper_uri_normalize($uri) {
152   $scheme = \Drupal::service('file_system')->uriScheme($uri);
153
154   if (file_stream_wrapper_valid_scheme($scheme)) {
155     $target = file_uri_target($uri);
156
157     if ($target !== FALSE) {
158       $uri = $scheme . '://' . $target;
159     }
160   }
161
162   return $uri;
163 }
164
165 /**
166  * Creates a web-accessible URL for a stream to an external or local file.
167  *
168  * Compatibility: normal paths and stream wrappers.
169  *
170  * There are two kinds of local files:
171  * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
172  *   These are files that have either been uploaded by users or were generated
173  *   automatically (for example through CSS aggregation).
174  * - "shipped files", i.e. those outside of the files directory, which ship as
175  *   part of Drupal core or contributed modules or themes.
176  *
177  * @param string $uri
178  *   The URI to a file for which we need an external URL, or the path to a
179  *   shipped file.
180  *
181  * @return string
182  *   A string containing a URL that may be used to access the file.
183  *   If the provided string already contains a preceding 'http', 'https', or
184  *   '/', nothing is done and the same string is returned. If a stream wrapper
185  *   could not be found to generate an external URL, then FALSE is returned.
186  *
187  * @see https://www.drupal.org/node/515192
188  * @see file_url_transform_relative()
189  */
190 function file_create_url($uri) {
191   // Allow the URI to be altered, e.g. to serve a file from a CDN or static
192   // file server.
193   \Drupal::moduleHandler()->alter('file_url', $uri);
194
195   $scheme = \Drupal::service('file_system')->uriScheme($uri);
196
197   if (!$scheme) {
198     // Allow for:
199     // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
200     // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
201     //   http://example.com/bar.jpg by the browser when viewing a page over
202     //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
203     // Both types of relative URIs are characterized by a leading slash, hence
204     // we can use a single check.
205     if (Unicode::substr($uri, 0, 1) == '/') {
206       return $uri;
207     }
208     else {
209       // If this is not a properly formatted stream, then it is a shipped file.
210       // Therefore, return the urlencoded URI with the base URL prepended.
211       $options = UrlHelper::parse($uri);
212       $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
213       // Append the query.
214       if ($options['query']) {
215         $path .= '?' . UrlHelper::buildQuery($options['query']);
216       }
217
218       // Append fragment.
219       if ($options['fragment']) {
220         $path .= '#' . $options['fragment'];
221       }
222
223       return $path;
224     }
225   }
226   elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
227     // Check for HTTP and data URI-encoded URLs so that we don't have to
228     // implement getExternalUrl() for the HTTP and data schemes.
229     return $uri;
230   }
231   else {
232     // Attempt to return an external URL using the appropriate wrapper.
233     if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
234       return $wrapper->getExternalUrl();
235     }
236     else {
237       return FALSE;
238     }
239   }
240 }
241
242 /**
243  * Transforms an absolute URL of a local file to a relative URL.
244  *
245  * May be useful to prevent problems on multisite set-ups and prevent mixed
246  * content errors when using HTTPS + HTTP.
247  *
248  * @param string $file_url
249  *   A file URL of a local file as generated by file_create_url().
250  *
251  * @return string
252  *   If the file URL indeed pointed to a local file and was indeed absolute,
253  *   then the transformed, relative URL to the local file. Otherwise: the
254  *   original value of $file_url.
255  *
256  * @see file_create_url()
257  */
258 function file_url_transform_relative($file_url) {
259   // Unfortunately, we pretty much have to duplicate Symfony's
260   // Request::getHttpHost() method because Request::getPort() may return NULL
261   // instead of a port number.
262   $request = \Drupal::request();
263   $host = $request->getHost();
264   $scheme = $request->getScheme();
265   $port = $request->getPort() ?: 80;
266   if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
267     $http_host = $host;
268   }
269   else {
270     $http_host = $host . ':' . $port;
271   }
272
273   return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
274 }
275
276 /**
277  * Checks that the directory exists and is writable.
278  *
279  * Directories need to have execute permissions to be considered a directory by
280  * FTP servers, etc.
281  *
282  * @param $directory
283  *   A string reference containing the name of a directory path or URI. A
284  *   trailing slash will be trimmed from a path.
285  * @param $options
286  *   A bitmask to indicate if the directory should be created if it does
287  *   not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only
288  *   (FILE_MODIFY_PERMISSIONS).
289  *
290  * @return
291  *   TRUE if the directory exists (or was created) and is writable. FALSE
292  *   otherwise.
293  */
294 function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) {
295   if (!file_stream_wrapper_valid_scheme(\Drupal::service('file_system')->uriScheme($directory))) {
296     // Only trim if we're not dealing with a stream.
297     $directory = rtrim($directory, '/\\');
298   }
299
300   // Check if directory exists.
301   if (!is_dir($directory)) {
302     // Let mkdir() recursively create directories and use the default directory
303     // permissions.
304     if ($options & FILE_CREATE_DIRECTORY) {
305       return @drupal_mkdir($directory, NULL, TRUE);
306     }
307     return FALSE;
308   }
309   // The directory exists, so check to see if it is writable.
310   $writable = is_writable($directory);
311   if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) {
312     return drupal_chmod($directory);
313   }
314
315   return $writable;
316 }
317
318 /**
319  * Creates a .htaccess file in each Drupal files directory if it is missing.
320  */
321 function file_ensure_htaccess() {
322   file_save_htaccess('public://', FALSE);
323   $private_path = PrivateStream::basePath();
324   if (!empty($private_path)) {
325     file_save_htaccess('private://', TRUE);
326   }
327   file_save_htaccess('temporary://', TRUE);
328
329   // If a staging directory exists then it should contain a .htaccess file.
330   // @todo https://www.drupal.org/node/2696103 catch a more specific exception
331   //   and simplify this code.
332   try {
333     $staging = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
334   }
335   catch (\Exception $e) {
336     $staging = FALSE;
337   }
338   if ($staging) {
339     // Note that we log an error here if we can't write the .htaccess file. This
340     // can occur if the staging directory is read-only. If it is then it is the
341     // user's responsibility to create the .htaccess file.
342     file_save_htaccess($staging, TRUE);
343   }
344 }
345
346 /**
347  * Creates a .htaccess file in the given directory.
348  *
349  * @param string $directory
350  *   The directory.
351  * @param bool $private
352  *   (Optional) FALSE indicates that $directory should be a web-accessible
353  *   directory. Defaults to TRUE which indicates a private directory.
354  * @param bool $force_overwrite
355  *   (Optional) Set to TRUE to attempt to overwrite the existing .htaccess file
356  *   if one is already present. Defaults to FALSE.
357  */
358 function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
359   if (\Drupal::service('file_system')->uriScheme($directory)) {
360     $htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
361   }
362   else {
363     $directory = rtrim($directory, '/\\');
364     $htaccess_path = $directory . '/.htaccess';
365   }
366
367   if (file_exists($htaccess_path) && !$force_overwrite) {
368     // Short circuit if the .htaccess file already exists.
369     return TRUE;
370   }
371   $htaccess_lines = FileStorage::htaccessLines($private);
372
373   // Write the .htaccess file.
374   if (file_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) {
375     return drupal_chmod($htaccess_path, 0444);
376   }
377   else {
378     $variables = ['%directory' => $directory, '@htaccess' => $htaccess_lines];
379     \Drupal::logger('security')->error("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <pre><code>@htaccess</code></pre>", $variables);
380     return FALSE;
381   }
382 }
383
384 /**
385  * Returns the standard .htaccess lines that Drupal writes to file directories.
386  *
387  * @param bool $private
388  *   (Optional) Set to FALSE to return the .htaccess lines for a web-accessible
389  *   public directory. The default is TRUE, which returns the .htaccess lines
390  *   for a private directory that should not be web-accessible.
391  *
392  * @return string
393  *   The desired contents of the .htaccess file.
394  *
395  * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
396  *   Use \Drupal\Component\PhpStorage\FileStorage::htaccessLines().
397  *
398  * @see https://www.drupal.org/node/2418133
399  */
400 function file_htaccess_lines($private = TRUE) {
401   return FileStorage::htaccessLines($private);
402 }
403
404 /**
405  * Determines whether the URI has a valid scheme for file API operations.
406  *
407  * There must be a scheme and it must be a Drupal-provided scheme like
408  * 'public', 'private', 'temporary', or an extension provided with
409  * hook_stream_wrappers().
410  *
411  * @param $uri
412  *   The URI to be tested.
413  *
414  * @return
415  *   TRUE if the URI is allowed.
416  */
417 function file_valid_uri($uri) {
418   // Assert that the URI has an allowed scheme. Bare paths are not allowed.
419   $uri_scheme = \Drupal::service('file_system')->uriScheme($uri);
420   if (!file_stream_wrapper_valid_scheme($uri_scheme)) {
421     return FALSE;
422   }
423   return TRUE;
424 }
425
426 /**
427  * Copies a file to a new location without database changes or hook invocation.
428  *
429  * This is a powerful function that in many ways performs like an advanced
430  * version of copy().
431  * - Checks if $source and $destination are valid and readable/writable.
432  * - If file already exists in $destination either the call will error out,
433  *   replace the file or rename the file based on the $replace parameter.
434  * - If the $source and $destination are equal, the behavior depends on the
435  *   $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME
436  *   will rename the file until the $destination is unique.
437  * - Works around a PHP bug where copy() does not properly support streams if
438  *   safe_mode or open_basedir are enabled.
439  *   @see https://bugs.php.net/bug.php?id=60456
440  *
441  * @param $source
442  *   A string specifying the filepath or URI of the source file.
443  * @param $destination
444  *   A URI containing the destination that $source should be copied to. The
445  *   URI may be a bare filepath (without a scheme). If this value is omitted,
446  *   Drupal's default files scheme will be used, usually "public://".
447  * @param $replace
448  *   Replace behavior when the destination file already exists:
449  *   - FILE_EXISTS_REPLACE - Replace the existing file.
450  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
451  *       unique.
452  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
453  *
454  * @return
455  *   The path to the new file, or FALSE in the event of an error.
456  *
457  * @see file_copy()
458  */
459 function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
460   if (!file_unmanaged_prepare($source, $destination, $replace)) {
461     return FALSE;
462   }
463   // Attempt to resolve the URIs. This is necessary in certain configurations
464   // (see above).
465   $real_source = drupal_realpath($source) ?: $source;
466   $real_destination = drupal_realpath($destination) ?: $destination;
467   // Perform the copy operation.
468   if (!@copy($real_source, $real_destination)) {
469     \Drupal::logger('file')->error('The specified file %file could not be copied to %destination.', ['%file' => $source, '%destination' => $destination]);
470     return FALSE;
471   }
472   // Set the permissions on the new file.
473   drupal_chmod($destination);
474   return $destination;
475 }
476
477 /**
478  * Internal function that prepares the destination for a file_unmanaged_copy or
479  * file_unmanaged_move operation.
480  *
481  * - Checks if $source and $destination are valid and readable/writable.
482  * - Checks that $source is not equal to $destination; if they are an error
483  *   is reported.
484  * - If file already exists in $destination either the call will error out,
485  *   replace the file or rename the file based on the $replace parameter.
486  *
487  * @param $source
488  *   A string specifying the filepath or URI of the source file.
489  * @param $destination
490  *   A URI containing the destination that $source should be moved/copied to.
491  *   The URI may be a bare filepath (without a scheme) and in that case the
492  *   default scheme (file://) will be used. If this value is omitted, Drupal's
493  *   default files scheme will be used, usually "public://".
494  * @param $replace
495  *   Replace behavior when the destination file already exists:
496  *   - FILE_EXISTS_REPLACE - Replace the existing file.
497  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
498  *       unique.
499  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
500  *
501  * @return
502  *   TRUE, or FALSE in the event of an error.
503  *
504  * @see file_unmanaged_copy()
505  * @see file_unmanaged_move()
506  */
507 function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) {
508   $original_source = $source;
509   $logger = \Drupal::logger('file');
510
511   // Assert that the source file actually exists.
512   if (!file_exists($source)) {
513     // @todo Replace drupal_set_message() calls with exceptions instead.
514     drupal_set_message(t('The specified file %file could not be moved/copied because no file by that name exists. Please check that you supplied the correct filename.', ['%file' => $original_source]), 'error');
515     if (($realpath = drupal_realpath($original_source)) !== FALSE) {
516       $logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', ['%file' => $original_source, '%realpath' => $realpath]);
517     }
518     else {
519       $logger->notice('File %file could not be moved/copied because it does not exist.', ['%file' => $original_source]);
520     }
521     return FALSE;
522   }
523
524   // Build a destination URI if necessary.
525   if (!isset($destination)) {
526     $destination = file_build_uri(drupal_basename($source));
527   }
528
529
530   // Prepare the destination directory.
531   if (file_prepare_directory($destination)) {
532     // The destination is already a directory, so append the source basename.
533     $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
534   }
535   else {
536     // Perhaps $destination is a dir/file?
537     $dirname = drupal_dirname($destination);
538     if (!file_prepare_directory($dirname)) {
539       // The destination is not valid.
540       $logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', ['%file' => $original_source, '%destination' => $dirname]);
541       drupal_set_message(t('The specified file %file could not be moved/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.', ['%file' => $original_source]), 'error');
542       return FALSE;
543     }
544   }
545
546   // Determine whether we can perform this operation based on overwrite rules.
547   $destination = file_destination($destination, $replace);
548   if ($destination === FALSE) {
549     drupal_set_message(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', ['%file' => $original_source]), 'error');
550     $logger->notice('File %file could not be moved/copied because a file by that name already exists in the destination directory (%destination)', ['%file' => $original_source, '%destination' => $destination]);
551     return FALSE;
552   }
553
554   // Assert that the source and destination filenames are not the same.
555   $real_source = drupal_realpath($source);
556   $real_destination = drupal_realpath($destination);
557   if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) {
558     drupal_set_message(t('The specified file %file was not moved/copied because it would overwrite itself.', ['%file' => $source]), 'error');
559     $logger->notice('File %file could not be moved/copied because it would overwrite itself.', ['%file' => $source]);
560     return FALSE;
561   }
562   // Make sure the .htaccess files are present.
563   file_ensure_htaccess();
564   return TRUE;
565 }
566
567 /**
568  * Constructs a URI to Drupal's default files location given a relative path.
569  */
570 function file_build_uri($path) {
571   $uri = file_default_scheme() . '://' . $path;
572   return file_stream_wrapper_uri_normalize($uri);
573 }
574
575 /**
576  * Determines the destination path for a file.
577  *
578  * @param $destination
579  *   A string specifying the desired final URI or filepath.
580  * @param $replace
581  *   Replace behavior when the destination file already exists.
582  *   - FILE_EXISTS_REPLACE - Replace the existing file.
583  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
584  *       unique.
585  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
586  *
587  * @return
588  *   The destination filepath, or FALSE if the file already exists
589  *   and FILE_EXISTS_ERROR is specified.
590  */
591 function file_destination($destination, $replace) {
592   if (file_exists($destination)) {
593     switch ($replace) {
594       case FILE_EXISTS_REPLACE:
595         // Do nothing here, we want to overwrite the existing file.
596         break;
597
598       case FILE_EXISTS_RENAME:
599         $basename = drupal_basename($destination);
600         $directory = drupal_dirname($destination);
601         $destination = file_create_filename($basename, $directory);
602         break;
603
604       case FILE_EXISTS_ERROR:
605         // Error reporting handled by calling function.
606         return FALSE;
607     }
608   }
609   return $destination;
610 }
611
612 /**
613  * Moves a file to a new location without database changes or hook invocation.
614  *
615  * This is a powerful function that in many ways performs like an advanced
616  * version of rename().
617  * - Checks if $source and $destination are valid and readable/writable.
618  * - Checks that $source is not equal to $destination; if they are an error
619  *   is reported.
620  * - If file already exists in $destination either the call will error out,
621  *   replace the file or rename the file based on the $replace parameter.
622  * - Works around a PHP bug where rename() does not properly support streams if
623  *   safe_mode or open_basedir are enabled.
624  *   @see https://bugs.php.net/bug.php?id=60456
625  *
626  * @param $source
627  *   A string specifying the filepath or URI of the source file.
628  * @param $destination
629  *   A URI containing the destination that $source should be moved to. The
630  *   URI may be a bare filepath (without a scheme) and in that case the default
631  *   scheme (file://) will be used. If this value is omitted, Drupal's default
632  *   files scheme will be used, usually "public://".
633  * @param $replace
634  *   Replace behavior when the destination file already exists:
635  *   - FILE_EXISTS_REPLACE - Replace the existing file.
636  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
637  *       unique.
638  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
639  *
640  * @return
641  *   The path to the new file, or FALSE in the event of an error.
642  *
643  * @see file_move()
644  */
645 function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
646   if (!file_unmanaged_prepare($source, $destination, $replace)) {
647     return FALSE;
648   }
649   // Ensure compatibility with Windows.
650   // @see drupal_unlink()
651   if ((substr(PHP_OS, 0, 3) == 'WIN') && (!file_stream_wrapper_valid_scheme(file_uri_scheme($source)))) {
652     chmod($source, 0600);
653   }
654   // Attempt to resolve the URIs. This is necessary in certain configurations
655   // (see above) and can also permit fast moves across local schemes.
656   $real_source = drupal_realpath($source) ?: $source;
657   $real_destination = drupal_realpath($destination) ?: $destination;
658   // Perform the move operation.
659   if (!@rename($real_source, $real_destination)) {
660     // Fall back to slow copy and unlink procedure. This is necessary for
661     // renames across schemes that are not local, or where rename() has not been
662     // implemented. It's not necessary to use drupal_unlink() as the Windows
663     // issue has already been resolved above.
664     if (!@copy($real_source, $real_destination) || !@unlink($real_source)) {
665       \Drupal::logger('file')->error('The specified file %file could not be moved to %destination.', ['%file' => $source, '%destination' => $destination]);
666       return FALSE;
667     }
668   }
669   // Set the permissions on the new file.
670   drupal_chmod($destination);
671   return $destination;
672 }
673
674 /**
675  * Modifies a filename as needed for security purposes.
676  *
677  * Munging a file name prevents unknown file extensions from masking exploit
678  * files. When web servers such as Apache decide how to process a URL request,
679  * they use the file extension. If the extension is not recognized, Apache
680  * skips that extension and uses the previous file extension. For example, if
681  * the file being requested is exploit.php.pps, and Apache does not recognize
682  * the '.pps' extension, it treats the file as PHP and executes it. To make
683  * this file name safe for Apache and prevent it from executing as PHP, the
684  * .php extension is "munged" into .php_, making the safe file name
685  * exploit.php_.pps.
686  *
687  * Specifically, this function adds an underscore to all extensions that are
688  * between 2 and 5 characters in length, internal to the file name, and not
689  * included in $extensions.
690  *
691  * Function behavior is also controlled by the configuration
692  * 'system.file:allow_insecure_uploads'. If it evaluates to TRUE, no alterations
693  * will be made, if it evaluates to FALSE, the filename is 'munged'. *
694  * @param $filename
695  *   File name to modify.
696  * @param $extensions
697  *   A space-separated list of extensions that should not be altered.
698  * @param $alerts
699  *   If TRUE, drupal_set_message() will be called to display a message if the
700  *   file name was changed.
701  *
702  * @return string
703  *   The potentially modified $filename.
704  */
705 function file_munge_filename($filename, $extensions, $alerts = TRUE) {
706   $original = $filename;
707
708   // Allow potentially insecure uploads for very savvy users and admin
709   if (!\Drupal::config('system.file')->get('allow_insecure_uploads')) {
710     // Remove any null bytes. See
711     // http://php.net/manual/security.filesystem.nullbytes.php
712     $filename = str_replace(chr(0), '', $filename);
713
714     $whitelist = array_unique(explode(' ', strtolower(trim($extensions))));
715
716     // Split the filename up by periods. The first part becomes the basename
717     // the last part the final extension.
718     $filename_parts = explode('.', $filename);
719     $new_filename = array_shift($filename_parts); // Remove file basename.
720     $final_extension = array_pop($filename_parts); // Remove final extension.
721
722     // Loop through the middle parts of the name and add an underscore to the
723     // end of each section that could be a file extension but isn't in the list
724     // of allowed extensions.
725     foreach ($filename_parts as $filename_part) {
726       $new_filename .= '.' . $filename_part;
727       if (!in_array(strtolower($filename_part), $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
728         $new_filename .= '_';
729       }
730     }
731     $filename = $new_filename . '.' . $final_extension;
732
733     if ($alerts && $original != $filename) {
734       drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $filename]));
735     }
736   }
737
738   return $filename;
739 }
740
741 /**
742  * Undoes the effect of file_munge_filename().
743  *
744  * @param $filename
745  *   String with the filename to be unmunged.
746  *
747  * @return
748  *   An unmunged filename string.
749  */
750 function file_unmunge_filename($filename) {
751   return str_replace('_.', '.', $filename);
752 }
753
754 /**
755  * Creates a full file path from a directory and filename.
756  *
757  * If a file with the specified name already exists, an alternative will be
758  * used.
759  *
760  * @param $basename
761  *   String filename
762  * @param $directory
763  *   String containing the directory or parent URI.
764  *
765  * @return
766  *   File path consisting of $directory and a unique filename based off
767  *   of $basename.
768  */
769 function file_create_filename($basename, $directory) {
770   // Strip control characters (ASCII value < 32). Though these are allowed in
771   // some filesystems, not many applications handle them well.
772   $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
773   if (substr(PHP_OS, 0, 3) == 'WIN') {
774     // These characters are not allowed in Windows filenames
775     $basename = str_replace([':', '*', '?', '"', '<', '>', '|'], '_', $basename);
776   }
777
778   // A URI or path may already have a trailing slash or look like "public://".
779   if (substr($directory, -1) == '/') {
780     $separator = '';
781   }
782   else {
783     $separator = '/';
784   }
785
786   $destination = $directory . $separator . $basename;
787
788   if (file_exists($destination)) {
789     // Destination file already exists, generate an alternative.
790     $pos = strrpos($basename, '.');
791     if ($pos !== FALSE) {
792       $name = substr($basename, 0, $pos);
793       $ext = substr($basename, $pos);
794     }
795     else {
796       $name = $basename;
797       $ext = '';
798     }
799
800     $counter = 0;
801     do {
802       $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
803     } while (file_exists($destination));
804   }
805
806   return $destination;
807 }
808
809 /**
810  * Deletes a file and its database record.
811  *
812  * Instead of directly deleting a file, it is strongly recommended to delete
813  * file usages instead. That will automatically mark the file as temporary and
814  * remove it during cleanup.
815  *
816  * @param $fid
817  *   The file id.
818  *
819  * @see file_unmanaged_delete()
820  * @see \Drupal\file\FileUsage\FileUsageBase::delete()
821  */
822 function file_delete($fid) {
823   return file_delete_multiple([$fid]);
824 }
825
826 /**
827  * Deletes files.
828  *
829  * Instead of directly deleting a file, it is strongly recommended to delete
830  * file usages instead. That will automatically mark the file as temporary and
831  * remove it during cleanup.
832  *
833  * @param $fid
834  *   The file id.
835  *
836  * @see file_unmanaged_delete()
837  * @see \Drupal\file\FileUsage\FileUsageBase::delete()
838  */
839 function file_delete_multiple(array $fids) {
840   entity_delete_multiple('file', $fids);
841 }
842
843 /**
844  * Deletes a file without database changes or hook invocations.
845  *
846  * This function should be used when the file to be deleted does not have an
847  * entry recorded in the files table.
848  *
849  * @param $path
850  *   A string containing a file path or (streamwrapper) URI.
851  *
852  * @return
853  *   TRUE for success or path does not exist, or FALSE in the event of an
854  *   error.
855  *
856  * @see file_delete()
857  * @see file_unmanaged_delete_recursive()
858  */
859 function file_unmanaged_delete($path) {
860   if (is_file($path)) {
861     return drupal_unlink($path);
862   }
863   $logger = \Drupal::logger('file');
864   if (is_dir($path)) {
865     $logger->error('%path is a directory and cannot be removed using file_unmanaged_delete().', ['%path' => $path]);
866     return FALSE;
867   }
868   // Return TRUE for non-existent file, but log that nothing was actually
869   // deleted, as the current state is the intended result.
870   if (!file_exists($path)) {
871     $logger->notice('The file %path was not deleted because it does not exist.', ['%path' => $path]);
872     return TRUE;
873   }
874   // We cannot handle anything other than files and directories. Log an error
875   // for everything else (sockets, symbolic links, etc).
876   $logger->error('The file %path is not of a recognized type so it was not deleted.', ['%path' => $path]);
877   return FALSE;
878 }
879
880 /**
881  * Deletes all files and directories in the specified filepath recursively.
882  *
883  * If the specified path is a directory then the function will call itself
884  * recursively to process the contents. Once the contents have been removed the
885  * directory will also be removed.
886  *
887  * If the specified path is a file then it will be passed to
888  * file_unmanaged_delete().
889  *
890  * Note that this only deletes visible files with write permission.
891  *
892  * @param $path
893  *   A string containing either an URI or a file or directory path.
894  * @param $callback
895  *   (optional) Callback function to run on each file prior to deleting it and
896  *   on each directory prior to traversing it. For example, can be used to
897  *   modify permissions.
898  *
899  * @return
900  *   TRUE for success or if path does not exist, FALSE in the event of an
901  *   error.
902  *
903  * @see file_unmanaged_delete()
904  */
905 function file_unmanaged_delete_recursive($path, $callback = NULL) {
906   if (isset($callback)) {
907     call_user_func($callback, $path);
908   }
909   if (is_dir($path)) {
910     $dir = dir($path);
911     while (($entry = $dir->read()) !== FALSE) {
912       if ($entry == '.' || $entry == '..') {
913         continue;
914       }
915       $entry_path = $path . '/' . $entry;
916       file_unmanaged_delete_recursive($entry_path, $callback);
917     }
918     $dir->close();
919
920     return drupal_rmdir($path);
921   }
922   return file_unmanaged_delete($path);
923 }
924
925
926
927 /**
928  * Moves an uploaded file to a new location.
929  *
930  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
931  *   Use \Drupal\Core\File\FileSystem::moveUploadedFile().
932  *
933  * @see https://www.drupal.org/node/2418133
934  */
935 function drupal_move_uploaded_file($filename, $uri) {
936   return \Drupal::service('file_system')->moveUploadedFile($filename, $uri);
937 }
938
939 /**
940  * Saves a file to the specified destination without invoking file API.
941  *
942  * This function is identical to file_save_data() except the file will not be
943  * saved to the {file_managed} table and none of the file_* hooks will be
944  * called.
945  *
946  * @param $data
947  *   A string containing the contents of the file.
948  * @param $destination
949  *   A string containing the destination location. This must be a stream wrapper
950  *   URI. If no value is provided, a randomized name will be generated and the
951  *   file will be saved using Drupal's default files scheme, usually
952  *   "public://".
953  * @param $replace
954  *   Replace behavior when the destination file already exists:
955  *   - FILE_EXISTS_REPLACE - Replace the existing file.
956  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
957  *                          unique.
958  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
959  *
960  * @return
961  *   A string with the path of the resulting file, or FALSE on error.
962  *
963  * @see file_save_data()
964  */
965 function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
966   // Write the data to a temporary file.
967   $temp_name = drupal_tempnam('temporary://', 'file');
968   if (file_put_contents($temp_name, $data) === FALSE) {
969     drupal_set_message(t('The file could not be created.'), 'error');
970     return FALSE;
971   }
972
973   // Move the file to its final destination.
974   return file_unmanaged_move($temp_name, $destination, $replace);
975 }
976
977 /**
978  * Finds all files that match a given mask in a given directory.
979  *
980  * Directories and files beginning with a dot are excluded; this prevents
981  * hidden files and directories (such as SVN working directories) from being
982  * scanned. Use the umask option to skip configuration directories to
983  * eliminate the possibility of accidentally exposing configuration
984  * information. Also, you can use the base directory, recurse, and min_depth
985  * options to improve performance by limiting how much of the filesystem has
986  * to be traversed.
987  *
988  * @param $dir
989  *   The base directory or URI to scan, without trailing slash.
990  * @param $mask
991  *   The preg_match() regular expression for files to be included.
992  * @param $options
993  *   An associative array of additional options, with the following elements:
994  *   - 'nomask': The preg_match() regular expression for files to be excluded.
995  *     Defaults to the 'file_scan_ignore_directories' setting.
996  *   - 'callback': The callback function to call for each match. There is no
997  *     default callback.
998  *   - 'recurse': When TRUE, the directory scan will recurse the entire tree
999  *     starting at the provided directory. Defaults to TRUE.
1000  *   - 'key': The key to be used for the returned associative array of files.
1001  *     Possible values are 'uri', for the file's URI; 'filename', for the
1002  *     basename of the file; and 'name' for the name of the file without the
1003  *     extension. Defaults to 'uri'.
1004  *   - 'min_depth': Minimum depth of directories to return files from. Defaults
1005  *     to 0.
1006  * @param $depth
1007  *   The current depth of recursion. This parameter is only used internally and
1008  *   should not be passed in.
1009  *
1010  * @return
1011  *   An associative array (keyed on the chosen key) of objects with 'uri',
1012  *   'filename', and 'name' properties corresponding to the matched files.
1013  */
1014 function file_scan_directory($dir, $mask, $options = [], $depth = 0) {
1015   // Merge in defaults.
1016   $options += [
1017     'callback' => 0,
1018     'recurse' => TRUE,
1019     'key' => 'uri',
1020     'min_depth' => 0,
1021   ];
1022   // Normalize $dir only once.
1023   if ($depth == 0) {
1024     $dir = file_stream_wrapper_uri_normalize($dir);
1025     $dir_has_slash = (substr($dir, -1) === '/');
1026   }
1027
1028   // Allow directories specified in settings.php to be ignored. You can use this
1029   // to not check for files in common special-purpose directories. For example,
1030   // node_modules and bower_components. Ignoring irrelevant directories is a
1031   // performance boost.
1032   if (!isset($options['nomask'])) {
1033     $ignore_directories = Settings::get('file_scan_ignore_directories', []);
1034     array_walk($ignore_directories, function(&$value) {
1035       $value = preg_quote($value, '/');
1036     });
1037     $default_nomask = '/^' . implode('|', $ignore_directories) . '$/';
1038   }
1039
1040   $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri';
1041   $files = [];
1042   // Avoid warnings when opendir does not have the permissions to open a
1043   // directory.
1044   if (is_dir($dir)) {
1045     if ($handle = @opendir($dir)) {
1046       while (FALSE !== ($filename = readdir($handle))) {
1047         // Skip this file if it matches the nomask or starts with a dot.
1048         if ($filename[0] != '.'
1049           && !(isset($options['nomask']) && preg_match($options['nomask'], $filename))
1050           && !(!empty($default_nomask) && preg_match($default_nomask, $filename))
1051           ) {
1052           if ($depth == 0 && $dir_has_slash) {
1053             $uri = "$dir$filename";
1054           }
1055           else {
1056             $uri = "$dir/$filename";
1057           }
1058           if ($options['recurse'] && is_dir($uri)) {
1059             // Give priority to files in this folder by merging them in after
1060             // any subdirectory files.
1061             $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
1062           }
1063           elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
1064             // Always use this match over anything already set in $files with
1065             // the same $options['key'].
1066             $file = new stdClass();
1067             $file->uri = $uri;
1068             $file->filename = $filename;
1069             $file->name = pathinfo($filename, PATHINFO_FILENAME);
1070             $key = $options['key'];
1071             $files[$file->$key] = $file;
1072             if ($options['callback']) {
1073               $options['callback']($uri);
1074             }
1075           }
1076         }
1077       }
1078
1079       closedir($handle);
1080     }
1081     else {
1082       \Drupal::logger('file')->error('@dir can not be opened', ['@dir' => $dir]);
1083     }
1084   }
1085
1086   return $files;
1087 }
1088
1089 /**
1090  * Determines the maximum file upload size by querying the PHP settings.
1091  *
1092  * @return
1093  *   A file size limit in bytes based on the PHP upload_max_filesize and
1094  *   post_max_size
1095  */
1096 function file_upload_max_size() {
1097   static $max_size = -1;
1098
1099   if ($max_size < 0) {
1100     // Start with post_max_size.
1101     $max_size = Bytes::toInt(ini_get('post_max_size'));
1102
1103     // If upload_max_size is less, then reduce. Except if upload_max_size is
1104     // zero, which indicates no limit.
1105     $upload_max = Bytes::toInt(ini_get('upload_max_filesize'));
1106     if ($upload_max > 0 && $upload_max < $max_size) {
1107       $max_size = $upload_max;
1108     }
1109   }
1110   return $max_size;
1111 }
1112
1113 /**
1114  * Sets the permissions on a file or directory.
1115  *
1116  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1117  *   Use \Drupal\Core\File\FileSystem::chmod().
1118  *
1119  * @see https://www.drupal.org/node/2418133
1120  */
1121 function drupal_chmod($uri, $mode = NULL) {
1122   return \Drupal::service('file_system')->chmod($uri, $mode);
1123 }
1124
1125 /**
1126  * Deletes a file.
1127  *
1128  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1129  *   Use \Drupal\Core\File\FileSystem::unlink().
1130  *
1131  * @see https://www.drupal.org/node/2418133
1132  */
1133 function drupal_unlink($uri, $context = NULL) {
1134   return \Drupal::service('file_system')->unlink($uri, $context);
1135 }
1136
1137 /**
1138  * Resolves the absolute filepath of a local URI or filepath.
1139  *
1140  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1141  *   Use \Drupal\Core\File\FileSystem::realpath().
1142  *
1143  * @see https://www.drupal.org/node/2418133
1144  */
1145 function drupal_realpath($uri) {
1146   return \Drupal::service('file_system')->realpath($uri);
1147 }
1148
1149 /**
1150  * Gets the name of the directory from a given path.
1151  *
1152  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1153  *   Use \Drupal\Core\File\FileSystem::dirname().
1154  *
1155  * @see https://www.drupal.org/node/2418133
1156  */
1157 function drupal_dirname($uri) {
1158   return \Drupal::service('file_system')->dirname($uri);
1159 }
1160
1161 /**
1162  * Gets the filename from a given path.
1163  *
1164  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1165  *   Use \Drupal\Core\File\FileSystem::basename().
1166  *
1167  * @see https://www.drupal.org/node/2418133
1168  */
1169 function drupal_basename($uri, $suffix = NULL) {
1170   return \Drupal::service('file_system')->basename($uri, $suffix);
1171 }
1172
1173 /**
1174  * Creates a directory, optionally creating missing components in the path to
1175  * the directory.
1176  *
1177  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1178  *   Use \Drupal\Core\File\FileSystem::mkdir().
1179  *
1180  * @see https://www.drupal.org/node/2418133
1181  */
1182 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
1183   return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context);
1184 }
1185
1186 /**
1187  * Removes a directory.
1188  *
1189  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1190  *   Use \Drupal\Core\File\FileSystem::rmdir().
1191  *
1192  * @see https://www.drupal.org/node/2418133
1193  */
1194 function drupal_rmdir($uri, $context = NULL) {
1195   return \Drupal::service('file_system')->rmdir($uri, $context);
1196 }
1197
1198 /**
1199  * Creates a file with a unique filename in the specified directory.
1200  *
1201  * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
1202  *   Use \Drupal\Core\File\FileSystem::tempnam().
1203  *
1204  * @see https://www.drupal.org/node/2418133
1205  */
1206 function drupal_tempnam($directory, $prefix) {
1207   return \Drupal::service('file_system')->tempnam($directory, $prefix);
1208 }
1209
1210 /**
1211  * Gets and sets the path of the configured temporary directory.
1212  *
1213  * @return mixed|null
1214  *   A string containing the path to the temporary directory.
1215  */
1216 function file_directory_temp() {
1217   $temporary_directory = \Drupal::config('system.file')->get('path.temporary');
1218   if (empty($temporary_directory)) {
1219     // Needs set up.
1220     $config = \Drupal::configFactory()->getEditable('system.file');
1221     $temporary_directory = ComponentFileSystem::getOsTemporaryDirectory();
1222
1223     if (empty($temporary_directory)) {
1224       // If no directory has been found default to 'files/tmp'.
1225       $temporary_directory = PublicStream::basePath() . '/tmp';
1226
1227       // Windows accepts paths with either slash (/) or backslash (\), but will
1228       // not accept a path which contains both a slash and a backslash. Since
1229       // the 'file_public_path' variable may have either format, we sanitize
1230       // everything to use slash which is supported on all platforms.
1231       $temporary_directory = str_replace('\\', '/', $temporary_directory);
1232     }
1233     // Save the path of the discovered directory. Do not check config schema on
1234     // save.
1235     $config->set('path.temporary', (string) $temporary_directory)->save(TRUE);
1236   }
1237
1238   return $temporary_directory;
1239 }
1240
1241 /**
1242  * Discovers a writable system-appropriate temporary directory.
1243  *
1244  * @return mixed
1245  *   A string containing the path to the temporary directory.
1246  *
1247  * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
1248  *   Use \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory().
1249  *
1250  * @see https://www.drupal.org/node/2418133
1251  */
1252 function file_directory_os_temp() {
1253   return ComponentFileSystem::getOsTemporaryDirectory();
1254 }
1255
1256 /**
1257  * @} End of "defgroup file".
1258  */