diff --git a/core/includes/file.inc b/core/includes/file.inc index 0dcd141..2970bbe 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -299,7 +299,7 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) return FALSE; } // The directory exists, so check to see if it is writable. - $writable = is_writable($directory); + $writable = file_directory_is_writable($directory); if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) { return drupal_chmod($directory); } @@ -308,6 +308,46 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) } /** + * Determines if a directory is writable by the web server. + * + * 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 $uri + * A URI or pathname pointing to the directory that will be checked. + * + * @return + * TRUE if the directory is writable and executable; FALSE otherwise. + */ +function file_directory_is_writable($uri) { + // By converting the URI to a normal path using drupal_realpath(), we can + // correctly handle both stream wrappers and normal paths. + return is_writable(drupal_realpath($uri)) && drupal_is_executable($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 $uri + * A URI or pathname pointing to the file or directory that will be checked. + * + * @return + * TRUE if the file or directory is executable; FALSE otherwise. + * + * @see is_executable() + * @ingroup php_wrappers + */ +function drupal_is_executable($uri) { + // By converting the URI to a normal path using drupal_realpath(), we can + // correctly handle both stream wrappers and normal paths. + return is_executable(drupal_realpath($uri)); +} + +/** * Creates a .htaccess file in each Drupal files directory if it is missing. */ function file_ensure_htaccess() { @@ -363,7 +403,7 @@ function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALS $htaccess_lines = FileStorage::htaccessLines($private); // Write the .htaccess file. - if (file_exists($directory) && is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) { + if (file_exists($directory) && file_directory_is_writable($directory) && file_put_contents($htaccess_path, $htaccess_lines)) { return drupal_chmod($htaccess_path, 0444); } else { diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index ceb9982..608ed04 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1818,7 +1818,7 @@ function install_check_translations($langcode, $server_pattern) { // 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_directory_is_writable($translations_directory); $translations_directory_exists = TRUE; } @@ -2000,7 +2000,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($conf_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 e243c18..2c24e24 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -509,7 +509,7 @@ function drupal_install_config_directories() { ':handbook_url' => 'https://www.drupal.org/server-permissions', ))); } - elseif (is_writable($config_directories[CONFIG_SYNC_DIRECTORY])) { + elseif (file_directory_is_writable($config_directories[CONFIG_SYNC_DIRECTORY])) { // Put a README.txt into the sync config directory. This is required so that // they can later be added to git. Since this directory is auto-created, we // have to write out the README rather than just adding it to the drupal core @@ -683,12 +683,12 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { } break; case FILE_WRITABLE: - if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) { + if (!file_directory_is_writable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_EXECUTABLE: - if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) { + if (!drupal_is_executable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; @@ -698,12 +698,12 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { } break; case FILE_NOT_WRITABLE: - if (is_writable($file) && !drupal_install_fix_file($file, $mask)) { + if (file_directory_is_writable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_NOT_EXECUTABLE: - if (is_executable($file) && !drupal_install_fix_file($file, $mask)) { + if (drupal_is_executable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; @@ -798,12 +798,12 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { } break; case FILE_WRITABLE: - if (!is_writable($file)) { + if (!file_directory_is_writable($file)) { $mod |= 0222; } break; case FILE_EXECUTABLE: - if (!is_executable($file)) { + if (!drupal_is_executable($file)) { $mod |= 0111; } break; @@ -813,12 +813,12 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { } break; case FILE_NOT_WRITABLE: - if (is_writable($file)) { + if (file_directory_is_writable($file)) { $mod &= ~0222; } break; case FILE_NOT_EXECUTABLE: - if (is_executable($file)) { + if (drupal_is_executable($file)) { $mod &= ~0111; } break; diff --git a/core/lib/Drupal/Component/FileSystem/FileSystem.php b/core/lib/Drupal/Component/FileSystem/FileSystem.php index 26989cd..86484fb 100644 --- a/core/lib/Drupal/Component/FileSystem/FileSystem.php +++ b/core/lib/Drupal/Component/FileSystem/FileSystem.php @@ -34,7 +34,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) && file_directory_is_writable($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/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index 0381728..5ea97cb 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -305,7 +305,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 (!file_directory_is_writable($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. @@ -347,7 +347,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_is_executable($path)) { // Set it to read + execute. $new_perms = substr(sprintf('%o', fileperms($path)), -4, -1) . "5"; $filetransfer->chmod($path, intval($new_perms, 8), $recursive); diff --git a/core/modules/config/config.install b/core/modules/config/config.install index c971ae6..3d9adea 100644 --- a/core/modules/config/config.install +++ b/core/modules/config/config.install @@ -21,7 +21,7 @@ function config_requirements($phase) { // Ensure the configuration sync directory is writable. This is only a warning // because only configuration import from a tarball requires the folder to be // web writable. - if ($phase !== 'install' && !is_writable($directory)) { + if ($phase !== 'install' && !file_directory_is_writable($directory)) { $requirements['config directory ' . CONFIG_SYNC_DIRECTORY] = [ 'title' => t('Configuration directory: %type', ['%type' => CONFIG_SYNC_DIRECTORY]), 'description' => t('The directory %directory is not writable.', ['%directory' => $directory]), diff --git a/core/modules/config/src/Form/ConfigImportForm.php b/core/modules/config/src/Form/ConfigImportForm.php index 1d651d8..3cbf1c3 100644 --- a/core/modules/config/src/Form/ConfigImportForm.php +++ b/core/modules/config/src/Form/ConfigImportForm.php @@ -51,7 +51,7 @@ public function getFormId() { */ public function buildForm(array $form, FormStateInterface $form_state) { $directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY); - $directory_is_writable = is_writable($directory); + $directory_is_writable = file_directory_is_writable($directory); if (!$directory_is_writable) { drupal_set_message($this->t('The directory %directory is not writable.', ['%directory' => $directory]), 'error'); } diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php index d9e0917..06ac189 100644 --- a/core/modules/system/src/Form/PerformanceForm.php +++ b/core/modules/system/src/Form/PerformanceForm.php @@ -132,7 +132,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ); $directory = 'public://'; - $is_writable = is_dir($directory) && is_writable($directory); + $is_writable = is_dir($directory) && file_directory_is_writable($directory); $disabled = !$is_writable; $disabled_message = ''; if (!$is_writable) { diff --git a/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php b/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php index f9331ba..5735e3c 100644 --- a/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php +++ b/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php @@ -57,8 +57,8 @@ public function testSitesDirectoryHardeningConfig() { $this->assertEqual(REQUIREMENT_WARNING, $requirements['configuration_files']['severity'], 'Warning severity is properly set.'); $this->assertEqual($this->t('Protection disabled'), (string) $requirements['configuration_files']['description']['#context']['configuration_error_list']['#items'][0], 'Description is properly set.'); - $this->assertTrue(is_writable($site_path), 'Site directory remains writable when automatically fixing permissions is disabled.'); - $this->assertTrue(is_writable($settings_file), 'settings.php remains writable when automatically fixing permissions is disabled.'); + $this->assertTrue(file_directory_is_writable($site_path), 'Site directory remains writable when automatically fixing permissions is disabled.'); + $this->assertTrue(file_directory_is_writable($settings_file), 'settings.php remains writable when automatically fixing permissions is disabled.'); // Re-enable permissions enforcement. $settings = Settings::getAll(); @@ -68,8 +68,8 @@ public function testSitesDirectoryHardeningConfig() { // Manually trigger the requirements check. $this->checkSystemRequirements(); - $this->assertFalse(is_writable($site_path), 'Site directory is protected when automatically fixing permissions is enabled.'); - $this->assertFalse(is_writable($settings_file), 'settings.php is protected when automatically fixing permissions is enabled.'); + $this->assertFalse(file_directory_is_writable($site_path), 'Site directory is protected when automatically fixing permissions is enabled.'); + $this->assertFalse(file_directory_is_writable($settings_file), 'settings.php is protected when automatically fixing permissions is enabled.'); } /** diff --git a/core/modules/system/system.install b/core/modules/system/system.install index cbdafbc..0d66fc8 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -593,7 +593,7 @@ function system_requirements($phase) { if ($phase == 'install') { file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); } - $is_writable = is_writable($directory); + $is_writable = file_directory_is_writable($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 01c8c5e..8e2f893 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -871,7 +871,7 @@ function system_check_directory($form_element, FormStateInterface $form_state) { $logger->error('The directory %directory does not exist and could not be created.', array('%directory' => $directory)); } - if (is_dir($directory) && !is_writable($directory) && !drupal_chmod($directory)) { + if (is_dir($directory) && !file_directory_is_writable($directory) && !drupal_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.', array('%directory' => $directory))); $logger->error('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory)); diff --git a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php index ac22072..d4943e9 100644 --- a/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php +++ b/core/tests/Drupal/Tests/Listeners/HtmlOutputPrinter.php @@ -24,7 +24,7 @@ public function __construct($out, $verbose, $colors, $debug, $numberOfColumns) { $html_output_directory = rtrim($html_output_directory, '/'); // Check if directory exists. - if (!is_dir($html_output_directory) || !is_writable($html_output_directory)) { + if (!is_dir($html_output_directory) || !file_directory_is_writable($html_output_directory)) { $this->writeWithColor('bg-red, fg-black', "HTML output directory $html_output_directory is not a writable directory."); } else {