说真的,如果您能够先证明您已读过下列这几个 FAQs ,但遇到的问题并不单纯、非叁言两语即可回答的话,那麽您 post到 comp.infosystems.www.authoring.cgi上(如果是有关 HTTP 、 HTML ,或 CGI通信协定)的问题可能也会得到口气和缓而有用的答覆。表面上看似 Perl,但骨子里是 CGI之类的问题,如果 post到 comp.lang.perl.misc人家可能就不会这麽乐意地接受了。
几个实用的 FAQs 分别是:
http://www.perl.com/perl/faq/idiots-guide.html http://www3.pair.com/webthing/docs/cgi/faqs/cgifaq.shtml http://www.perl.com/perl/faq/perl-cgi-faq.html http://www-genome.wi.mit.edu/WWW/faqs/www-security-faq.html http://www.boutell.com/faq/
【译者】上面第叁份文件,Perl-CGI-FAQ的中译版可在 http://2Ti.com/cgi-bin/2T/perl/perl-cgi-faq-chi/ 处取得。最後一份(WWW FAQ)的中译版可自 http://www.acer.net/document/cwwwfaq/ 取得。
许多人尝试用简陋的正规表示式来解决这个问题,譬如说像
s/<.*?>//g
,但这个式子在很多情况下会失败,因为要处理的字串可能会跨越断行字元,也可能含有被 quote【跳脱】的箭头号,或有
HTML comment出现;再加上一些疏忽,譬如,人们常忘了转换如
<
的 entities(跳脱字
元)。
以下这个「简陋」的方法对大多数的档案都有效:
#!/usr/bin/perl -p0777 s/<(?:[^>'"]*|(['"]).*?\1)*>//gs
如果您想要更完整的解法,请看叁部曲的 striphtml 程式, http://www.perl.com/CPAN/authors/Tom_Christiansen/scripts/striphtml.gz 。
#!/usr/bin/perl -n00 # qxurl - tchrist@perl.com print "$2\n" while m{ < \s* A \s+ HREF \s* = \s* (["']) (.*?) \1 \s* > }gsix;
这个版本并不替相对式写法的 URLs 作调整,也不懂代换 bases【< LINK BASE=``...''>】,或如何处理 HTML comments、同时处理同一个标签里的 HREF和 NAME 属性,或接受 URL形式的参数。同时,它要比一个较「完整」、利用 LWP [libwww-perl]模组套件的解法,例如 http://www.perl.com/CPAN/authors/Tom_Christiansen/scripts/xurl.gz这个程 式,快上一百倍。
start_multipart_form()
这个 method
就是为此设计的,它和 startform()
这个 method
是两回事。
$html_code = `lynx -source $url`; $text_data = `lynx -dump $url`;
收录在 CPAN里的 libwww-perl (LWP)模组则提供了更强的方法来做这件事。它不但可钻过 proxies,而且也不需要 lynx:
# print HTML from a URL use LWP::Simple; getprint "http://www.sn.no/libwww-perl/";
# print ASCII from HTML from a URL use LWP::Simple; use HTML::Parse; use HTML::FormatText; my ($html, $ascii); $html = get("http://www.perl.com/"); defined $html or die "Can't fetch HTML from http://www.perl.com/"; $ascii = HTML::FormatText->new->format(parse_html($html)); print $ascii;
$string = "http://altavista.digital.com/cgi-bin/query?pg=q&what=news&fmt=.&q=%2Bcgi-bin+%2Bperl.exe"; $string =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/ge;
编码比较困难一点,因为您不能盲目地把所有非字母数字的字元 (\W
)都一律转换成十六进位的跳脱码。很重要的是有特殊意义的字元,如
/
和 ?
便不可以
转换。要做得正确,最简单的方法大概是使用现成的 URI::Escape模组,而不要重新发明轮子。这个模组是 libwww-perl
套件
(LWP)的一部分,可自
CPAN取得。
Content-Type
这个标头,相反地,用 Location:
这个标头。按正式规定,应当 URL:
才是正确的标头。因此 CGI.pm模组(可
由CPAN取得)两个标头都送:
Location: http://www.domain.com/newpage URI: http://www.domain.com/newpage
要注意的是,由於 servers采用「最高效率化」的运作方式,故在这些标头中如 果使用相对式的 URLs可能会产生奇怪的後果。
use HTTPD::UserAdmin (); HTTPD::UserAdmin ->new(DB => "/foo/.htpasswd") ->add($username => $password);
简单一句话:使用 tainting(沾腥?)这项功能(详见 perlsec
)。它会让所有不在您的 script中、来路不明的资料(譬如,
CGI参数)无法放到
eval
或 system
等呼叫中使用。除了使用 tainting之外,绝对不要使用单一参数
的方式下参数给 system()
或 exec(),
而应将欲执行的指令及其参数放在一个序
列或阵列里面,再传给 system()
或 exec(),这样便可避免
globbing【即被 shell先做解译】。
$/ = ''; $header = <MSG>; $header =~ s/\n\s+/ /g; #将延续行合并成单行 %head = ( UNIX_FROM_LINE, split /^([-\w]+):\s*/m, $header );
譬如说,您若想保留所有 Received栏位资料的话【因 Received栏位通常不止一个】,这个解法便不太行了。一个完整的解法是使用收录在 CPAN的 Mail::Header模组( MailTools 套件的一部分)。
$ENV{CONTENT_LENGTH}
和 $ENV{QUERY_STRING}
的程式码。没错,这麽
写是可以行得通,但事实上也有很多在网路上出没的这类程式根本不能用!
请不要忍不住去重新发明轮子【译者:这是英文的说法 (reinventing the wheels),也就是浪费时间做人家做过的事的意思】。请改用 CGI.pm或 CGI_Lite.pm(可自 CPAN取得)。如果您被困在无模组的 perl1 .. perl4的土地上,您可以试看看 cgi-lib.pl(可至 http://www.bio.cam.ac.uk/web/form.html取得)。
如果没有寄封信到一个位址去试试看它会不会弹回来(即使是这麽做您还得面对停顿的问题),您是无法确定一个位址是否真的存在的。即使您套用 email 标头的标准规格来做检查的依据,您还是有可能会遇到问题,因为有些送得到的位址并不 符合 RFC-822(电子邮件标头的标准)的规定,但有些符合标准的位址却无法投 递。
许多人试图用一个简单的正规表示式,例如 /^[\w.-]+\@([\w.-]\.)+\w+$/
来消除一些通常是无效的 email位址。不过,这样做也把很多合格的位址给一起滤掉了,而且对测试一个位址有没有希望投递成功完全没有帮助,所以在此建议大家不要这麽做;不过您可以看看: http://www.perl.com/CPAN/authors/Tom_Christiansen/scripts/ckaddr.gz。这个 script真的彻底地依据所有的
RFC规定来做检验(除了内嵌式 comments外),同时会排除一些您可能不会想送信去的位址(如 Bill Clinton【美国柯林顿总统】或您的 postmaster),然後它会确定位址中的主机名称可在
DNS中找得到。这个 script
跑起来不是很快,但至少有效。
不少 CGI scripts的作者使用另一个替代的方案:用一个简单的正规表示式,(如上头的那个)。如果一个位址能让这个式子对得上的话,那麽就接受这个位址。如果这个位址对不上这个式子的话,便再向使用者讯问,以确定她们填入的这个位 址正确无误。
use MIME::base64; $decoded = decode_base64($encoded);
一个比较直接的解法是先做一点简单的转译,然後使用 unpack()
这个函数的 ``u''
格式:
tr#A-Za-z0-9+/##cd; #去除非 base64字元 tr#A-Za-z0-9+/# -_#; #转换成 uu码格式 $len = pack("c", 32 + 0.75*length); #计算长度字元 print unpack("u", $len . $_); # uu解码後 print
use Sys::Hostname; $address = sprintf('%s@%s', getpwuid($<), hostname);
有的公司对 email位址有统筹规画,因此这麽一来您可能会合成出不被公司的 email 主机接受的位址。所以如果有这类的顾虑的话,您应该直接向 users要他们的 email 位址。 而且,并不是所有能跑 Perl的系统都像 Unix一样,可以很容易得到这些资料。
CPAN里的 Mail::Util模组( MailTools
套件的一部分)中有一个 mailaddress()
函数,它会试着去猜 user的 email位址。这个函数使用比上面的 code聪明的方法,它会参考安装时所得到的设定资料,但是错误仍可能发生。所以,再一次地,最好的方法通常是直接问 user
本人。
#送信 use Mail::Internet; use Mail::Header; #设定使用哪台主机 $ENV{SMTPHOSTS} = 'mail.frii.com'; #制作标头 $header = new Mail::Header; $header->add('From', 'gnat@frii.com'); $header->add('Subject', 'Testing'); $header->add('To', 'gnat@frii.com'); #制作本文 $body = 'This is a test, ignore'; #产生 mail物件 $mail = new Mail::Internet(undef, Header => $header, Body => \[$body]); #送出 $mail->smtpsend or die;
`hostname`
这个程式来取得主机名。
虽然这麽做很方便,但也同时增加了移植到其他平台上的困难。这是一个很典型的
例子,在方便和可移植性之间作抉择,不论选哪一边,必须付出一些牺牲和代价。
Sys::Hostname这个模组(标准 perl发行的一部分)可用来取得机器的名字,然後您便可利用 gethostbyname()
这个系统呼叫来找出该机的
IP位址了(假定您的
DNS
运作正常)。
use Socket; use Sys::Hostname; my $host = hostname(); my $addr = inet_ntoa(scalar(gethostbyname($name || 'localhost')));
至少在 Unix底下,取得 DNS网域名最简单的方法大概要算是直接从 /etc/resolv.conf这个档案里面找。当然,这麽做的前提是 resolv.conf这个档 案的设定必须照惯例的格式,还有就是这个档案必先存在才行。
(Perl在非 Unix系统下尚需要一有效的方法来测出机器和网域名)
perl -MNews::NNTPClient -e 'print News::NNTPClient->new->list("newsgroups")'
中译版着作权所有:萧百龄及两只老虎工作室。本中译版遵守并使用与原文版相同 的使用条款发行。