用户编码正则表达式输入以便与File :: Find :: Rule一起使用是否安全?

时间:2016-04-20 06:34:24

标签: perl utf-8

我正在开发一个应用程序,它在命令行中使用正则表达式作为用户输入,然后应用该正则表达式查找当前目录下的某些文件。该应用程序支持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编码,并使其有效。

我想知道是否有人知道为什么后一个例子正在工作,如果还有其他情况下编码正则表达式不起作用? (所以我想确定是否可以使用File::Find::Rule或者是否应该切换到File::Find,这样我就可以避免编写正则表达式。)

1 个答案:

答案 0 :(得分:3)

事实证明,对正则表达式进行编码是不安全的。特别是如果括号表达式后跟一个或多个字符,则正则表达式可能会选择不需要的文件。原因是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正则表达式也将返回,因为只有编码æ的两个字节中的第一个将是括号表达式使用,最后一个字节与.中的$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