Skip to content

Commit b4aa93e

Browse files
committed
[GR-28734] Glob escaping
PullRequest: truffleruby/3430
2 parents 327ff20 + e02a803 commit b4aa93e

File tree

4 files changed

+39
-7
lines changed

4 files changed

+39
-7
lines changed

spec/ruby/core/dir/fixtures/common.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def self.mock_dir_files
8282

8383
special/test{1}/file[1]
8484
special/{}/special
85+
special/test\ +()[]{}/hello_world.erb
8586
]
8687

8788
platform_is_not :windows do

spec/ruby/core/dir/glob_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
nested/
8080
nested/.dotsubir/
8181
special/
82+
special/test\ +()[]{}/
8283
special/test{1}/
8384
special/{}/
8485
subdir_one/
@@ -130,6 +131,7 @@
130131
./nested/
131132
./nested/.dotsubir/
132133
./special/
134+
./special/test\ +()[]{}/
133135
./special/test{1}/
134136
./special/{}/
135137
./subdir_one/

spec/ruby/core/dir/shared/glob.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@
111111
it "matches files with backslashes in their name" do
112112
Dir.glob('special/\\\\{a,b}').should == ['special/\a']
113113
end
114+
115+
it "matches directory with special characters in their name in complex patterns" do
116+
Dir.glob("special/test +()\\[\\]\\{\\}/hello_world{.{en},}{.{html},}{+{phone},}{.{erb},}").should == ['special/test +()[]{}/hello_world.erb']
117+
end
114118
end
115119

116120
it "matches regexp special ^" do
@@ -225,6 +229,7 @@
225229
dir/
226230
nested/
227231
special/
232+
special/test\ +()[]{}/
228233
special/test{1}/
229234
special/{}/
230235
subdir_one/

src/main/ruby/truffleruby/core/dir_glob.rb

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131

3232
class Dir
3333
module Glob
34-
no_meta_chars = '[^*?\\[\\]{}\\\\]'
34+
no_meta_chars = /[^*?\[\]{}\\]/
35+
no_meta_chars_unescaped = /(?:#{no_meta_chars}|\\\\|\\\*|\\\?|\\\[|\\\]|\\\{|\\\})/
3536
NO_GLOB_META_CHARS = /\A#{no_meta_chars}+\z/
36-
TRAILING_BRACES = /\A(#{no_meta_chars}+)(?:\{(#{no_meta_chars}*)\})?\z/
37+
NO_GLOB_META_CHARS_UNESCAPED = /\A#{no_meta_chars_unescaped}+\z/
38+
TRAILING_BRACES = /\A(#{no_meta_chars_unescaped}+)(?:\{(#{no_meta_chars_unescaped}*)\})?\z/
3739

3840
class Node
3941
def initialize(nxt, flags)
40-
@flags = flags
42+
@flags = flags | File::FNM_EXTGLOB
4143
@next = nxt
4244
@separator = nil
4345
end
@@ -349,6 +351,10 @@ def process_entry(entry, entry_type, matches, parent, glob_base_dir)
349351
end
350352
end
351353

354+
def self.unescape(pattern)
355+
pattern.gsub(/\\(.)/, '\\1')
356+
end
357+
352358
def self.path_split(str)
353359
start = 0
354360
ret = []
@@ -383,7 +389,10 @@ def self.path_split(str)
383389
end
384390

385391
def self.single_compile(glob, flags=0)
386-
if glob.getbyte(-1) != 47 && NO_GLOB_META_CHARS.match?(glob) # byte value 47 = ?/
392+
escape = (flags & File::FNM_NOESCAPE) == 0
393+
if escape && glob.getbyte(-1) != 47 && NO_GLOB_META_CHARS_UNESCAPED.match?(glob) # byte value 47 = ?/
394+
return ConstantEntry.new nil, flags, unescape(glob)
395+
elsif !escape && glob.getbyte(-1) != 47 && NO_GLOB_META_CHARS.match?(glob) # byte value 47 = ?/
387396
return ConstantEntry.new nil, flags, glob
388397
end
389398

@@ -393,8 +402,10 @@ def self.single_compile(glob, flags=0)
393402
last = DirectoriesOnly.new nil, flags
394403
else
395404
file = parts.pop
396-
if NO_GLOB_META_CHARS.match?(file)
405+
if !escape && NO_GLOB_META_CHARS.match?(file)
397406
last = ConstantEntry.new nil, flags, file
407+
elsif escape && NO_GLOB_META_CHARS_UNESCAPED.match?(file)
408+
last = ConstantEntry.new nil, flags, unescape(file)
398409
elsif file == '*'
399410
last = AllNameEntryMatch.new nil, flags, file
400411
elsif file && file[0] == '*' && NO_GLOB_META_CHARS.match?(file[1..])
@@ -414,14 +425,25 @@ def self.single_compile(glob, flags=0)
414425
else
415426
last = RecursiveDirectories.new last, flags
416427
end
417-
elsif NO_GLOB_META_CHARS.match?(dir)
428+
elsif !escape && NO_GLOB_META_CHARS.match?(dir)
418429
while NO_GLOB_META_CHARS.match?(parts[-2])
419430
next_sep = parts.pop
420431
next_sect = parts.pop
421432

422433
dir = next_sect << next_sep << dir
423434
end
424435

436+
last = ConstantDirectory.new last, flags, dir
437+
elsif escape && NO_GLOB_META_CHARS_UNESCAPED.match?(dir)
438+
439+
dir = unescape(dir)
440+
while NO_GLOB_META_CHARS_UNESCAPED.match?(parts[-2])
441+
next_sep = parts.pop
442+
next_sect = unescape(parts.pop)
443+
444+
dir = next_sect << next_sep << dir
445+
end
446+
425447
last = ConstantDirectory.new last, flags, dir
426448
elsif !dir.empty?
427449
last = DirectoryMatch.new last, flags, dir
@@ -466,9 +488,10 @@ def self.glob(base_dir, pattern, flags, matches)
466488
# only as a suffix.
467489

468490
if braces = m[2]
469-
stem = m[1]
491+
stem = unescape(m[1])
470492

471493
braces.split(',').each do |s|
494+
s = unescape(s)
472495
path = "#{stem}#{s}"
473496
if Truffle::FileOperations.exist? path_join(base_dir, path)
474497
matches << path
@@ -480,6 +503,7 @@ def self.glob(base_dir, pattern, flags, matches)
480503
matches << stem if Truffle::FileOperations.exist? path_join(base_dir, stem)
481504
end
482505
else
506+
pattern = unescape(pattern)
483507
matches << pattern if Truffle::FileOperations.exist?(path_join(base_dir, pattern))
484508
end
485509

0 commit comments

Comments
 (0)