From 9feca5cfd2e8ebef51537ec767585a38f235341e Mon Sep 17 00:00:00 2001 From: Bob Vincent Date: Fri, 16 Sep 2011 18:40:10 -0400 Subject: [PATCH] Issue #1165694: file_scan_directory() should include common version-control and temporary files in its default no-mask pattern. --- includes/file.inc | 34 ++++++++++++----- modules/simpletest/tests/file.test | 74 +++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/includes/file.inc b/includes/file.inc index 6e2e5cb2828c9f4622b9253feed8a296b463050d..06f96a1b686fe325461f4a5ed910df82ae829b18 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1981,18 +1981,19 @@ function file_download() { /** * Finds all files that match a given mask in a given directory. * - * Directories and files beginning with a period are excluded; this - * prevents hidden files and directories (such as SVN working directories) - * from being scanned. + * The current '.' and parent '..' directories are always excluded, to + * avoid infinite recursion errors. * * @param $dir * The base directory or URI to scan, without trailing slash. * @param $mask - * The preg_match() regular expression of the files to find. + * The preg_match() regular expression for files to be included. * @param $options * An associative array of additional options, with the following elements: - * - 'nomask': The preg_match() regular expression of the files to ignore. - * Defaults to '/(\.\.?|CVS)$/'. + * - 'nomask': The preg_match() regular expression for files to excluded. + * The default regex excludes "hidden" files whose name starts with a dot, + * version-control files and directories, temporary files, and MACOS + * resource forks. * - 'callback': The callback function to call for each match. There is no * default callback. * - 'recurse': When TRUE, the directory scan will recurse the entire tree @@ -2004,17 +2005,26 @@ function file_download() { * - 'min_depth': Minimum depth of directories to return files from. Defaults * to 0. * @param $depth - * Current depth of recursion. This parameter is only used internally and + * The current depth of recursion. This parameter is only used internally and * should not be passed in. * * @return * An associative array (keyed on the chosen key) of objects with 'uri', - * 'filename', and 'name' members corresponding to the matching files. + * 'filename', and 'name' properties corresponding to the matched files. */ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { // Merge in defaults. $options += array( - 'nomask' => '/(\.\.?|CVS)$/', + // The following is the default regex for files to be excluded. + 'nomask' => '/ + .(?:~|\$|\.(?:old|bak|BAK|orig|rej))$ # Temporary file suffixes. + |^(?:\.|cvslog\.|\#|,|_\$). # Hidden or temporary prefixes. + |^(?: + CVS(?:\.adm)? # CVS directories. + |RCS(?:LOG)?|SCCS # RCS and SCCS directories. + |__MACOSX # MacOSX resource fork directory. + )$ + /x', 'callback' => 0, 'recurse' => TRUE, 'key' => 'uri', @@ -2025,7 +2035,11 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { $files = array(); if (is_dir($dir) && $handle = opendir($dir)) { while (FALSE !== ($filename = readdir($handle))) { - if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') { + // Always exclude the current and parent directories. + if ($filename === '.' || $filename === '..') { + continue; + } + if (!preg_match($options['nomask'], $filename)) { $uri = "$dir/$filename"; $uri = file_stream_wrapper_uri_normalize($uri); if (is_dir($uri) && $options['recurse']) { diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 3633bae11dd58e735ad5f33e60d3eb66a58e1f7f..55d0372e7f492b32031f055c914b9ca05380f825 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -1099,14 +1099,14 @@ class FileScanDirectoryTest extends FileTestCase { } /** - * Check that the no-mask parameter is honored. + * Check that the nomask parameter is honored. */ function testOptionNoMask() { // Grab a listing of all the JavaSscript files. $all_files = file_scan_directory($this->path, '/^javascript-/'); $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.')); - // Now use the nomast parameter to filter out the .script file. + // Now use the nomask parameter to filter out the .script file. $filtered_files = file_scan_directory($this->path, '/^javascript-/', array('nomask' => '/.script$/')); $this->assertEqual(1, count($filtered_files), t('Filtered correctly.')); } @@ -1165,6 +1165,76 @@ class FileScanDirectoryTest extends FileTestCase { } } +class FileScanDirectoryNomaskTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'File scan directory nomask', + 'description' => 'Tests the default nomask pattern of the file_scan_directory() function.', + 'group' => 'File API', + ); + } + + /** + * Check the default nomask pattern. + */ + function testDefaultNomask() { + // Create a new, randomly-named directory to hold the test files. + $testdir = file_default_scheme() . '://' . $this->randomName(20); + drupal_mkdir($testdir); + // Whole filenames that should be excluded. + $excluded = array( + 'CVS', 'CVS.adm', 'RCS', 'RCSLOG', 'SCCS', '__MACOSX', + // All dot-files are excluded, but we are particularly concerned with + // these ones: + '.svn', '.git', '.bzr', + ); + // Included filenames. + $included = array('FILE'); + // Prefixes that should be excluded. + $prefixes = array('cvslog.', '.', '#', ',', '_$'); + foreach ($prefixes as $prefix) { + // The '.' directory is always excluded, and '_$' is excluded by suffix. + if ($prefix != '.' && $prefix != '_$') { + $included[] = $prefix; + } + $excluded[] = $prefix . 'FILE'; + } + // Suffixes that should be excluded. + $suffixes = array( + '~', '$', '.old', '.bak', '.BAK', '.orig', '.rej', + ); + foreach ($suffixes as $suffix) { + // A leading dot is always excluded. + if ($suffix[0] != '.') { + $included[] = $suffix; + } + $excluded[] = 'FILE' . $suffix; + } + // Build a list of test filenames. + $testnames = array_merge($included, $excluded); + // For each test name, create a subdirectory containing a regular file. + foreach ($testnames as $name) { + $subdir = "$testdir/$name"; + drupal_mkdir($subdir); + file_put_contents("$subdir/FILE", 'test'); + } + // Verify that all the files can be found with a non-matching 'nomask'. + $unfiltered_files = file_scan_directory( + $testdir, '/^FILE$/', array('nomask' => '/^$/') + ); + $args = array( + '%found' => count($unfiltered_files), + '%expected' => count($testnames), + ); + $this->assertEqual($args['%found'], $args['%expected'], t('Found %found files; expected %expected.', $args)); + $this->verbose(t('Created the following files: @files', array('@files' => implode(', ', array_keys($unfiltered_files))))); + // Verify that only the expected files are found with the default 'nomask'. + $filtered_files = file_scan_directory($testdir, '/^FILE$/'); + $this->assertEqual(count($included), count($filtered_files), t('The default nomask regex is working properly.')); + $this->verbose(t('Found the following files: @files', array('@files' => implode(', ', array_keys($filtered_files))))); + } +} + /** * Tests the file_scan_directory() function on remote filesystems. */ -- 1.7.5.4