“您使用了很多非常糟糕的语法,我想给您一些反馈”。
那么,就在这里,告诉我如何使它更Perlish?我来自C语言背景,所以全是耳朵(或眼睛)!
#!/usr/bin/perl -T
use strict;
use warnings;
use CGI qw/:all/;
my $basepath = '/some/path/to';
my $f = param("f") || '';
($f eq '') && die;
my $path = "$basepath/$f.pdf";
open(PATH, "<$path") || die;
binmode PATH;
print header(-type => "application/pdf",
-target => "$f");
print;
while (<PATH>) {
print($_);
}
close(PATH);
#1 楼
我将逐行检查您的代码并提供反馈。我们将跳过关于不使用CGI的一般建议,因为它实际上适合您在此处尝试做的事情。被其他人说。我试图提供完整的反馈,但我没有从他人那里抄袭。如果我们中的几个人提供相同的反馈,则可能必须有一定道理。#!/usr/bin/perl -T
-T
标志打开污染模式。我不确定您打算这样做,还是只是从其他地方复制了它。污染模式实质上可确保必须先验证从外部进入程序的数据,然后再使用它们。但是您并没有保留变量$f
。可能会咬你的背。use strict;
use warnings;
这很好!做得好。您的代码应始终以
strict
和warnings
开头。use CGI qw/:all/;
您要将很多东西导入名称空间,但您只使用
param
。相反,可以考虑仅导入:cgi
,这将减少您的工作量,或者仅导入param
和header
。这也使人们更容易记住该函数的来源。use CGI qw(param header);
my $f = param("f") || '';
$f
不是最大的变量名称。使用描述性变量名,例如$filename
。如果文件名为零0
或0000
,则此操作将失败。在本文结尾处了解有关此内容的更多信息。($f eq '') && die;
有些人喜欢使用逻辑运算符进行流程控制,这真的很奇怪。如果您希望简洁的语法没有块,请使用后缀
if
或unless
。此外,请提供一条错误消息,说明失败的原因。这是CGI,因此它将显示在您的服务器日志中。您不希望在第10行说死,是吗?
我更喜欢对空字符串使用
q{}
引号运算符,因为它显示了更清晰地给出空字符串的意图。die "No filename given" if $f eq q{}.
open(PATH, "<$path") || die;
不要将GLOB用作文件句柄。它对所有程序都是全局的,包括其他模块和名称空间。如果另一个模块要对句柄使用相同的PATH
名称,则会发生冲突。而是使用词汇文件句柄。约定是命名文件句柄$fh
。这具有附加的好处,即一旦变量超出范围,Perl就会为您关闭句柄。在这种情况下,程序结束了。因此,您不必显式使用close
。使用
open
的三参数形式,其中第二个参数是模式。在您的情况下,阅读时为<
。这使文件操作更安全,因为您的$path
可以例如以管道|
开头,这将改变含义。特别是如果您的文件名的一部分来自用户(甚至没有取消过它),这是至关重要的建议。 。请改用||
,因为绑定非常松散,因此您不需要在or
后面加上括号。它也会读得更像实际的英文。在
open
中包含一条有意义的错误消息,尤其是从die
调用返回的错误$!
。open my $fh, '<', $path or die "Error opening '$path': $!";
binmode $fh;
print header(-type => "application/pdf",
-target => "$f");
正确缩进代码。这看起来很奇怪。您已缩进第二行9个空格。看起来像vim中的一个完整选项卡和一个空格。取而代之的是,将其正确地折断并对齐。平均而言,读取的代码是编写的代码的十倍。为此做准备。
如果不需要插值,请不要引用变量。如果只有一个变量,请不要使用引号。
print header(
-type => "application/pdf",
-target => $filename,
);
print;
该行将打印
open
的当前值。我不确定程序执行时这是什么。这可能不是故意的。摆脱它。while (<PATH>) {
print($_);
}
如果要使用循环变量,请对其进行命名。
$_
主题对于短单线,$_
等确实非常有用,但是map
循环很少出现这种情况。while ( my $buffer = <$fh> ) {
print $buffer;
}
您也可以使用上述文章-fix表示法。同样,您不需要使用该主题,因为它是隐式的。
print while <$fh>;
但是您也可以完全摆脱该主题,而只需使用以下事实:
while
是列表上下文,菱形运算符print
以列表上下文形式将完整文件返回为数组。print <$fh>;
我之前说过,您对
<>
的检查如果名称为假值,则使用$filename
的操作将失败。请记住,Perl没有实际的||
和true
,只是评估真实性的值,而不是评估真实性的值。在Perl中很少有错误的事情。 false
,0
,空字符串undef
和空列表q{}
就是这种情况。 /> my $filename = param('f');
die unless $filename;
如果没有参数
()
,则0
函数返回f
。所以我们可以通过说param
来避免这种情况。这让undef
通过。但是空字符串呢?本质上,您需要确定名为
unless defined
的文件是否有效。随你(由你决定。如果您不希望这样做,则必须进行其他检查。还可以使用
0.pdf
检查该文件是否存在。my $filename = param('f');
die unless defined $filename;
但是,无论如何
$basepath/.pdf
都会进行检查,因此不需要。#2 楼
#!/usr/bin/perl -T
use strict;
use warnings;
use CGI qw/ :all /;
my $basepath = '/some/path/to';
my $f = param('f') || ''; # No interpolation, no double quotes.
die if $f eq ''; # Don't use && for flow control.
my $path = "$basepath/$f.pdf";
open my $PATH, '<', $path or die; # Lexical filehandles, 3-argument open.
binmode $PATH;
print header(-type => 'application/pdf', # Single quotes.
-target => $f); # No quotes needed at all.
# print; # This prints $_, are you sure?
print while <$PATH>;
close $PATH;
我还将为
die
提供一个参数,以便您可以更轻松地搜索日志。#3 楼
您的param('f')
是用户提供的,因此可能引发安全问题。特别是,它可以用于导航到$basepath/../../some-secret-file.pdf
。减少了影响,因为您只能读取以.pdf
结尾的文件,但它基本上仍然存在。作为缓解措施,您应限制允许的文件名并与之匹配。即明确验证您的文件名是否正确,不要仅仅尝试排除一些已知的错误模式。例如,在这种情况下,您可能希望允许任何仅由ASCII字母或数字组成的文件名:
unless ($filename =~ /\A[a-zA-Z0-9]+\z/) {
die "Illegal file name was provided: $filename";
}
(断言
\A
和\z
开头是/字符串的结尾,因此请确保整个字符串与该模式匹配,并且不仅在某处包含此模式。)而不是
die
,对于用户而言,它会作为500内部服务器错误显示,显式返回一些4xx错误可能更合适。评论
\ $ \ begingroup \ $
-T本质上应该在这里。
\ $ \ endgroup \ $
–simbabque
17年11月22日在13:58
\ $ \ begingroup \ $
@simbabque Perl认为从具有污名的文件中读取是可以的。因此,异味模式将不会阻止所提出的安全问题。这是一项强大的安全功能,但不是灵丹妙药。
\ $ \ endgroup \ $
–阿蒙
17年11月22日在14:09
\ $ \ begingroup \ $
@amon是的,我知道安全问题。这就是为什么我的CGI脚本没有在用户指定整个文件名的情况下而是在文件名后附加“ .pdf”扩展名,因此,更糟糕的是,恶意用户可以打开PDF文件,而不仅仅是打开任何文件。在另一篇文章中,这是实际脚本的简化版本。为了简洁起见,我省略了错误检查和安全检查处理代码,以便更清楚地显示我所提出的实际问题。
\ $ \ endgroup \ $
– amblabs
17年11月24日在8:40
#4 楼
使用原始标头将“内联”和文件名结合在一起:print $q->header(
-type => "application/pdf",
-target => $file,
'-Content-Disposition' => "inline; filename=\"$file\"",
);
评论
\ $ \ begingroup \ $
打印,而<$ fh>一次仅缓冲一行。 print <$ fh>是否一次行缓冲,还是将整个文档读入内存?
\ $ \ endgroup \ $
–马克
17年11月21日在21:19
\ $ \ begingroup \ $
是后者。
\ $ \ endgroup \ $
–乔罗巴
17年11月22日在8:27
\ $ \ begingroup \ $
我对使用菱形运算符读取二进制文件至关重要(是的,尽管PDF主要由明文命令组成,但流和最终流之间的所有内容(例如,都是二进制文件,因此binmode可以)。如perlopentut(1)所述,您可能应该改用read()。
\ $ \ endgroup \ $
–杜布
17年11月22日在10:02
\ $ \ begingroup \ $
-target => $ filename;末尾不得使用分号。
\ $ \ endgroup \ $
–天花板
19年4月16日在9:58
\ $ \ begingroup \ $
@ceving哦,对。我已经解决了。感谢您指出。
\ $ \ endgroup \ $
–simbabque
19年4月16日在10:59