下载回源码包以后,就3个文件:
cgic.c 函数库
capture.c 一个很简单的CGI例子,仅仅输出两行提示文字
cgictest.c 一个演示读取form表单数据的CGI例子
首先在vc6里创建一个空的win32静态库cgic,然后添加cgic.c,编译后得到cgic.lib库
创建一个空的console工程cgictest,然后添加cgictest.c,在setting|link添加cgic.lib,编译得到cgictest.exe
将cgictest.exe拷贝到事先创建好的测试网站的cgi-bin下,在iis里右击cgictest.exe浏览,就可以看到一个form表单,几乎所有的控件都包括了
最后以capture.c为例说明一下例子的源码结构:
#include "cgic.h"
int cgiMain() {
cgiWriteEnvironment("/CHANGE/THIS/PATH/capcgi.dat");
cgiHeaderContentType("text/html");
fprintf(cgiOut, "<title>Captured</title>/n");
fprintf(cgiOut, "<h1>Captured</h1>/n");
fprintf(cgiOut, "Your form submission was captured for use in/n");
fprintf(cgiOut, "debugging CGI code./n");
return 0;
}
可以看到capture.c实在是太简单了,主函数main被定义在了cgic.c里,在主函数的最后调用了cgiMain(),所有我们要开发一个自己的cgi的话,
只需要实现一个cgiMain()即可,就像capture.c那样,其他的功能就看你的需求喽.cgic和gsoap相结合就可以打造一个基于cgi的webservice的服务器端了.
原文及源码: http://www.boutell.com/cgic/
网上看到一篇中译版的,转过来附在后面
cgic: 为C语言编写CGI的C函数库
由Thomas Boutell开发
目录
CGIC介绍
怎样写CGIC应用程序
怎样产生图片在CGIC中?
CGI调试特征: 利用捕获
cgic函数参考
cgic变量参考
cgic结果编码参考
cgic快速索引
一般的UNIX系统都支持ANSIC,增加相应的库函数(和相应的h文件)就可以实现CGI。在此我向大家推荐一个用于CGI编程的ANSIC库:cgic。
cgic是用来生成基于CGI的WWW应用程序的C语言函数库,它有以下功能:
*对数据进行语法分析
*接收以GET和PSOT两种方式发送的数据
*把FORM中的不同域连接成连续的串
*为检索FORM数据而提供字符串,整数,浮点以及单项和多项选择功能
*为数字字段提供边界检测
*把CGI环境变量加载到非空的C串中
*为调试而捕捉CGI状态
*提供相对安全的系统调用功能
用一般ANSI C或C++编译器就可以编译cgic程序,不过与通常C程序不同的是,用cgic写的源码其主函数是cgiMain(),而不是通常的main()。cgic的函数库会自动把cgiMain连接到相应的main()上去。
--------------------------------------------------------------------------------
写CGIC程序
Note: 所有的cgic应用程序必须连接cgic.c.
用cgimain()替代main() 必须包含: #include"cgic.h."
基本结构cgictest.c:
int cgiMain() {
#if DEBUG
/* Load a saved CGI scenario if we're debugging */
cgiReadEnvironment("/path/to/capcgi.dat");
#endif
/* Important: we must indicate the type of document */
cgiHeaderContentType("text/html");
/* Now invoke other functions to handle each part of the form */
fprintf(cgiOut, "<HTML><HEAD>/n");
fprintf(cgiOut, "<TITLE>cgic test</TITLE></HEAD>/n"):
fprintf(cgiOut, "<BODY><H1>cgic test</H1>/n");
Name();
Address();
Hungry();
Temperature();
Frogs();
Color();
Flavors();
NonExButtons();
RadioButtons();
fprintf(cgiOut, "</BODY></HTML>/n");
/* This value will be the exit code of the program; 0
generally indicates success among Unix and DOS programs */
return 0;
}
capture
输出标头
cgiHeaderContentType()在输出文挡之前简要说明MIME内型,如 "text/html"。
cgiHeaderStatus()代替输出错误代码 cgiHeaderLocation()代替重新引导至其他页面。在一个独立的应用程序中只能有一个cgiHeader函数。
重点:在cgiHeader函数组中, cgiHeaderContentType(), 在任何向浏览器输出之前被调用. 否则将出错或浏览器不能识别。 cgiOut
接着, cgiMain() 调用不同的函数.当函数结束后,将返回0
处理输入文本
void Name() {
char name[81];
cgiFormStringNoNewlines("name", name, 81);
fprintf(cgiOut, "Name: %s<BR>/n", name);
}
这个函数的功能就是取的并显示由用户输入的name .
处理输出
Important: cgiOut通常相当于stdout
cgiFormString 确保断航
处理单一Checkboxes输入
这个Hungry() function确定用户是否选择"hungry"这个 checkbox:
void Hungry() {
if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) {
fprintf(cgiOut, "I'm Hungry!<BR>/n");
} else {
fprintf(cgiOut, "I'm Not Hungry!<BR>/n");
}
}
这个函数依靠 cgiFormCheckboxSingle() 确定单一的checkbox 被选择。 cgiFormCheckboxSingle() 接受checkbox名字的属性值,如果存在就返回 cgiFormSuccess,否则返回cgiFormNotFound 如果是多项checkboxes,就用 cgiFormCheckboxMultiple()和cgiFormStringMultiple() 函数.
处理数字输入
Temperature() 返回浮点书的值确保在特定的返回内。
void Temperature() {
double temperature;
cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6);
fprintf(cgiOut, "My temperature is %f.<BR>/n", temperature);
}
依靠cgiFormDoubleBounded()得到数据.第一个数据是返回数据中输入域的名字。最后一个值是用户没有提交时的默认值。
这个函数总是找回在特定返回内合适的值; cgiFormDoubleBounded返回的值被检查确信用户输入的资料在规定范围内, 而不是其他无效的数据。查看 cgiFormDoubleBounded() 更多的资料. 如果限度检查不理想,可以用 cgiFormDouble() 替代.
在整数输入,cgiFormInteger 和 cgiFormIntegerBounded 可以利用. 这些函数的功能类似.
处理单一选择输入
<SELECT> HTML标签被用于向用户提供几个选择. Radio buttons 和checkboxes 椰油这样的用途,大门、能够选择的数量很小时. Color()
char *colors[] = {
"Red",
"Green",
"Blue"
};
void Color() {
int colorChoice;
cgiFormSelectSingle("colors", colors, 3, &colorChoice, 0);
fprintf(cgiOut, "I am: %s<BR>/n", colors[colorChoice]);
}
这个函数确定用户选择了几个选项从<SELECT> 在表但的列表. cgiFormSelectSingle()
cgiFormSelectSingle() 总是显示合理的选项值.
radio button也可以用这个函数.另外还有 cgiFormRadio(), 也是一样的
处理多项选择的输入
NonExButtons()
char *votes[] = {
"A",
"B",
"C",
"D"
};
void NonExButtons() {
int voteChoices[4];
int i;
int result;
int invalid;
char **responses;
/* Method #1: check for valid votes. This is a good idea,
since votes for nonexistent candidates should probably
be discounted... */
fprintf(cgiOut, "Votes (method 1):<BR>/n");
result = cgiFormCheckboxMultiple("vote", votes, 4,
voteChoices, &invalid);
if (result == cgiFormNotFound) {
fprintf(cgiOut, "I hate them all!<p>/n");
} else {
fprintf(cgiOut, "My preferred candidates are:/n");
fprintf(cgiOut, "<ul>/n");
for (i=0; (i < 4); i++) {
if (voteChoices[i]) {
fprintf(cgiOut, "<li>%s/n", votes[i]);
}
}
fprintf(cgiOut, "</ul>/n");
}
参考cgiFormCheckboxMultiple(), cgiFormSelectMultiple().
cgiFormCheckboxMultiple() cgiFormCheckboxMultiple
NonExButtons() 函数在 cgictest.c:
/* Method #2: get all the names voted for and trust them.
This is good if the form will change more often
than the code and invented responses are not a danger
or can be checked in some other way. */
fprintf(cgiOut, "Votes (method 2):<BR>/n");
result = cgiFormStringMultiple("vote", &responses);
if (result == cgiFormNotFound) {
fprintf(cgiOut, "I hate them all!<p>/n");
} else {
int i = 0;
fprintf(cgiOut, "My preferred candidates are:/n");
fprintf(cgiOut, "<ul>/n");
while (responses[i]) {
fprintf(cgiOut, "<li>%s/n", responses[i]);
i++;
}
fprintf(cgiOut, "</ul>/n");
}
/* We must be sure to free the string array or a memory
leak will occur. Simply calling free() would free
the array but not the individual strings. The
function cGIStringArrayFree() does the job completely. */
cgiStringArrayFree(responses);
}
参考cgiFormStringMultiple()
cgiFormStringMultiple()
/* An array of strings; each C string is an array of characters */
char **responses;
cgiFormStringMultiple("vote", &responses);
检查CGI环境变量
将用到的变量 这里,
产生图象
#include "cgic.h"
#include "gd.h"
char *colors[] = {
"red", "green", "blue"
};
#define colorsTotal 3
int cgiMain() {
int colorChosen;
gdImagePtr im;
int r, g, b;
/* Use gd to create an image */
im = gdImageCreate(64, 64);
r = gdImageColorAllocate(im, 255, 0, 0);
g = gdImageColorAllocate(im, 0, 255, 0);
b = gdImageColorAllocate(im, 0, 0, 255);
/* Now use cgic to find out what color the user requested */
cgiFormSelectSingle("color", 3, &colorChosen, 0);
/* Now fill with the desired color */
switch(colorChosen) {
case 0:
gdImageFill(im, 32, 32, r);
break;
case 1:
gdImageFill(im, 32, 32, g);
break;
case 2:
gdImageFill(im, 32, 32, b);
break;
}
/* Now output the image. Note the content type! */
cgiHeaderContentType("image/gif");
/* Send the image to cgiOut */
gdImageGif(im, cgiOut);
/* Free the gd image */
gdImageDestroy(im);
return 0;
}
为调试而捕捉CGI状态
cgic函数参考
cgiFormResultType cgiFormString( char *name, char *result, int max)
用于从输入域中copy字符串。他将域名max-1字节中的字符copy到缓冲区result。若域不存在,则copy一个空串到result缓冲区。在此函数中所有的新行由换行符代表。
cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max)
它与cgiFormString函数相似,只是所有的CR和LF都被去掉了。
cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length)
它返回指向name的字符串的长度,并将长度放入length中。
cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray)
若同一名字有多个输入域,或域中的字符串可以动态变化,那么你可以使用本函数。它把名为name的所有输入域的值放在prtToStringArray中。
void cgiStringArrayFree(char **stringArray)
它释放了分配给stringArray的内存。
cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV)
从输入域中取出整数放入result中。
cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV)
若输入域中的整数在界限内则取出并放入result中。
cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV)
从输入域中取出浮点数放入result中。
cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV)
若输入域中的浮点数在界限内则取出并放入result中。
cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV)
取出复选框(跟在select语句之后的),把选择的名字copy到choicesText,把选择的个数copy到choicesTotal,把当前的选择copy到result。
cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid)
与cgiFormSelectSingle类似,只指向整型数组的result代表了选择的项。
cgiFormResultType cgiFormCheckboxSingle( char *name)
若复选框被选中,则函数返回cgiFormSuccess,否则返回cgiFormNotFound。
cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid)
与cgiFormCheckboxSingle类似,但它处理同一名字有多个复选框的情况。name指向复选框的名字;valuesText指向包含有每个复选框中参数的一个数组;valuesTotal指向复选框的总数;result是一个整型数组,每个复选框选中的用1代表,没选中的用0代表。
cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV)
与cgiFormCheckboxMultiple相似,只是这里是单选按钮而不是复选框。
void cgiHeaderLocation(char *redirectUrl)
重定向到redirectUrl指定的URL。
void cgiHeaderStatus(int status, char *statusMessage)
输出状态代码status和消息statusMessage。
void cgiHeaderContentType(char *mimeType)
用于告知浏览器返回的是什么类型的文档。
cgiEnvironmentResultType cgiWriteEnvironment(char *filename)
本函数把当前CGI环境写入filename文件中以便以后调试时使用
cgiEnvironmentResultType cgiReadEnvironment(char *filename)
本函数从filename文件中读取CGI环境以便用来调试。
int cgiMain()
一个程序必须要写这个函数, 这是主程序开始之处。
cgic变量参考
This section provides a reference guide to the various global variables provided by cgic for the programmer to utilize. These variables should always be used in preference to stdin, stdout, and calls to getenv() in order to ensure compatibility with the cgic CGI debugging features.
大多数的变量相当于各种CGI变量,重要的是VGIC的变量不能为空.
char *cgiServerSoftware
服务器软件名称,或者一个空的字符串 or to an empty string if unknown.
char *cgiServerName
返回服务器名称或空
char *cgiGatewayInterface
网关接口 (通常是 CGI/1.1),或空
char *cgiServERProtocol
网络协议(usually HTTP/1.0),或空
char *cgiServerPort
服务器端口(usually 80),或空
char *cgiRequestMethod
请求方式 (usually GET or POST),或空
char *cgiPathInfo
指出附加虚拟路径
char *cgiPathTranslated
指出附加虚拟路径并由服务器转为本地路径
char *cgiScriptName
调用程序的名字
char *cgiQueryString
包含 GET-method 请求或者 <ISINDEX> 标签. 这个信息不需要解吸,除非用<ISINDEX>标签通常由CGIC函数库自动解析。
char *cgiRemoteHost
从浏览器返回客户主机的名字
char *cgiRemoteAddr
从浏览器返回客户的IP地址
char *cgiAuthType
返回用户授权信息
char *cgiRemoteUser
鉴别用户 cgiAuthType.
char *cgiRemoteIdent
返回用户的名字(用户通过用户坚定协议)这个消息是不安全的,特别是Windows系统。
char *cgiContentType
返回MIME内型
char *cgiAccept
参考 cgiHeaderContentType() cgiUserAgent
char *cgiUserAgent
取的用户浏览器信息
char *cgiReferrer
指向用户访问的URL.
int cgiContentLength
表单或查询数据的字节被认为是标准的.
FILE *cgiOut
CGI输出. cgiHeader函数,象cgiHeaderContentType, 首先被用于输出mime头; 用于 fprintf() 和fwrite(). cgiOut通常相当于stdout。
FILE *cgiIn
CGI输入. 在决大部分时间你都不会需要这个函数。
cgic结果编码参考
在大量的按列中, cgic函数有计划的产生合理的结果,甚至浏览器和用户不合理时。无论如何, 有时候知道不合理的事情发生,尤其赋予一个值或定义一个范围是一个不充分的解决方案。下面的这些结果编码有助更好了解。
cgiFormSuccess
提交信息成功
cgiFormTruncated
删除部分字节.
cgiFormBadType
错误的输入信息(没有按要求)
cgiFormEmpty
提交信息为空.
cgiFormNotFound
提交信息没有找到.
cgiFormConstrained
数字属于某个特定的范围,被迫低于或高于适当范围。
cgiFormNoSuchChoice
单一选择提交的值是不被接受。通常说明表但和程序之间存在矛盾。
cgiEnvironmentIO
从CGI环境或获取的文件读或写的企图失败,报出I/O的错误。
cgiEnvironmentMemory
从CGI环境或获取的文件读或写的企图失败,报出out-of-memory的错误。
cgiEnvironmentSuccess
从CGI环境或获取的文件读或写的企图成功。
cgic快速索引
cgiAccept | cgiAuthType | cgiContentLength | cgiContentType | cgiEnvironmentIO | cgiEnvironmentMemory | cgiEnvironmentSuccess | cgiFormBadType | cgiFormCheckboxMultiple() | cgiFormCheckboxSingle() | cgiFormConstrained | cgiFormDouble() | cgiFormDoubleBounded() | cgiFormEmpty | cgiFormInteger() | cgiFormIntegerBounded() | cgiFormNoSuchChoice | cgiFormNotFound | cgiFormRadio() | cgiFormSelectMultiple() | cgiFormSelectSingle() | cgiFormString() | cgiFormStringMultiple() | cgiFormStringNoNewlines() | cgiFormStringSpaceNeeded() | cgiFormSuccess | cgiFormTruncated | cgiGatewayInterface | cgiHeaderContentType() | cgiHeaderLocation() | cgiHeaderStatus() | cgiIn | cgiMain() cgiOut | cgiPathInfo | cgiPathTranslated | cgiQueryString | cgiReadEnvironment() | cgiReferrer() | cgiRemoteAddr | cgiRemoteHost | cgiRemoteIdent | cgiRemoteUser | cgiRequestMethod | cgiScriptName | cgiServerName | cgiServerPort | cgiServerProtocol | cgiServerSoftware | cgiStringArrayFree() | cgiUserAgent | cgiWriteEnvironment()
=========================================================================
CGIC简明教程
本系列的目的是演示如何使用C语言的CGI库“CGIC”完成Web开发的各种要求。
基础知识
* 1: 使用CGIC的基本思路
* 2: 获取Get请求字符串
* 3: 反转义
* 4: 获取请求中的参数值
进阶训练
* 用CGIC实现文件上传
CGIC简明教程1:使用CGIC的基本思路
C语言编程是一项复杂且容易出错的工作,所以在完成复杂任务时,一定要选择合适的库。对于用C语言编写CGI程序则更是如此。
CGIC是非常优秀的C语言CGI库函数。 其下载地址为:www.boutell.com/cgic/#obtain,现在的版本号是2.05。
本站从今天开始,将逐步介绍如何使用CGIC完成各种操作,也可以说是一个Tutorial。
(注:本系列涉及的编程环境都是Linux,Windows用户需要对用到的操作系统命令稍作修改)
本文纲要 :
CGIC的安装、测试安装、使用CGIC的基本思路;
1) CGIC的下载安装
从上面提供的官方网址下载了CGIC库之后,解开压缩包,里面有大约10个文件,有用的是:
cgic.h:头文件;
cgic.c:CGIC的源代码文件;
cgictest.c:CGIC库的作者提供的一个CGI程序例子;
capture.c:用于调试CGI程序的工具;
Makefile:安装CGIC的脚本文件;
可以看到,整个库实际上就是cgic.c一个文件,可以说是非常的精炼。
我们可以把CGIC安装为操作系统的一个动态链接库,这样我们每次编译的时候,就不需要有cgic.c这个源文件了。
但是由于需要(以后将会看到),我们将修改cgic.c代码,所以我们不把它安装进系统。每次编译的时候,只要把cgic.c和cgic.h放到当前文件夹就好了。
2) 测试安装
在开始编写你自己的CGI程序之前,一定要先走通他的例子程序,免得后来程序出错的时候还不知道是配置有问题,还是你的程序代码有问题。
我们用他自带cgictest.c来实现自己的第一个C语言CGI程序。
你可以新建一个工作目录,用于存放你的CGI程序源代码,把cgic.h, cgic.c, cgictest.c三个文件拷贝到这个目录,然后建立一个Makefile文件,其内容为:
1. test.cgi:cgictest.c cgic.h cgic.c
2. gcc -wall cgictest.c cgic.c -o test.cgi
需要提醒的是,第二行开头一定是一个tab键(且仅有一个),不能使用空格。
保存好Makefile的内容之后,执行make命令:
make
我们看到,当前目录下应该多了一个test.cgi文件。
在你的网站根目录下建立一个cgi-bin目录(当然名字可以任意取,但作为习惯,一般叫做cgi-bin),然后在Apache的配置文件里赋予其执行CGI代码的权限,权限修改完之后要重启Apache。完成之后,把刚才生成的test.cgi放到cgi-bin目录中。此时我们可以在浏览器中输入以下地址进行访问:
http://127.0.0.1/cgi-bin/test.cgi
如果正常的话,应该看到一个网页被展示出来。这样,第一个C语言的CGI程序就运行起来了。
如果浏览器报错,那么多半是配置Apache的时候有些操作没有正确完成。
3) 使用CGIC的基本思路
从cgic.c的代码可以看出,它定义了main函数,而在cgictest.c中定义了一个cgiMain函数。也就是说,对于使用CGIC编写的CGI程序,都是从cgic.c中的代码进入,在库函数完成了一系列必要的操作(比如解析参数、获取系统环境变量)之后,它才会调用你的代码(从你定义的cgiMain进入)。
另外一点就是,cgi程序输出HTML页面的方式都是使用printf把页面一行一行地打印出来,比如cgictest.c中的这一段代码:
fprintf(cgiOut, "<textarea NAME=/"address/" ROWS=4 COLS=40>/n");
fprintf(cgiOut, "Default contents go here. /n");
fprintf(cgiOut, "</textarea>/n");
上面这段代码的运行结果就是在页面上输出一个textarea。 第一个参数cgiOut实际上就是stdin,所以我们可以直接使用printf,而不必使用fprintf。不过在调试的时候会用到fprintf来重定向输出。
这种方式与Java Servlet非常类似,Servlet也是通过调用打印语句System.out.println(…)来输出一个页面。(不过后来Java推出了JSP来克服这种不便。)
但是与Servlet不同的地方在于,使用C语言的我们还要自己输出HTML头部(声明文档类型):
cgiHeaderContentType("text/html");
这个语句的调用一定要在所有printf语句之前。而这个语句执行的任务实际上就是:
void cgiHeaderContentType(char *mimeType) {
fprintf(cgiOut, "Content-type: %s/r/n/r/n", mimeType);
}
这个语句告诉浏览器,这次传来的数据是什么类型,是一个HTML文档,还是一个bin文件… 如果是个HTML文档,就通过浏览器窗口显示,如果是一个bin(二进制)文件,则打开下载窗口,让用户选择是否保存文件以及保存文件的路径。
理解了这几点之后,你就可以编写自己的CGIC程序了。新建一个文件test.c试试:
下载: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5. int cgiMain() {
6. cgiHeaderContentType("text/html");
7. fprintf(cgiOut, "<HTML><HEAD>/n");
8. fprintf(cgiOut, "<TITLE>My First CGI</TITLE></HEAD>/n");
9. fprintf(cgiOut, "<BODY><H1>Hello CGIC</H1></BODY>/n");
10. fprintf(cgiOut, "</HTML>/n");
11. return 0;
12. }
把Makefile文件中的cgitest.c全部换称test.c,保存,再执行make命令即可。
此时通过浏览器访问,会在页面上看到一个大大的“Hello CGIC”。
CGIC简明教程2:获取Get请求字符串
Get请求就是我们在浏览器地址栏输入URL时发送请求的方式,或者我们在HTML中定义一个表单(form)时,把action属性设为“Get”时的工作方式;
Get请求字符串就是跟在URL后面以问号“?”开始的字符串,但不包括问号。比如这样的一个请求:
http://127.0.0.1/cgi-bin/out.cgi?ThisIsTheGetString
在上面这个URL中,“ThisIsTheGetString”就是Get请求字符串。
在进入我们自己编写的cgi代码之前,CGIC库已经事先把这个字符串取到了,我们可以在程序中直接获得,要做的仅仅是在你编写的cgiMain方法前面加入以下声明:
extern char *cgiQueryString;
现在给出一个简单的例子,这个例子跟上一篇的测试程序非常相似,只不过程序的输出是使用者输入的Get请求字符串。
下载: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. extern char *cgiQueryString;
7. int cgiMain() {
8. cgiHeaderContentType("text/html");
9. fprintf(cgiOut, "<HTML><HEAD>/n");
10. fprintf(cgiOut, "<TITLE>My CGIC</TITLE></HEAD>/n");
11. fprintf(cgiOut, "<BODY>");
12. fprintf(cgiOut, "<H1>%s</H1>",cgiQueryString);
13. fprintf(cgiOut, "</BODY>/n");
14. fprintf(cgiOut, "</HTML>/n");
15. return 0;
16. }
假设把这个程序编译成out.cgi(编译方法参见上一篇),并部署到Web服务器的cgi-bin目录下,当用户在浏览器地址栏输入本文开头给出的URL字符串时,浏览器页面上会显示:
ThisIsTheGetString
我们也可以编写一个用于测试的HTML页面:
下载: test.html
1. <html>
2. <head>
3. <title>Test</title>
4. </head>
5. <body>
6. <form action="cgi-bin/out.cgi" method="get">
7. <input type="text" name="theText">
8. <input type="submit" value="Continue →">
9. </form>
10. </body>
11. </html>
文件的部署结构应该为:
|test.html
|—-cgi-bin/out.cgi
大家可以试试,通过浏览器访问http://127.0.0.1/test.html,在文本框内输入一些字符,并点击提交按钮,然后就可以看到cgi程序的执行结果:把在文本框输入的字符原样显示在浏览器上。
CGIC简明教程3:反转义
浏览器在发送Get请求时,会把请求字符串进行转义操作(英文术语为: escape); 比如,我们在地址栏输入(注意最后”it’s me”中的空格):
http://localhost/~Jack/cgi-bin/out.cgi?it's me
浏览器会把它转义为:
http://localhost/~Jack/cgi-bin/out.cgi?it's%20me
在上一篇最后给出的例子中,如果在文本框内输入
it's me
你会发现,浏览器最终发送的请求为
http://localhost/~Jack/cgi-bin/out.cgi?theText=it%27s+me
通过CGIC,我们可以把这些被转义后的字符还原为我们本来的输入,这个过程就叫“反转义” (Unescape)。
不过这个过程有点像hack他的代码。
整个过程分三个步骤:
1)打开cgic.c,找到这一行语句:
static cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
注意,我们要找的只是这个函数声明,不是函数定义;
2)在这个函数声明语句的上方,你会看到一个结构体定义:
1. typedef enum {
2. cgiUnescapeSuccess,
3. cgiUnescapeMemory
4. } cgiUnescapeResultType;
把这几行语句复制到cgic.h文件中,并在这里把它注释掉;
同时还要删除在第一步中找到的函数声明语句中的“static”关键字。
3)我们现在就可以使用反转义函数cgiUnescapeChars了:
在你自己的代码(按照惯例,还是test.c)中,加入以下声明语句即可
extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
接下来我们给出一段完整的test.c代码
下载: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. extern char *cgiQueryString;
7. extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
8. int cgiMain() {
9. char * buffer;
10. cgiHeaderContentType("text/html");
11. fprintf(cgiOut, "<HTML><HEAD>/n");
12. fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>/n");
13. fprintf(cgiOut, "<BODY>");
14. cgiUnescapeChars(&buffer, cgiQueryString, strlen(cgiQueryString));
15. fprintf(cgiOut, "<H1>%s</H1>",buffer);
16. fprintf(cgiOut, "</BODY>/n");
17. fprintf(cgiOut, "</HTML>/n");
18. free(buffer);
19. return 0;
20. }
值得注意的是,buffer的存储空间是cgiUnescapeChars帮你分配的,但最后要由你自己来释放(free),这一点千万不可忘记。
下面你可以结合上一篇给出的测试用html代码试试该cgi程序的运行结果,也可以直接在浏览器地址栏输入一些带有特殊符号的字符串。
最后讲一下为什么不得不用这种hacker的方式来完成该任务,而CGIC不显式提供?
CGIC的出发点是,我们平时只需要解析请求中的键值对,比如:”?q=nice&client=IE”,当我们在服务端查询“q”的值时,我们就能得到“nice”。CGIC有一族函数帮助我们完成这个任务,比如cgiFormString(以后会讲到)。在解析这种请求格式的时候,如果我们提供的参数值含有被转义的字符,那么CGIC就会在内部调用cgiUnescapeChars完成反转义。
但是,有时候我们会发送非常复杂的Get请求字符串,但并不是“键-值”对的格式。这就需要直接使用cgiUnescapeChars进行反转义了。
例如:假设我们有个服务端cgi程序chat.cgi,这是个网络聊天机器人(也许你可以开发自己的Web版MSN机器人、QQ机器人)。如果我们发送如下请求:
http://127.0.0.1/cgi-bin/chat.cgi?"this is a cgi user"
那么chat.cgi就会把“this is a cgi user”当做你对它说的话,经过处理,它会回复一段语句。为了方便,我们并没有写成“键-值”对的形式。这个时候被我们hack的cgiUnescapeChars就能派上用场了。
CGIC简明教程4:获取请求中的参数值
我们在提交一个表单(form)时,怎样把表单内的值提取出来呢?
比如下面这个表单:
<form action="cgi-bin/out.cgi" method="POST">
<input type="text" name="name" />
<input type="text" name="number" />
<input type="submit" value="Submit" />
</form>
当out.cgi收到请求时,需要把输入框”name”和输入框”number”内的值提取出来。而且不管form中的action是GET还是POST,都要有效。
下面给出示例代码:
下载: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. int cgiMain() {
7. char name[241];
8. char number[241];
9. cgiHeaderContentType("text/html");
10. fprintf(cgiOut, "<HTML><HEAD>/n");
11. fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>/n");
12. fprintf(cgiOut, "<BODY>");
13. cgiFormString("name", name, 241);
14. cgiFormString("number", number, 241);
15. fprintf(cgiOut, "<H1>%s</H1>",name);
16. fprintf(cgiOut, "<H1>%s</H1>",number);
17. fprintf(cgiOut, "</BODY>/n");
18. fprintf(cgiOut, "</HTML>/n");
19. return 0;
20. }
从上面的代码可以看出,第13行和第14行获取了输入框的值。
获取输入参数值在CGIC中其实有一族函数,cgiFormString是其中最常用的一个。
cgiFormStringNoNewlines用来去掉换行符(如果用户是在一个TextArea里输入字符的话);
cgiFormStringSpaceNeeded用于测试输入值的长度,可以以此为依据,然后按需精确分配缓冲区。
用C语言库(CGIC)编写CGI,实现文件上传
用C语言编写cgi程序的话,多半会用到CGIC。 这是个非常流行的库,遇到文件上传之类的应用更是离不开它。官方页面及下载地址为:www.boutell.com/cgic/#obtain
不少网站都有文件上传的功能,本文展示如何用CGIC库编写文件上传的服务端程序,最后给出一段简单的HTML代码,供大家测试使用 。
下载: upload.c
1. #include<stdio.h>
2. #include<string.h>
3. #include<unistd.h>
4. #include<fcntl.h>
5. #include<sys/stat.h>
6. #include"cgic.h"
7. #define BufferLen 1024
8. int cgiMain(void){
9. cgiFilePtr file;
10. int targetFile;
11. mode_t mode;
12. char name[128];
13. char fileNameOnServer[64];
14. char contentType[1024];
15. char buffer[BufferLen];
16. char *tmpStr=NULL;
17. int size;
18. int got,t;
19. cgiHeaderContentType("text/html");
20. //取得html页面中file元素的值,应该是文件在客户机上的路径名
21. if (cgiFormFileName("file", name, sizeof(name)) !=cgiFormSuccess) {
22. fprintf(stderr,"could not retrieve filename/n");
23. goto FAIL;
24. }
25. cgiFormFileSize("file", &size);
26. //取得文件类型,不过本例中并未使用
27. cgiFormFileContentType("file", contentType, sizeof(contentType));
28. //目前文件存在于系统临时文件夹中,通常为/tmp,通过该命令打开临时文件。临时文件的名字与用户文件的名字不同,所以不能通过路径/tmp/userfilename的方式获得文件
29. if (cgiFormFileOpen("file", &file) != cgiFormSuccess) {
30. fprintf(stderr,"could not open the file/n");
31. goto FAIL;
32. }
33. t=-1;
34. //从路径名解析出用户文件名
35. while(1){
36. tmpStr=strstr(name+t+1,"//");
37. if(NULL==tmpStr)
38. tmpStr=strstr(name+t+1,"/");//if "//" is not path separator, try "/"
39. if(NULL!=tmpStr)
40. t=(int)(tmpStr-name);
41. else
42. break;
43. }
44. strcpy(fileNameOnServer,name+t+1);
45. mode=S_IRWXU|S_IRGRP|S_IROTH;
46. //在当前目录下建立新的文件,第一个参数实际上是路径名,此处的含义是在cgi程序所在的目录(当前目录))建立新文件
47. targetFile=open(fileNameOnServer,O_RDWR|O_CREAT|O_TRUNC|O_APPEND,mode);
48. if(targetFile<0){
49. fprintf(stderr,"could not create the new file,%s/n",fileNameOnServer);
50. goto FAIL;
51. }
52. //从系统临时文件中读出文件内容,并放到刚创建的目标文件中
53. while (cgiFormFileRead(file, buffer, BufferLen, &got) ==cgiFormSuccess){
54. if(got>0)
55. write(targetFile,buffer,got);
56. }
57. cgiFormFileClose(file);
58. close(targetFile);
59. goto END;
60. FAIL:
61. fprintf(stderr,"Failed to upload");
62. return 1;
63. END:
64. printf("File /"%s/" has been uploaded",fileNameOnServer);
65. return 0;
66. }
假设该文件存储为upload.c,则使用如下命令编辑:
gcc -Wall upload.c cgic.c -o upload.cgi
编译完成后把upload.cgi复制到你部署cgi程序的目录(通常命名为cgi-bin)。
正式部署时,请务必修改用open创建新文件那一行代码。把open的第一个参数设置为目标文件在服务器上存储的绝对路径,或者相对于cgi程序的相对路径。本例中,出于简单考虑,在cgi程序所在目录下创建新文件。
测试用HTML代码:
下载: upload.html
1. <form target="_blank" method="post" action="cgi-bin/upload.cgi">
2. <input name="file" type="file" /> <input name="submit" type="submit" />
3. </form>
最后的文件目录结构为
/MyWebRoot
|—/upload.html
|—/cgi-bin
|——/upload.cgi
当然,你必须配置能够cgi-bin,并且程序要有权限在cgi-bin目录下创建文件(因为此例把文件上传到cgi-bin目录下)。
那么如何控制上传文件的大小呢?因为你有时会不允许用户上传太大的文件。
通过分析cgic.c的源代码,我们发现它定义了一个变量cgiContentLength,表示请求的长度。但我们需要首先判断这是一个上传文件的请求,然后才能根据cgiContentLength来检查用户是否要上传一个太大的文件。
cgic.c的main函数中进行了一系列if-else判断来检查请求的类型,首先确定这是一个post请求,然后确定数据的编码方式为 “multipart/form-data”,这个判断通过之后,就要开始准备接收数据了。所以我们要在接收数据开始之前使用 cgiContentLength判断大小,如果超过标准,就立即返回,不允许继续操作。
下面贴出修改后代码片段(直接修改cgic.c的源代码即可):
1. else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) {
2. #ifdef CGICDEBUG
3. CGICDEBUGSTART
4. fprintf(dout, "Calling PostMultipartInput/n");
5. CGICDEBUGEND
6. #endif /* CGICDEBUG */
7. //我的代码
8. //UpSize:文件长度上限值,以byte为单位,UpSize是一个int变量,因为cgiContentLength的类型为int
9. if(cgiContentLength>UpSize){
10. cgiHeaderContentType("text/html");
11. printf("File too large!/n");
12. cgiFreeResources();
13. return -1;
14. }
15. //我的代码结束
16. if (cgiParsePostMultipartInput() != cgiParseSuccess) {
17. #ifdef CGICDEBUG
18. CGICDEBUGSTART
19. fprintf(dout, "PostMultipartInput failed/n");
20. CGICDEBUGEND
21. #endif /* CGICDEBUG */
22. cgiFreeResources();
23. return -1;
24. }
25. #ifdef CGICDEBUG
26. CGICDEBUGSTART
27. fprintf(dout, "PostMultipartInput succeeded/n");
28. CGICDEBUGEND
29. #endif /* CGICDEBUG */
30. }
31. }
变量UpSize表示文件大小的上限。在cgic.c的main中找到相关代码,并修改成上面这样即可。你可以在cgic.c中定义UpSize,也可以在刚才完成的upload.c中定义,然后在cgic.c中用extern方式引用。