综述
Boa 作为一种轻巧实用的 WEB 服务器广泛应用于嵌入式设备上,
但 Boa 对实现动态网页的 CGI 的支持上仍存在一些缺陷,
本文描述了 Boa 对 CGI 的 Status/Location 的支持的缺陷及其修正方法.
版本: 所有版本 (0.94.13)
缺陷: BOA 解析 CGI 应答头时不能完整处理 Status 和 Location
缺陷描述:
CGI/1.1 标准规定, CGI 脚本可以通过 Status 设置 HTTP 应答状态(如, Status: 500 Internal Error) 和
Location 进行地址重定向 (如, Location: www.xxx.com), 而不管它们在应答头中的位置.
Boa 支持 Stauts 和 Location 两种应答头, 但它的实现仅能正确处理 Stauts 和 Location 在应答第一行的
CGI 应答. 这将给 CGI 程序的移植带来很多不便, 进而影响 Boa 作为Web Server 的功能的发挥.
影响功能:
ASP/PHP/JSP/Perl/... 等的 header, redirect, ... 等都会应用到 Stauts/Location 进行设置应答状态和
地址重定向. Boa 的该实现将影响 CGI 脚本正常功能的使用.
缺陷功能对比(对Status/Location的支持程序):
Apache 1.3.x/2.x IIS 4.x/5.x/6.X Boa 0.9x thttpd mini-httpd
完全支持 完全支持 * 部分支持 完全支持 完全支持
缺陷分析
缺陷分析 CGI 应用程序进行应答时, 可以 HTTP 头进行有限的控制. 如,设置客户端不缓存页面可用下面的 C 脚本, HTTP/1.0: printf("Pragma: no-cache/n"); 或 HTTP/1.1: printf("Cache-Control: no-cache; no-store/n"); 如果, 同时还需要告诉浏览器进行设置 Cookie 和控制相应状态(200 OK) 或地址重定向, 那么就必须输出多行 http 头控制语句, CGI 支持两个解析头 "Status: " 和 "Loction: ", 即协议规定, Web 服务器支持解析头时能使用 "Status: " 进行应答状态控制, 使用 "Location: " 进行地址重定向, 并为应答添加状态头 "HTTP/1.0 302 Moved Temporarily/n" 或 "HTTP/1.1 302 Found/n". 而不管它们在 CGI 应答头的什么位置. 分析 Boa Source Code: cgi_header.c Line 82-136 容易发现, Boa 只解析 CGI 应答的第一行, 是否为 "Status: ", "Location: ", 如下所示
|
修正方法
CGI 应答头包括多行, 我们必须对其进行逐行分析, 并作出正确的应答.
下面是修改好的源程序, 即将原来的 82-136 (即相当下文#else, #endif内部分) 替换成如下代码:
- #if 1
- while(1) {
- int len;
- char * pnext = NULL;
- char * ptmp = NULL;
- /* not find HTTP header tailer */
- if (NULL == (pnext=strchr(buf, '/n'))) /* has no '/n' */
- break;
- /* the length of this line,
- * include '/n'
- */
- len = pnext - buf + 1;
- if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
- /* not the first one
- * exchange this line to the first line
- */
- if (buf != req->header_line)
- {
- if (NULL == (ptmp=(char *)malloc(len)))
- {
- log_error_time();
- perror("malloc");
- send_r_error(req);
- return 0;
- }
- /* move Status: to line header */
- memcpy(ptmp, buf, len);
- memmove(req->header_line+len, req->header_line, buf-req->header_line);
- memcpy(req->header_line, ptmp, len);
- free(ptmp);
- }
- /* force pointer header */
- buf = req->header_line;
- #ifdef FASCIST_LOGGING
- log_error_time();
- fprintf(stderr, "%s:%d - found Location header /"%s/"/n",
- __FILE__, __LINE__, buf + 10);
- #endif
- if (buf[10] == '/') { /* virtual path */
- log_error_time();
- fprintf(stderr,
- "server does not support internal redirection: " /
- "/"%s/"/n", buf + 10);
- send_r_bad_request(req);
- /*
- * We (I, Jon) have declined to support absolute-path parsing
- * because I see it as a major security hole.
- * Location: /etc/passwd or Location: /etc/shadow is not funny.
- *
- * Also, the below code is borked.
- * request_uri could contain /cgi-bin/bob/extra_path
- */
- /*
- strcpy(req->request_uri, buf + 10);
- return internal_redirect(req);
- */
- } else { /* URL */
- char *c2;
- c2 = strchr(buf + 10, '/n');
- /* c2 cannot ever equal NULL here because we already have found one */
- --c2;
- while (*c2 == '/r')
- --c2;
- ++c2;
- /* c2 now points to a '/r' or the '/n' */
- *c2++ = '/0'; /* end header */
- /* first next header, or is at req->header_end */
- while ((*c2 == '/n' || *c2 == '/r') && c2 < req->header_end)
- ++c2;
- if (c2 == req->header_end)
- send_r_moved_temp(req, buf + 10, "");
- else
- send_r_moved_temp(req, buf + 10, c2);
- }
- req->status = DONE;
- return 1;
- } else if (!strncasecmp(buf, "Status: ", 8)) {
- /* not the first one
- * exchange this line to the first line
- */
- if (buf != req->header_line)
- {
- if (NULL == (ptmp=(char *)malloc(len)))
- {
- log_error_time();
- perror("malloc");
- send_r_error(req);
- return 0;
- }
- /* move Status: to line header */
- memcpy(ptmp, buf, len);
- memmove(req->header_line+len, req->header_line, buf-req->header_line);
- memcpy(req->header_line, ptmp, len);
- free(ptmp);
- }
- req->header_line--;
- memcpy(req->header_line, "HTTP/1.0 ", 9);
- return 1;
- }
- /* pointer to next line */
- buf = pnext + 1;
- /* reach the end of HTTP header */
- if ('/0' == buf[0] || '/n' == buf[0] || '/r' == buf[0])
- break;
- }
- if (1) { /* always done */
- #else
- if (!strncasecmp(buf, "Status: ", 8)) {
- req->header_line--;
- memcpy(req->header_line, "HTTP/1.0 ", 9);
- } else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
- #ifdef FASCIST_LOGGING
- log_error_time();
- fprintf(stderr, "%s:%d - found Location header /"%s/"/n",
- __FILE__, __LINE__, buf + 10);
- #endif
- if (buf[10] == '/') { /* virtual path */
- log_error_time();
- fprintf(stderr,
- "server does not support internal redirection: " /
- "/"%s/"/n", buf + 10);
- send_r_bad_request(req);
- /*
- * We (I, Jon) have declined to support absolute-path parsing
- * because I see it as a major security hole.
- * Location: /etc/passwd or Location: /etc/shadow is not funny.
- *
- * Also, the below code is borked.
- * request_uri could contain /cgi-bin/bob/extra_path
- */
- /*
- strcpy(req->request_uri, buf + 10);
- return internal_redirect(req);
- */
- } else { /* URL */
- char *c2;
- c2 = strchr(buf + 10, '/n');
- /* c2 cannot ever equal NULL here because we already have found one */
- --c2;
- while (*c2 == '/r')
- --c2;
- ++c2;
- /* c2 now points to a '/r' or the '/n' */
- *c2++ = '/0'; /* end header */
- /* first next header, or is at req->header_end */
- while ((*c2 == '/n' || *c2 == '/r') && c2 < req->header_end)
- ++c2;
- if (c2 == req->header_end)
- send_r_moved_temp(req, buf + 10, "");
- else
- send_r_moved_temp(req, buf + 10, c2);
- }
- req->status = DONE;
- return 1;
- } else { /* not location and not status */
- #endif