說真的,如果您能夠先證明您已讀過下列這幾個 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")'
中譯版著作權所有:蕭百齡及兩隻老虎工作室。本中譯版遵守並使用與原文版相同 的使用條款發行。