Perl:只是正则表达式的quotemeta?文件名是否安全?

时间:2010-09-26 05:24:14

标签: perl file unix

在回答有关使用空格(以及可能的其他字符)安全转义文件名的this问题时,one of the answers表示使用Perl的内置 quotemeta 功能

quotemeta的文档说明:

quotemeta (and \Q ... \E ) are useful when interpolating strings 
into regular expressions, because by default an interpolated variable 
will be considered a mini-regular expression.  

在quotemeta的文档中,唯一提及它的用法是使用/[A-Za-z_0-9]/转义\以外的所有字符,以便在正则表达式中使用。它没有说明文件名的用途。然而,这似乎是一种非常令人愉快的,如果没有记录的副作用。

在对SinanÜnüranswer对早期问题的评论中,hobbs说:

  

shell转义 不同   regexp逃避,虽然我不能   想出一个情况   quotemeta会给人一种真正不安全的感觉   结果,它不适合任务。   如果你必须逃避,而不是   绕过壳,我建议尝试   String :: ShellQuote需要更多   使用sh单一的保守方法   报价除了以外的所有东西   单引号本身,和   单引号的反斜杠。 - hobbs   09年8月13日14:25

完全是否安全 - 使用quotemeta代替String::Shellquote这样更保守的文件引用? quotemeta utf8或多字节字符是否安全?

我把一个不明确的测试放在一起。除了包含\n\r的文件名或目录名外,quotemeta似乎运作良好。虽然很少见,但这些字符在Unix中是合法的,我已经看过了。回想一下,某些字符,如LF,CR和NUL无法使用\进行转义。我用带有quotemeta的700k文件读取了我的硬盘,但没有出现故障。

我怀疑(虽然我还没有证明),quotemeta可能会因多字节字符而失败,其中一个或多个字节属于ASCII范围。例如,à可以编码为一个字符(UTF8 C3 A0)或两个字符(U + 0061给出a u + 0300是一个组合格雷夫口音)。我在quotemeta中遇到的唯一失败是在我创建的路径中包含\n\r的文件。我会对nasty_names进行测试的其他角色感兴趣。

ShellQuote完全适用于所有文件名,但创建文件时由NUL终止的文件名除外。我从来没有遇到过它。

那么使用什么?需要明确的是:shell引用不是我经常做的事情,因为我通常只是使用Perl打开来打开进程的管道。该方法不会遇到讨论的shell问题。我感兴趣,因为我看到通常用于文件名转义的quotemeta。

(感谢Ether我添加了IPC :: System :: Simple)

测试文件:

use strict; use warnings; use autodie;
use String::ShellQuote;
use File::Find;
use File::Path;
use IPC::System::Simple 'capturex';

my @nasty_names;
my $top_dir = '/Users/andrew/bin/pipetestdir/testdir';
my $sub_dir = "easy_to_remove_me";
my (@qfail, @sfail, @ipcfail);

sub wanted { 
    if ($File::Find::name) { 
         my $rtr;
         my $exec1="ls ".quotemeta($File::Find::name);
         my $exec2="ls ".shell_quote($File::Find::name);
         my @exec3= ("ls", $File::Find::name);

         $rtr=`$exec1`;  
         push @qfail, "$exec1" 
              if $rtr=~/^\s*$/ ;

         $rtr=`$exec2`;
         push @sfail, "$exec2" 
              if $rtr=~/^\s*$/ ;

         $rtr = capturex(@exec3);
         push @ipcfail, \@exec3
              if $rtr=~/^\s*$/ ;     
    }
}

chdir($top_dir) or die "$!";
mkdir "$top_dir/$sub_dir";
chdir "$top_dir/$sub_dir";

push @nasty_names, "name with new line \n in the middle";
push @nasty_names, "name with CR \r in the middle";
push @nasty_names, "name with tab\tright there";
push @nasty_names, "utf \x{0061}\x{0300} combining diacritic";
push @nasty_names, "utf e̋ alt combining diacritic";
push @nasty_names, "utf e\x{cc8b} alt combining diacritic";
push @nasty_names, "utf άέᾄ greek";
push @nasty_names, 'back\slashes\\Not\\\at\\\\end';
push @nasty_names, qw|back\slashes\\IS\\\at\\\\end\\\\|;

sub create_nasty_files {
    for my $name (@nasty_names) {
       open my $fh, '>', $name ; 
       close $fh;
    }
}

for my $dir (@nasty_names) {
    chdir("$top_dir/$sub_dir");
    mkpath($dir);
    chdir $dir;
    create_nasty_files();
}

find(\&wanted, $top_dir);

print "\nquotemeta failed on:\n", join "\n", @qfail;
print "\nShell Quote failed on:\n", join "\n", @sfail;
print "\ncapturex failed on:\n", join "\n", @ipcfail;
print "\n\n\n",
      "Remove \"$top_dir/$sub_dir\" before running again...\n\n";

3 个答案:

答案 0 :(得分:15)

在这些假设下,Quotemeta是安全的:

  1. 只有非字母数字字符才有特殊含义。
  2. 如果非字母数字字符具有特殊含义,则在其前面添加反斜杠将始终使其成为非特殊字符。
  3. 如果非字母数字字符没有特殊含义,则在其前面加一个反斜杠将无效。
  4. 无论你使用什么引用上下文,shell都违反规则2和3 - 在引号之外,反斜杠 - 换行不会生成换行符;在双引号中,反斜杠标点符号在输出中添加反斜杠(在某个标点符号列表之外);在单引号中,一切都是字面的,反斜杠甚至不能保护你免受结束单引号。

    如果您需要引用shell的内容,我仍然建议String::ShellQuote。我还建议尽量避免让shell完全处理您的文件名,如果可以,请使用LIST - 表单system / exec / openIPC::Open2,{ {3}}或IPC::Open3

    除了shell之外的东西......许多不同的东西违反了一个或多个规则。例如,过时的POSIX“基本”正则表达式和各种编辑器正则表达式都有标点字符,默认情况下是非特殊的,但是当前面加反斜杠时变为特殊。基本上我所说的是,知道你正在非常好地提供数据,并正确逃脱。只有在quotemeta完全合适的情况下才使用{{1}},或者如果您将其用于不太重要的事情。

答案 1 :(得分:3)

您还可以使用IPC::System::Simple capture()capturex()(我在第一个问题的另一个答案中建议),这将让您绕过shell。

我将这些行添加到您的脚本中,发现没有示例失败:

use IPC::System::Simple 'capturex';
...
my (@qfail, @sfail, @ipcfail);
...
         my @exec3= ("ls", $File::Find::name);
...
         $rtr = capturex(@exec3);
         push @ipcfail, \@exec3
              if $rtr=~/^\s*$/ ;
...
print "\ncapturex failed on:\n", join "\n", @ipcfail;

但总的来说,你应该解决实际问题,而不是试图找到更好的创可贴。 quotemeta专门用于转义正则表达式重要字符,正如您所发现的那样,它与对shell重要的字符集不完全重叠。

答案 2 :(得分:0)

以下是仅限Unix的解决方案;有关Windows支持,请参阅https://stackoverflow.com/a/32161361/45375

另一种选择是这个简单的函数,即使使用非ASCII字符(假设编码正确),以及\n\r也可以稳健地工作,但不包括NUL (见下)。

sub quoteforsh { join ' ', map { "'" . s/'/'\\''/gr . "'" } @_ }

该函数用单引号括起每个参数,如果指定了多个参数,则用空格分隔它们。

使用单引号字符串,因为它们的内容不受类似POSIX的shell中的任何解释。

但是,因此,您甚至无法自行转义'个实例,这需要以下解决方法:每个嵌入式'实例都替换为'\''(原文如此),这有效地分割了将字符串输入多个单引号字符串,并使用转义'个实例 - \' - 拼接 - 然后shell将字符串部分重新组合为单个字符串。

示例:

print quoteforsh 'I\'m here & wëll';

字面上产生(包括封闭的单引号)'I'\''m here & wëll',它们是 3 连续的字符串 - 'I'\''&well',然后shell会重新组合成单个字符串,在删除引号后,会产生I'm here & wëll

OSX Unicode警告:HFS +将文件名存储在NFD中(分解 Unicode正常形式 - 基本字母后跟另一个与关联变音符号相关的字符),而Perl通常会创建NFC(组成 Unicode正常形式 - 单个字符标识重音字母。)

当使用 literal 文件名时,这种区别并不重要(系统调用执行映射),但是当使用globs时,它确实存在,并且不幸的是,你必须自己做两种形式之间的翻译。

支持NUL(0x0)字符:

我不认为NUL字符。在文件名中是一个现实世界的关注:

  • 大多数类似POSIX的shell(bashdashksh忽略 NUL个字符。在命令行上 - zsh是唯一的例外。
  • 即使这不是问题,根据Wikipedia,大多数Unix系统支持NUL字符。在文件名中。

此外,尝试将带有NUL的文字传递给Perl的system()函数会破坏调用,大概是因为字符串传递给{{1在第一个sh -c

切断
NUL