综述
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: ", 如下所示
- 23
- 24 int process_cgi_header(request * req)
- 25 {
- 26 char *buf;
- 27 char *c;
- 28
- 29 if (req->cgi_status != CGI_DONE)
- 30 req->cgi_status = CGI_BUFFER;
- 31
- 32 buf = req->header_line;
- 33
- 34 c = strstr(buf, "\n\r\n");
- 35 if (c == NULL) {
- 36 c = strstr(buf, "\n\n");
- 37 if (c == NULL) {
- 38 log_error_time();
- 39 fputs("cgi_header: unable to find LFLF\n", stderr);
- 40 #ifdef FASCIST_LOGGING
- 41 log_error_time();
- 42 fprintf(stderr, "\"%s\"\n", buf);
- 43 #endif
- 44 send_r_bad_gateway(req);
- 45 return 0;
- 46 }
- 47 }
- 48 if (req->simple) {
- 49 if (*(c + 1) == '\r')
- 50 req->header_line = c + 2;
- 51 else
- 52 req->header_line = c + 1;
- 53 return 1;
- 54 }
- 55 if (!strncasecmp(buf, "Status: ", 8)) {
- 56 req->header_line--;
- 57 memcpy(req->header_line, "HTTP/1.0 ", 9);
- 58 } else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
- 59 #ifdef FASCIST_LOGGING
- 60
- 61 log_error_time();
- 62 fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
- 63 __FILE__, __LINE__, buf + 10);
- 64 #endif
- 65
- 66
- 67 if (buf[10] == '/') { /* virtual path */
- 68 log_error_time();
- 69 fprintf(stderr,
- 70 "server does not support internal redirection: " \
- 71 "\"%s\"\n", buf + 10);
- 72 send_r_bad_request(req);
- 73
- 74 /*
- 75 * We (I, Jon) have declined to support absolute-path parsing
- 76 * because I see it as a major security hole.
- 77 * Location: /etc/passwd or Location: /etc/shadow is not funny.
- 78 *
- 79 * Also, the below code is borked.
- 80 * request_uri could contain /cgi-bin/bob/extra_path
- 81 */
- 82
- 83 /*
- 84 strcpy(req->request_uri, buf + 10);
- 85 return internal_redirect(req);
- 86 */
- 87 } else { /* URL */
- 88 char *c2;
- 89 c2 = strchr(buf + 10, '\n');
- 90 /* c2 cannot ever equal NULL here because we already have found one */
- 91
- 92 --c2;
- 93 while (*c2 == '\r')
- 94 --c2;
- 95 ++c2;
- 96 /* c2 now points to a '\r' or the '\n' */
- 97 *c2++ = '\0'; /* end header */
- 98
- 99 /* first next header, or is at req->header_end */
- 100 while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
- 101 ++c2;
- 102 if (c2 == req->header_end)
- 103 send_r_moved_temp(req, buf + 10, "");
- 104 else
- 105 send_r_moved_temp(req, buf + 10, c2);
- 106 }
- 107 req->status = DONE;
- 108 return 1;
- 109 } else { /* not location and not status */
- 110 char *dest;
- 111 int howmuch;
- 112 send_r_request_ok(req); /* does not terminate */
- 113 /* got to do special things because
- 114 a) we have a single buffer divided into 2 pieces
- 115 b) we need to merge those pieces
- 116 Easiest way is to memmove the cgi data backward until
- 117 it touches the buffered data, then reset the cgi data pointers
- 118 */
- 119 dest = req->buffer + req->buffer_end;
- 120 if (req->method == M_HEAD) {
- 121 if (*(c + 1) == '\r')
- 122 req->header_end = c + 2;
- 123 else
- 124 req->header_end = c + 1;
- 125 req->cgi_status = CGI_DONE;
- 126 }
- 127 howmuch = req->header_end - req->header_line;
- 128
- 129 if (dest + howmuch > req->buffer + BUFFER_SIZE) {
- 130 /* big problem */
- 131 log_error_time();
- 130 fprintf(stderr, "Too much data to move! Aborting! %s %d\n",
- 131 __FILE__, __LINE__);
- 132 /* reset buffer pointers because we already called
- 133 send_r_request_ok… */
- 134 req->buffer_start = req->buffer_end = 0;
- 135 send_r_error(req);
- 136 return 0;
- 137 }
- 138 memmove(dest, req->header_line, howmuch);
- 139 req->buffer_end += howmuch;
- 140 req->header_line = req->buffer + req->buffer_end;
- 141 req->header_end = req->header_line;
- 142 req_flush(req);
- 143 if (req->method == M_HEAD)
- 144 return 0;
- 145 }
- 146 return 1;
- 147 }
- 148
- 149
修正方法
CGI 应答头包括多行, 我们必须对其进行逐行分析, 并作出正确的应答.
下面是修改好的源程序, 即将原来的 82-136 (即相当下文#else, #endif内部分) 替换成如下代码:
转载连接:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=824840