diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 7d1b4da8a98679f55c7da3682881321479fd3c65..d532351b81ff98e347185c077dbcdbc29210e028 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1972,7 +1972,7 @@ function install_check_translations($langcode, $server_pattern): array { // Get values so the requirements errors can be specific. if (drupal_verify_install_file($translations_directory, FILE_EXIST, 'dir')) { $readable = is_readable($translations_directory); - $writable = is_writable($translations_directory); + $writable = $file_system->isWritable($translations_directory); $translations_directory_exists = TRUE; } @@ -2162,7 +2162,7 @@ function install_check_requirements($install_state) { // Otherwise, if $file does not exist yet, we can try to copy // $default_file to create it. elseif (!$exists) { - $copied = drupal_verify_install_file($site_path, FILE_EXIST | FILE_WRITABLE, 'dir') && @copy($default_file, $file); + $copied = drupal_verify_install_file($site_path, FILE_EXIST | FILE_WRITABLE | FILE_EXECUTABLE, 'dir') && @copy($default_file, $file); if ($copied) { // If the new $file file has the same owner as $default_file this means // $default_file is owned by the webserver user. This is an inherent diff --git a/core/includes/install.inc b/core/includes/install.inc index d1e7e86566619f32f6389b4e45f54c272abe001a..5050b82014ab9404b541237122fef6400fa1cd1c 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -309,6 +309,8 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $auto_f } } + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); // Verify file permissions. if (isset($mask)) { $masks = [FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE]; @@ -333,13 +335,13 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $auto_f break; case FILE_WRITABLE: - if (!is_writable($file)) { + if (!$file_system->isWritable($file)) { $return = FALSE; } break; case FILE_EXECUTABLE: - if (!is_executable($file)) { + if (!$file_system->isExecutable($file)) { $return = FALSE; } break; @@ -351,13 +353,13 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $auto_f break; case FILE_NOT_WRITABLE: - if (is_writable($file)) { + if ($file_system->isWritable($file)) { $return = FALSE; } break; case FILE_NOT_EXECUTABLE: - if (is_executable($file)) { + if ($file_system->isExecutable($file)) { $return = FALSE; } break; @@ -440,7 +442,8 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { if (!file_exists($file)) { return FALSE; } - + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); $mod = fileperms($file) & 0777; $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE]; @@ -458,13 +461,13 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { break; case FILE_WRITABLE: - if (!is_writable($file)) { + if (!$file_system->isWritable($file)) { $mod |= 0222; } break; case FILE_EXECUTABLE: - if (!is_executable($file)) { + if (!$file_system->isExecutable($file)) { $mod |= 0111; } break; @@ -476,13 +479,13 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { break; case FILE_NOT_WRITABLE: - if (is_writable($file)) { + if ($file_system->isWritable($file)) { $mod &= ~0222; } break; case FILE_NOT_EXECUTABLE: - if (is_executable($file)) { + if ($file_system->isExecutable($file)) { $mod &= ~0111; } break; diff --git a/core/lib/Drupal/Component/FileSystem/FileSystem.php b/core/lib/Drupal/Component/FileSystem/FileSystem.php index b4e8389d755acd4d14a387cc5ad0fcb2ee896712..85305132d877d2476808d128c57f18a861113969 100644 --- a/core/lib/Drupal/Component/FileSystem/FileSystem.php +++ b/core/lib/Drupal/Component/FileSystem/FileSystem.php @@ -36,7 +36,7 @@ public static function getOsTemporaryDirectory() { $directories[] = sys_get_temp_dir(); foreach ($directories as $directory) { - if (is_dir($directory) && is_writable($directory)) { + if (is_dir($directory) && is_writable($directory) && is_executable($directory)) { // Both sys_get_temp_dir() and ini_get('upload_tmp_dir') can return // paths with a trailing directory separator. return rtrim($directory, DIRECTORY_SEPARATOR); diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php index 8920c3daf1e5e881eee24d5f4c2ddd7ac75d7138..7d8f05a68e9efbcd03f77ed543e3bfbd270155f2 100644 --- a/core/lib/Drupal/Core/File/FileSystem.php +++ b/core/lib/Drupal/Core/File/FileSystem.php @@ -531,7 +531,7 @@ public function prepareDirectory(&$directory, $options = self::MODIFY_PERMISSION } } - $writable = is_writable($directory); + $writable = $this->isWritable($directory); if (!$writable && ($options & static::MODIFY_PERMISSIONS)) { return $this->chmod($directory); } @@ -734,4 +734,50 @@ protected function doScanDirectory($dir, $mask, array $options = [], $depth = 0) return array_merge(array_merge(...$files_in_sub_dirs), $files_in_this_directory); } + /** + * Determines if a directory is writable by the web server. + * + * PHP's is_writable() does not fully support stream wrappers, so this + * function fills that gap. + * In order to be able to write files within the directory, the directory + * itself must be writable, and it must also have the executable bit set. This + * helper function checks both at the same time. + * + * @param string $uri + * A URI or pathname pointing to the directory that will be checked. + * + * @return bool + * TRUE if the directory is writable and executable; FALSE otherwise. + */ + public function isWritable($uri) { + // By converting the URI to a normal path using drupal_realpath(), we can + // correctly handle both stream wrappers and normal paths. + $realpath = $this->realpath($uri); + return is_writable($realpath ?: $uri) && $this->isExecutable($uri); + } + + /** + * Determines if a file or directory is executable. + * + * PHP's is_executable() does not fully support stream wrappers, so this + * function fills that gap. + * + * @param string $uri + * A URI or pathname pointing to the file or directory that will be checked. + * + * @return bool + * TRUE if the file or directory is executable; FALSE otherwise. + * + * @see is_executable() + * @ingroup php_wrappers + */ + public function isExecutable($uri) { + // By converting the URI to a normal path using drupal_realpath(), we can + // correctly handle both stream wrappers and normal paths. + $realpath = $this->realpath($uri); + $filename = $realpath ?: $uri; + // Determine whether the URI is an executable file or a directory. + return is_executable($filename) || is_dir($filename); + } + } diff --git a/core/lib/Drupal/Core/File/FileSystemInterface.php b/core/lib/Drupal/Core/File/FileSystemInterface.php index f3beabfe22093d74e1329d02ddb49fdfb3148d4a..8f3967d0a2b0cf4b7dfd7a8cd4312fa84f697302 100644 --- a/core/lib/Drupal/Core/File/FileSystemInterface.php +++ b/core/lib/Drupal/Core/File/FileSystemInterface.php @@ -518,4 +518,38 @@ public function getTempDirectory(); */ public function scanDirectory($dir, $mask, array $options = []); + /** + * Determines if a directory is writable by the web server. + * + * PHP's is_writable() does not fully support stream wrappers, so this + * function fills that gap. + * In order to be able to write files within the directory, the directory + * itself must be writable, and it must also have the executable bit set. This + * helper function checks both at the same time. + * + * @param string $uri + * A URI or pathname pointing to the directory that will be checked. + * + * @return bool + * TRUE if the directory is writable and executable; FALSE otherwise. + */ + public function isWritable($uri); + + /** + * Determines if a file or directory is executable. + * + * PHP's is_executable() does not fully support stream wrappers, so this + * function fills that gap. + * + * @param string $uri + * A URI or pathname pointing to the file or directory that will be checked. + * + * @return bool + * TRUE if the file or directory is executable; FALSE otherwise. + * + * @see is_executable() + * @ingroup php_wrappers + */ + public function isExecutable($uri); + } diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index 908fdf7616a0a87cb0ebf5169162565f5762885e..d0213ec7358b45e8f188dcc36216f7fc67ece605 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -339,7 +339,7 @@ public function prepareInstallDirectory(&$filetransfer, $directory) { // Make the parent dir writable if need be and create the dir. if (!is_dir($directory)) { $parent_dir = dirname($directory); - if (!is_writable($parent_dir)) { + if (!\Drupal::service('file_system')->isWritable($parent_dir)) { @chmod($parent_dir, 0755); // It is expected that this will fail if the directory is owned by the // FTP user. If the FTP user == web server, it will succeed. @@ -385,7 +385,7 @@ public function prepareInstallDirectory(&$filetransfer, $directory) { * If the chmod should be applied recursively. */ public function makeWorldReadable(&$filetransfer, $path, $recursive = TRUE) { - if (!is_executable($path)) { + if (!\Drupal::service('file_system')->isExecutable($path)) { // Set it to read + execute. $new_perms = fileperms($path) & 0777 | 0005; $filetransfer->chmod($path, $new_perms, $recursive); diff --git a/core/modules/config/src/Form/ConfigImportForm.php b/core/modules/config/src/Form/ConfigImportForm.php index 74c73b8d999ac5d99e442d9bac367ed8baac6e86..bcd47d4e0ed82811efa1b23c6241dee0d1d51b98 100644 --- a/core/modules/config/src/Form/ConfigImportForm.php +++ b/core/modules/config/src/Form/ConfigImportForm.php @@ -77,7 +77,7 @@ public function getFormId() { */ public function buildForm(array $form, FormStateInterface $form_state) { $directory = $this->settings->get('config_sync_directory'); - $directory_is_writable = is_writable($directory); + $directory_is_writable = \Drupal::service('file_system')->isWritable($directory); if (!$directory_is_writable) { $this->messenger()->addError($this->t('The directory %directory is not writable.', ['%directory' => $directory])); } diff --git a/core/modules/media/media.install b/core/modules/media/media.install index f0acbf482da43f9231e14201e5c89803db7c507e..f3514d1128c50975e49f5e0384785f8981c5a2d9 100644 --- a/core/modules/media/media.install +++ b/core/modules/media/media.install @@ -23,7 +23,7 @@ function media_requirements($phase): array { if ($phase == 'install') { $destination = 'public://media-icons/generic'; \Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - $is_writable = is_writable($destination); + $is_writable = \Drupal::service('file_system')->isWritable($destination); $is_directory = is_dir($destination); if (!$is_writable || !$is_directory) { if (!$is_directory) { diff --git a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php index 61d0ace73d0a796c2ecc56612fa4361c4cf9184c..030fa184b62c0c27364fef00ecb51f1afab24125 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php +++ b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php @@ -145,7 +145,7 @@ public function transform($value, MigrateExecutableInterface $migrate_executable // If the directory exists and is writable, avoid // \Drupal\Core\File\FileSystemInterface::prepareDirectory() call and write // the file to destination. - if (!is_dir($dir) || !is_writable($dir)) { + if (!is_dir($dir) || !$this->fileSystem->isWritable($dir)) { if (!$this->fileSystem->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { throw new MigrateException("Could not create or write to directory '$dir'"); } diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php index 3a1cefb471bf781de365d17e474ee2c68b5c5e06..1567bc5a0a1232aec76d53613ab51b34865def63 100644 --- a/core/modules/system/src/Form/PerformanceForm.php +++ b/core/modules/system/src/Form/PerformanceForm.php @@ -129,7 +129,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $directory = 'assets://'; - $is_writable = is_dir($directory) && is_writable($directory); + $is_writable = is_dir($directory) && \Drupal::service('file_system')->isWritable($directory); $disabled = !$is_writable; $disabled_message = ''; if (!$is_writable) { diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 8da736101f1cb942dde3d9939c7e79f64ed5d14c..3f0e5fe2315924dcd7524edd4045225b442120ae 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -859,7 +859,7 @@ function system_requirements($phase): array { if ($phase == 'install') { \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); } - $is_writable = is_writable($directory); + $is_writable = \Drupal::service('file_system')->isWritable($directory); $is_directory = is_dir($directory); if (!$is_writable || !$is_directory) { $description = ''; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index c7ed1808206f4ca5e21284d6e73d54b69343cdc0..cfba75fbd1065e7d0c343071ba2c4008bc24da85 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -356,7 +356,7 @@ function system_check_directory($form_element, FormStateInterface $form_state) { $logger->error('The directory %directory does not exist and could not be created.', ['%directory' => $directory]); } - if (is_dir($directory) && !is_writable($directory) && !$file_system->chmod($directory)) { + if (is_dir($directory) && !$file_system->isWritable($directory) && !$file_system->chmod($directory)) { // If the directory is not writable and cannot be made so. $form_state->setErrorByName($form_element['#parents'][0], t('The directory %directory exists but is not writable and could not be made writable.', ['%directory' => $directory])); $logger->error('The directory %directory exists but is not writable and could not be made writable.', ['%directory' => $directory]);