perlfaq9 - perl常問問題集,第九篇

目錄


篇名

perlfaq9 -網路連線(原文版 Revision: 1.16, Date: 1997/04/23 18:12:06. 中文版 $Revision: 1.13 $, $Date: 1997/07/12 20:44:25 $)


概述

本篇涵蓋網路連線、 Internet ,還有幾個關於 WWW 的問題。


我的 CGI script可在指令列下執行但無法從瀏覽器執行。您能不能幫我修修看?

當然,但您恐怕付不起雇我們的簽約金 :-)

說真的,如果您能夠先證明您已讀過下列這幾個 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/ 取得。


如何去除文章中的 HTML標籤?

最正確(儘管不是最快)的方法是使用 HTML::Parse模組(可由 CPAN取得,是所有寫 Web程式者必備的 libwww-perl 套件的一部分)。

許多人嘗試用簡陋的正規表示式來解決這個問題,譬如說像 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


如何萃取 URLs?

一個快速但不完美的做法是

    #!/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這個程 式,快上一百倍。


如何從 user端上傳資料?如何在另一台機器上開一個檔案?

如果是 HTML表格的話,您可以使用 multipart/form-data的編碼格式。 CGI.pm(可自 CPAN取得)中的 start_multipart_form()這個 method 就是為此設計的,它和 startform()這個 method 是兩回事。


如何在 HTML中做 pop-up menu(跳出式選單)?

<SELECT><OPTION>這兩個標籤。 CGI.pm模組(可由 CPAN取得)對這個 widget【此指跳出式選單這個介面成分】還有許多其他的介面成分都有支援【即有製作動態標籤的函式】,其中有些是以巧妙模擬的方 式達成。


如何抓 HTML檔案?

有一個方法是,如果您的系統上裝有 lynx一類的文字模式的 HTML瀏覽器的話,那麼可以這麼做:

    $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;


如何解開或產生 Web上那些冠 %的碼?

以下是一個解碼的實例:

    $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取得。


如何【將 requests】轉向到另一頁去?

在您的回應中不要使用 Content-Type這個標頭,相反地,用 Location: 這個標頭。按正式規定,應當 URL: 才是正確的標頭。因此 CGI.pm模組(可 由CPAN取得)兩個標頭都送:

    Location: http://www.domain.com/newpage
    URI: http://www.domain.com/newpage

要注意的是,由於 servers採用「最高效率化」的運作方式,故在這些標頭中如 果使用相對式的 URLs可能會產生奇怪的後果。


如何替網頁加上密碼?

不一定,要看情況。您需要讀您 server的使用手冊,或者查查看上頭所列的幾個 FAQs。


要怎麼用 Perl來編輯 .htpasswd和 .htgroup這兩個檔案?

HTTPD::UserAdmin 和 HTTPD::GroupAdmin等模組為這些檔案提供了統一的物件導向介面,儘管這些檔案可能以各種不同的格式儲存。這些資料庫可能是純文字格式、 dbm、Berkeley DB或任何 DBI相容的資料庫驅動程式 (drivers)。 HTTPD::UserAdmin支援`Basic' 和 `Digest'這兩個認證模式所用的檔案。以下是 一例:

    use HTTPD::UserAdmin ();
    HTTPD::UserAdmin
          ->new(DB => "/foo/.htpasswd")
          ->add($username => $password);


如何防範使用者藉由填我的 CGI表格來做壞事?

閱讀 CGI security FAQ,(可在 http://www-genome.wi.mit.edu/WWW/faqs/www-security-faq.html取得),還有 Perl CGI FAQ,在 http://www.perl.com/CPAN/doc/FAQs/cgi/perl-cgi-faq.html

簡單一句話:使用 tainting(沾腥?)這項功能(詳見 perlsec )。它會讓所有不在您的 script中、來路不明的資料(譬如, CGI參數)無法放到 evalsystem等呼叫中使用。除了使用 tainting之外,絕對不要使用單一參數 的方式下參數給 system()exec(),而應將欲執行的指令及其參數放在一個序 列或陣列裡面,再傳給 system()exec(),這樣便可避免 globbing【即被 shell先做解譯】。


如何解讀、萃取 email標頭資料?

如果您只需要一個「快而髒」的解法的話,您可以試試這個從再版的 ``Programming Perl''第 222頁中拿出來的例子:

    $/ = '';
    $header = <MSG>;
    $header =~ s/\n\s+/ /g;      #將延續行合併成單行
    %head = ( UNIX_FROM_LINE, split /^([-\w]+):\s*/m, $header );

譬如說,您若想保留所有 Received欄位資料的話【因 Received欄位通常不止一個】,這個解法便不太行了。一個完整的解法是使用收錄在 CPAN的 Mail::Header模組( MailTools 套件的一部分)。


如何解譯 CGI表格?

很多人忍不住要自己寫程式來處理這部分的工作,所以您們大概都看過一大堆其中有 $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位址?

無法度。

如果沒有寄封信到一個位址去試試看它會不會彈回來(即使是這麼做您還得面對停頓的問題),您是無法確定一個位址是否真的存在的。即使您套用 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的作者使用另一個替代的方案:用一個簡單的正規表示式,(如上頭的那個)。如果一個位址能讓這個式子對得上的話,那麼就接受這個位址。如果這個位址對不上這個式子的話,便再向使用者訊問,以確定她們填入的這個位 址正確無誤。


如何解 MIME/BASE64字串?

MIME-tools套件(可自 CPAN取得)不但可處理這個問題而且有許多其他的功能。有了這個套件,解 BASE64碼就變得像這麼容易:

    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


如何根據使用者帳戶名稱自動合成 email位址?

在支援 getpwuid【UNIX系統呼叫】、 $<這個變數,和 Sys::Hostname模組(標準 perl 發行的一部分)的系統上,您可試試這樣的做法:

    use Sys::Hostname;
    $address = sprintf('%s@%s', getpwuid($<), hostname);

有的公司對 email位址有統籌規畫,因此這麼一來您可能會合成出不被公司的 email 主機接受的位址。所以如果有這類的顧慮的話,您應該直接向 users要他們的 email 位址。 而且,並不是所有能跑 Perl的系統都像 Unix一樣,可以很容易得到這些資料。

CPAN裡的 Mail::Util模組( MailTools 套件的一部分)中有一個 mailaddress()函數,它會試著去猜 user的 email位址。這個函數使用比上面的 code聰明的方法,它會參考安裝時所得到的設定資料,但是錯誤仍可能發生。所以,再一次地,最好的方法通常是直接問 user 本人。


我的程式如何送/讀 email?

送信:CPAN 上頭的 Mail::Mailer模組( MailTools套件的一部分)只適合在 Unix 上使用,但利用到 Net::SMTP的 Mail::Internet模組則沒有這個限制。 讀信:用 CPAN 上的 Mail::Folder模組( MailFolder 套件的一部分)或是用 CPAN 上頭的 Mail::Internet模組( 也是 MailTools 套件的一部分)。

   #送信
    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;


如何找出我的主機名/網域名/IP位址?

長久以來許多 code都很草率地直接呼叫 `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系統下尚需要一有效的方法來測出機器和網域名)


如何抓新聞討論群的文章或群組名錄?

使用 Net::NNTP或 News::NNTPClient模組,兩者皆可自 CPAN下載。這些模組 讓抓群組名錄這類的差事變得這麼容易:

    perl -MNews::NNTPClient
      -e 'print News::NNTPClient->;new->list("newsgroups")'


如何抓/丟 FTP檔案?

LWP::Simple模組(可自 CPAN下載)可以抓,但不能丟檔案。 Net::FTP模組(也可自 CPAN下載)雖比較複雜,但可用來丟、也能抓檔案。


如何用 Perl做 RPC?

有一個 DCE::RPC模組正在發展階段(但尚未完成)。一旦完成後它會隨著 DCE-Perl這個套件發行(可由 CPAN 下載)。至於 ONC::RPC這樣的模組則還沒聽說有人在發展。


作者及版權事宜

Copyright (c) 1997 Tom Christiansen and Nathan Torkington.著作權所有, All rights reserved。有關使用、(轉)發行事宜,詳見 perlfaq

中譯版著作權所有:蕭百齡及兩隻老虎工作室。本中譯版遵守並使用與原文版相同 的使用條款發行。