对用户输入的正则表达式进行编码以用于File::Find::Rule是否安全



我正在开发一个应用程序,该应用程序在命令行接受用户的regex作为输入,然后应用该regex来查找当前目录下的某些文件。该应用程序支持UTF-8输入,并且应该能够找到UTF-8编码的文件名。这里有一个例子:

use feature qw(say);
use open qw( :std :utf8 );
use strict;
use utf8;
use warnings;
use Encode ();
use File::Find::Rule;
system 'touch', 'aæ', 'bæ', 'aa'; # some test files, 
my $pat = 'æ$';
my $pat_encode = encode( $pat );
run_test( $pat_encode, 'With encode()' );
run_test( $pat, 'Without encode()' );
my $pat2 = '[æ]$';
my $pat2_encode = encode( $pat2 );
run_test( $pat2_encode, 'With encode()' );
sub encode {
    return Encode::encode('UTF-8', $_[0], Encode::FB_CROAK | Encode::LEAVE_SRC);
}
sub run_test {
    my ( $pat_encode, $test_str ) = @_;
    say $test_str;
    say '-' x length $test_str;
    say "";
    my @files = File::Find::Rule->new->name( qr/$pat_encode/ )->in('.');
    for (@files) {
        $_ = Encode::decode('UTF-8', $_, Encode::FB_CROAK | Encode::LEAVE_SRC );
    }
    say $_ for @files;
}

输出为:

With encode()
-------------
aæ
bæ
Without encode()
----------------
With encode()
-------------
aæ
bæ

我预计最后一个正则表达式[æ]$在编码后将无法工作,因为æ将扩展到两个字节的0xC3A6,但不知何故,Perl似乎知道正则表达式是用UTF-8编码的,并为其工作做了一些神奇的事情。

我想知道是否有人知道后一个例子为什么有效,以及是否有其他情况下编码regex不起作用?(因此,我正试图决定是否可以使用File::Find::Rule,或者是否应该切换到File::Find,这将使我避免对正则表达式进行编码。)

事实证明,对正则表达式进行编码是不安全的。特别是如果括号表达式后面跟着一个或多个字符,正则表达式可能会选择不需要的文件。原因是UTF-8编码版本中只有一个字节会与括号表达式匹配。考虑对我的脚本进行以下修改:

system 'touch', 'aæ', 'aæ1', 'aa'; # some test files, 
my $pat = 'æ.$';
my $pat_encode = encode( $pat );
run_test( $pat_encode, 'With encode()' );
run_test( $pat, 'Without encode()' );
my $pat2 = '[æ].$';
my $pat2_encode = encode( $pat2 );
run_test( $pat2_encode, 'With encode()' );

现在,这应该只返回文件aæ1,但是$pat2 regex也将返回,因为括号表达式只会用完编码的æ的两个字节中的第一个,留下最后一个字节由$pat2中的尾随.匹配。

输出为:

 With encode()
-------------
aæ1
Without encode()
----------------
With encode()
-------------
aæ
aæ1

解决方案似乎是使用File::Find

use File::Find ();
system 'touch', 'aæ', 'aæ1', 'aa'; # some test files, 
my $pat = '[æ].$';
my $files = find_files( $pat );
say $_ for @$files;
sub decode {
    return Encode::decode('UTF-8', $_[0], Encode::FB_CROAK | Encode::LEAVE_SRC );
}
sub find_files {
    my ( $pat ) = @_;
    my @files;
    File::Find::find( sub { wanted( $pat, @files ) }, '.' );
    return @files;
}
sub wanted {
    my ( $pat, $files ) = @_;
    my $name = decode( $_ );
    my $full_name = decode( $File::Find::name );
    push @$files, $full_name if $name =~ /$pat/;
}

现在输出正确:

./aæ1

更新

事实上,File::Find::Rule毕竟是可以使用的。只需使用exec规则而不是name规则:

my $pat = '[æ].$';
my $files = find_files( $pat );
say for @$files;
sub find_files {
    my ( $pat ) = @_;
    my @files = File::Find::Rule->new->exec( sub { wanted( $pat ) } )->in('.');
    for (@files) {
        $_ = decode( $_ );
    }
    return @files;
}
sub wanted {
    my ( $pat ) = @_;
    my $name = decode( $_ );
    return ( $name =~ /$pat/ ) ? 1 : 0;
}

现在的输出是:

aæ1

相关内容

最新更新