diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2007-09-23 13:56:57 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2007-09-23 13:56:57 +0000 |
commit | 32a471e4db43b5c1115084d02d8cf54db2098a72 (patch) | |
tree | 657c3599d669011871edce65d7f99564f5e9aafa | |
parent | 9a4e08eaa8e77809e276e98c98e7d6bc485fccff (diff) | |
download | busybox-w32-32a471e4db43b5c1115084d02d8cf54db2098a72.tar.gz busybox-w32-32a471e4db43b5c1115084d02d8cf54db2098a72.tar.bz2 busybox-w32-32a471e4db43b5c1115084d02d8cf54db2098a72.zip |
httpd: simplify CGI i/o loop. -200 bytes.
-rw-r--r-- | networking/httpd.c | 351 |
1 files changed, 183 insertions, 168 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index 057416040..33f083189 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
@@ -104,12 +104,15 @@ | |||
104 | //#define DEBUG 1 | 104 | //#define DEBUG 1 |
105 | #define DEBUG 0 | 105 | #define DEBUG 0 |
106 | 106 | ||
107 | #define IOBUF_SIZE 8192 /* IO buffer */ | ||
108 | |||
107 | /* amount of buffering in a pipe */ | 109 | /* amount of buffering in a pipe */ |
108 | #ifndef PIPE_BUF | 110 | #ifndef PIPE_BUF |
109 | # define PIPE_BUF 4096 | 111 | # define PIPE_BUF 4096 |
110 | #endif | 112 | #endif |
111 | 113 | #if PIPE_BUF >= IOBUF_SIZE | |
112 | #define IOBUF_SIZE 8192 /* IO buffer */ | 114 | # error "PIPE_BUF >= IOBUF_SIZE" |
115 | #endif | ||
113 | 116 | ||
114 | #define HEADER_READ_TIMEOUT 60 | 117 | #define HEADER_READ_TIMEOUT 60 |
115 | 118 | ||
@@ -1029,6 +1032,173 @@ static int get_line(void) | |||
1029 | } | 1032 | } |
1030 | 1033 | ||
1031 | #if ENABLE_FEATURE_HTTPD_CGI | 1034 | #if ENABLE_FEATURE_HTTPD_CGI |
1035 | |||
1036 | /* gcc 4.2.1 fares better with NOINLINE */ | ||
1037 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) ATTRIBUTE_NORETURN; | ||
1038 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) | ||
1039 | { | ||
1040 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ | ||
1041 | struct pollfd pfd[3]; | ||
1042 | int out_cnt; /* we buffer a bit of initial CGI output */ | ||
1043 | int count; | ||
1044 | |||
1045 | /* iobuf is used for CGI -> network data, | ||
1046 | * hdr_buf is for network -> CGI data (POSTDATA) */ | ||
1047 | |||
1048 | /* If CGI dies, we still want to correctly finish reading its output | ||
1049 | * and send it to the peer. So please no SIGPIPEs! */ | ||
1050 | signal(SIGPIPE, SIG_IGN); | ||
1051 | |||
1052 | /* NB: breaking out of this loop jumps to log_and_exit() */ | ||
1053 | out_cnt = 0; | ||
1054 | while (1) { | ||
1055 | memset(pfd, 0, sizeof(pfd)); | ||
1056 | |||
1057 | pfd[FROM_CGI].fd = fromCgi_rd; | ||
1058 | pfd[FROM_CGI].events = POLLIN; | ||
1059 | |||
1060 | if (toCgi_wr) { | ||
1061 | pfd[TO_CGI].fd = toCgi_wr; | ||
1062 | if (hdr_cnt > 0) { | ||
1063 | pfd[TO_CGI].events = POLLOUT; | ||
1064 | } else if (post_len > 0) { | ||
1065 | pfd[0].events = POLLIN; | ||
1066 | } else { | ||
1067 | /* post_len <= 0 && hdr_cnt <= 0: | ||
1068 | * no more POST data to CGI, | ||
1069 | * let CGI see EOF on CGI's stdin */ | ||
1070 | close(toCgi_wr); | ||
1071 | toCgi_wr = 0; | ||
1072 | } | ||
1073 | } | ||
1074 | |||
1075 | /* Now wait on the set of sockets */ | ||
1076 | count = poll(pfd, 3, -1); | ||
1077 | if (count <= 0) { | ||
1078 | #if 0 | ||
1079 | if (errno == EINTR) | ||
1080 | continue; | ||
1081 | #endif | ||
1082 | #if 0 | ||
1083 | if (waitpid(pid, &status, WNOHANG) <= 0) { | ||
1084 | /* Weird. CGI didn't exit and no fd's | ||
1085 | * are ready, yet poll returned?! */ | ||
1086 | continue; | ||
1087 | } | ||
1088 | if (DEBUG && WIFEXITED(status)) | ||
1089 | bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status)); | ||
1090 | if (DEBUG && WIFSIGNALED(status)) | ||
1091 | bb_error_msg("CGI killed, signal=%d", WTERMSIG(status)); | ||
1092 | #endif | ||
1093 | break; | ||
1094 | } | ||
1095 | |||
1096 | if (pfd[TO_CGI].revents) { | ||
1097 | /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */ | ||
1098 | /* Have data from peer and can write to CGI */ | ||
1099 | count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt); | ||
1100 | /* Doesn't happen, we dont use nonblocking IO here | ||
1101 | *if (count < 0 && errno == EAGAIN) { | ||
1102 | * ... | ||
1103 | *} else */ | ||
1104 | if (count > 0) { | ||
1105 | hdr_ptr += count; | ||
1106 | hdr_cnt -= count; | ||
1107 | } else { | ||
1108 | /* EOF/broken pipe to CGI, stop piping POST data */ | ||
1109 | hdr_cnt = post_len = 0; | ||
1110 | } | ||
1111 | } | ||
1112 | |||
1113 | if (pfd[0].revents) { | ||
1114 | /* post_len > 0 && hdr_cnt == 0 here */ | ||
1115 | /* We expect data, prev data portion is eaten by CGI | ||
1116 | * and there *is* data to read from the peer | ||
1117 | * (POSTDATA) */ | ||
1118 | //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len; | ||
1119 | //count = safe_read(0, hdr_buf, count); | ||
1120 | count = safe_read(0, hdr_buf, sizeof(hdr_buf)); | ||
1121 | if (count > 0) { | ||
1122 | hdr_cnt = count; | ||
1123 | hdr_ptr = hdr_buf; | ||
1124 | post_len -= count; | ||
1125 | } else { | ||
1126 | /* no more POST data can be read */ | ||
1127 | post_len = 0; | ||
1128 | } | ||
1129 | } | ||
1130 | |||
1131 | if (pfd[FROM_CGI].revents) { | ||
1132 | /* There is something to read from CGI */ | ||
1133 | char *rbuf = iobuf; | ||
1134 | |||
1135 | /* Are we still buffering CGI output? */ | ||
1136 | if (out_cnt >= 0) { | ||
1137 | /* HTTP_200[] has single "\r\n" at the end. | ||
1138 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, | ||
1139 | * CGI scripts MUST send their own header terminated by | ||
1140 | * empty line, then data. That's why we have only one | ||
1141 | * <cr><lf> pair here. We will output "200 OK" line | ||
1142 | * if needed, but CGI still has to provide blank line | ||
1143 | * between header and body */ | ||
1144 | |||
1145 | /* Must use safe_read, not full_read, because | ||
1146 | * CGI may output a few first bytes and then wait | ||
1147 | * for POSTDATA without closing stdout. | ||
1148 | * With full_read we may wait here forever. */ | ||
1149 | count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8); | ||
1150 | if (count <= 0) { | ||
1151 | /* eof (or error) and there was no "HTTP", | ||
1152 | * so write it, then write received data */ | ||
1153 | if (out_cnt) { | ||
1154 | full_write(1, HTTP_200, sizeof(HTTP_200)-1); | ||
1155 | full_write(1, rbuf, out_cnt); | ||
1156 | } | ||
1157 | break; /* CGI stdout is closed, exiting */ | ||
1158 | } | ||
1159 | out_cnt += count; | ||
1160 | count = 0; | ||
1161 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ | ||
1162 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { | ||
1163 | /* send "HTTP/1.0 " */ | ||
1164 | if (full_write(1, HTTP_200, 9) != 9) | ||
1165 | break; | ||
1166 | rbuf += 8; /* skip "Status: " */ | ||
1167 | count = out_cnt - 8; | ||
1168 | out_cnt = -1; /* buffering off */ | ||
1169 | } else if (out_cnt >= 4) { | ||
1170 | /* Did CGI add "HTTP"? */ | ||
1171 | if (memcmp(rbuf, HTTP_200, 4) != 0) { | ||
1172 | /* there is no "HTTP", do it ourself */ | ||
1173 | if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) | ||
1174 | break; | ||
1175 | } | ||
1176 | /* Commented out: | ||
1177 | if (!strstr(rbuf, "ontent-")) { | ||
1178 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); | ||
1179 | } | ||
1180 | * Counter-example of valid CGI without Content-type: | ||
1181 | * echo -en "HTTP/1.0 302 Found\r\n" | ||
1182 | * echo -en "Location: http://www.busybox.net\r\n" | ||
1183 | * echo -en "\r\n" | ||
1184 | */ | ||
1185 | count = out_cnt; | ||
1186 | out_cnt = -1; /* buffering off */ | ||
1187 | } | ||
1188 | } else { | ||
1189 | count = safe_read(fromCgi_rd, rbuf, PIPE_BUF); | ||
1190 | if (count <= 0) | ||
1191 | break; /* eof (or error) */ | ||
1192 | } | ||
1193 | if (full_write(1, rbuf, count) != count) | ||
1194 | break; | ||
1195 | if (DEBUG) | ||
1196 | fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); | ||
1197 | } /* if (pfd[FROM_CGI].revents) */ | ||
1198 | } /* while (1) */ | ||
1199 | log_and_exit(); | ||
1200 | } | ||
1201 | |||
1032 | static void setenv1(const char *name, const char *value) | 1202 | static void setenv1(const char *name, const char *value) |
1033 | { | 1203 | { |
1034 | setenv(name, value ? value : "", 1); | 1204 | setenv(name, value ? value : "", 1); |
@@ -1038,25 +1208,25 @@ static void setenv1(const char *name, const char *value) | |||
1038 | * Spawn CGI script, forward CGI's stdin/out <=> network | 1208 | * Spawn CGI script, forward CGI's stdin/out <=> network |
1039 | * | 1209 | * |
1040 | * Environment variables are set up and the script is invoked with pipes | 1210 | * Environment variables are set up and the script is invoked with pipes |
1041 | * for stdin/stdout. If a post is being done the script is fed the POST | 1211 | * for stdin/stdout. If a POST is being done the script is fed the POST |
1042 | * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). | 1212 | * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). |
1043 | * | 1213 | * |
1044 | * Parameters: | 1214 | * Parameters: |
1045 | * const char *url The requested URL (with leading /). | 1215 | * const char *url The requested URL (with leading /). |
1046 | * int bodyLen Length of the post body. | 1216 | * int post_len Length of the POST body. |
1047 | * const char *cookie For set HTTP_COOKIE. | 1217 | * const char *cookie For set HTTP_COOKIE. |
1048 | * const char *content_type For set CONTENT_TYPE. | 1218 | * const char *content_type For set CONTENT_TYPE. |
1049 | */ | 1219 | */ |
1050 | static void send_cgi_and_exit( | 1220 | static void send_cgi_and_exit( |
1051 | const char *url, | 1221 | const char *url, |
1052 | const char *request, | 1222 | const char *request, |
1053 | int bodyLen, | 1223 | int post_len, |
1054 | const char *cookie, | 1224 | const char *cookie, |
1055 | const char *content_type) ATTRIBUTE_NORETURN; | 1225 | const char *content_type) ATTRIBUTE_NORETURN; |
1056 | static void send_cgi_and_exit( | 1226 | static void send_cgi_and_exit( |
1057 | const char *url, | 1227 | const char *url, |
1058 | const char *request, | 1228 | const char *request, |
1059 | int bodyLen, | 1229 | int post_len, |
1060 | const char *cookie, | 1230 | const char *cookie, |
1061 | const char *content_type) | 1231 | const char *content_type) |
1062 | { | 1232 | { |
@@ -1065,9 +1235,7 @@ static void send_cgi_and_exit( | |||
1065 | char *fullpath; | 1235 | char *fullpath; |
1066 | char *script; | 1236 | char *script; |
1067 | char *purl; | 1237 | char *purl; |
1068 | int buf_count; | 1238 | int pid; |
1069 | int status; | ||
1070 | int pid = 0; | ||
1071 | 1239 | ||
1072 | /* | 1240 | /* |
1073 | * We are mucking with environment _first_ and then vfork/exec, | 1241 | * We are mucking with environment _first_ and then vfork/exec, |
@@ -1138,8 +1306,8 @@ static void send_cgi_and_exit( | |||
1138 | } | 1306 | } |
1139 | } | 1307 | } |
1140 | setenv1("HTTP_USER_AGENT", user_agent); | 1308 | setenv1("HTTP_USER_AGENT", user_agent); |
1141 | if (bodyLen) | 1309 | if (post_len) |
1142 | putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen)); | 1310 | putenv(xasprintf("CONTENT_LENGTH=%d", post_len)); |
1143 | if (cookie) | 1311 | if (cookie) |
1144 | setenv1("HTTP_COOKIE", cookie); | 1312 | setenv1("HTTP_COOKIE", cookie); |
1145 | if (content_type) | 1313 | if (content_type) |
@@ -1215,168 +1383,15 @@ static void send_cgi_and_exit( | |||
1215 | 1383 | ||
1216 | /* Parent process */ | 1384 | /* Parent process */ |
1217 | 1385 | ||
1218 | /* First, restore variables possibly changed by child */ | 1386 | /* Restore variables possibly changed by child */ |
1219 | xfunc_error_retval = 0; | 1387 | xfunc_error_retval = 0; |
1220 | 1388 | ||
1221 | /* Prepare for pumping data. | 1389 | /* Pump data */ |
1222 | * iobuf is used for CGI -> network data, | ||
1223 | * hdr_buf is for network -> CGI data (POSTDATA) */ | ||
1224 | buf_count = 0; | ||
1225 | close(fromCgi.wr); | 1390 | close(fromCgi.wr); |
1226 | close(toCgi.rd); | 1391 | close(toCgi.rd); |
1227 | 1392 | cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len); | |
1228 | /* If CGI dies, we still want to correctly finish reading its output | ||
1229 | * and send it to the peer. So please no SIGPIPEs! */ | ||
1230 | signal(SIGPIPE, SIG_IGN); | ||
1231 | |||
1232 | /* This loop still looks messy. What is an exit criteria? | ||
1233 | * "CGI's output closed"? Or "CGI has exited"? | ||
1234 | * What to do if CGI has closed both input and output, but | ||
1235 | * didn't exit? etc... */ | ||
1236 | |||
1237 | /* NB: breaking out of this loop jumps to log_and_exit() */ | ||
1238 | while (1) { | ||
1239 | fd_set readSet; | ||
1240 | fd_set writeSet; | ||
1241 | int nfound; | ||
1242 | int count; | ||
1243 | |||
1244 | FD_ZERO(&readSet); | ||
1245 | FD_ZERO(&writeSet); | ||
1246 | FD_SET(fromCgi.rd, &readSet); | ||
1247 | if (bodyLen > 0 || hdr_cnt > 0) { | ||
1248 | FD_SET(toCgi.wr, &writeSet); | ||
1249 | nfound = toCgi.wr > fromCgi.rd ? toCgi.wr : fromCgi.rd; | ||
1250 | if (hdr_cnt <= 0) | ||
1251 | FD_SET(0, &readSet); | ||
1252 | /* Now wait on the set of sockets! */ | ||
1253 | nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL); | ||
1254 | } else { | ||
1255 | if (!bodyLen) { | ||
1256 | close(toCgi.wr); /* no more POST data to CGI */ | ||
1257 | bodyLen = -1; | ||
1258 | } | ||
1259 | nfound = select(fromCgi.rd + 1, &readSet, NULL, NULL, NULL); | ||
1260 | } | ||
1261 | |||
1262 | if (nfound <= 0) { | ||
1263 | if (waitpid(pid, &status, WNOHANG) <= 0) { | ||
1264 | /* Weird. CGI didn't exit and no fd's | ||
1265 | * are ready, yet select returned?! */ | ||
1266 | continue; | ||
1267 | } | ||
1268 | close(fromCgi.rd); | ||
1269 | if (DEBUG && WIFEXITED(status)) | ||
1270 | bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status)); | ||
1271 | if (DEBUG && WIFSIGNALED(status)) | ||
1272 | bb_error_msg("CGI killed, signal=%d", WTERMSIG(status)); | ||
1273 | break; | ||
1274 | } | ||
1275 | |||
1276 | if (hdr_cnt > 0 && FD_ISSET(toCgi.wr, &writeSet)) { | ||
1277 | /* Have data from peer and can write to CGI */ | ||
1278 | count = safe_write(toCgi.wr, hdr_ptr, hdr_cnt); | ||
1279 | /* Doesn't happen, we dont use nonblocking IO here | ||
1280 | *if (count < 0 && errno == EAGAIN) { | ||
1281 | * ... | ||
1282 | *} else */ | ||
1283 | if (count > 0) { | ||
1284 | hdr_ptr += count; | ||
1285 | hdr_cnt -= count; | ||
1286 | } else { | ||
1287 | hdr_cnt = bodyLen = 0; /* EOF/broken pipe to CGI */ | ||
1288 | } | ||
1289 | } else if (bodyLen > 0 && hdr_cnt == 0 | ||
1290 | && FD_ISSET(0, &readSet) | ||
1291 | ) { | ||
1292 | /* We expect data, prev data portion is eaten by CGI | ||
1293 | * and there *is* data to read from the peer | ||
1294 | * (POSTDATA?) */ | ||
1295 | count = bodyLen > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : bodyLen; | ||
1296 | count = safe_read(0, hdr_buf, count); | ||
1297 | if (count > 0) { | ||
1298 | hdr_cnt = count; | ||
1299 | hdr_ptr = hdr_buf; | ||
1300 | bodyLen -= count; | ||
1301 | } else { | ||
1302 | bodyLen = 0; /* closed */ | ||
1303 | } | ||
1304 | } | ||
1305 | |||
1306 | #define PIPESIZE PIPE_BUF | ||
1307 | #if PIPESIZE >= IOBUF_SIZE | ||
1308 | # error "PIPESIZE >= IOBUF_SIZE" | ||
1309 | #endif | ||
1310 | if (FD_ISSET(fromCgi.rd, &readSet)) { | ||
1311 | /* There is something to read from CGI */ | ||
1312 | char *rbuf = iobuf; | ||
1313 | |||
1314 | /* Are we still buffering CGI output? */ | ||
1315 | if (buf_count >= 0) { | ||
1316 | /* HTTP_200[] has single "\r\n" at the end. | ||
1317 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, | ||
1318 | * CGI scripts MUST send their own header terminated by | ||
1319 | * empty line, then data. That's why we have only one | ||
1320 | * <cr><lf> pair here. We will output "200 OK" line | ||
1321 | * if needed, but CGI still has to provide blank line | ||
1322 | * between header and body */ | ||
1323 | |||
1324 | /* Must use safe_read, not full_read, because | ||
1325 | * CGI may output a few first bytes and then wait | ||
1326 | * for POSTDATA without closing stdout. | ||
1327 | * With full_read we may wait here forever. */ | ||
1328 | count = safe_read(fromCgi.rd, rbuf + buf_count, PIPESIZE - 8); | ||
1329 | if (count <= 0) { | ||
1330 | /* eof (or error) and there was no "HTTP", | ||
1331 | * so write it, then write received data */ | ||
1332 | if (buf_count) { | ||
1333 | full_write(1, HTTP_200, sizeof(HTTP_200)-1); | ||
1334 | full_write(1, rbuf, buf_count); | ||
1335 | } | ||
1336 | break; /* CGI stdout is closed, exiting */ | ||
1337 | } | ||
1338 | buf_count += count; | ||
1339 | count = 0; | ||
1340 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ | ||
1341 | if (buf_count >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { | ||
1342 | /* send "HTTP/1.0 " */ | ||
1343 | if (full_write(1, HTTP_200, 9) != 9) | ||
1344 | break; | ||
1345 | rbuf += 8; /* skip "Status: " */ | ||
1346 | count = buf_count - 8; | ||
1347 | buf_count = -1; /* buffering off */ | ||
1348 | } else if (buf_count >= 4) { | ||
1349 | /* Did CGI add "HTTP"? */ | ||
1350 | if (memcmp(rbuf, HTTP_200, 4) != 0) { | ||
1351 | /* there is no "HTTP", do it ourself */ | ||
1352 | if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) | ||
1353 | break; | ||
1354 | } | ||
1355 | /* Commented out: | ||
1356 | if (!strstr(rbuf, "ontent-")) { | ||
1357 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); | ||
1358 | } | ||
1359 | * Counter-example of valid CGI without Content-type: | ||
1360 | * echo -en "HTTP/1.0 302 Found\r\n" | ||
1361 | * echo -en "Location: http://www.busybox.net\r\n" | ||
1362 | * echo -en "\r\n" | ||
1363 | */ | ||
1364 | count = buf_count; | ||
1365 | buf_count = -1; /* buffering off */ | ||
1366 | } | ||
1367 | } else { | ||
1368 | count = safe_read(fromCgi.rd, rbuf, PIPESIZE); | ||
1369 | if (count <= 0) | ||
1370 | break; /* eof (or error) */ | ||
1371 | } | ||
1372 | if (full_write(1, rbuf, count) != count) | ||
1373 | break; | ||
1374 | if (DEBUG) | ||
1375 | fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); | ||
1376 | } /* if (FD_ISSET(fromCgi.rd)) */ | ||
1377 | } /* while (1) */ | ||
1378 | log_and_exit(); | ||
1379 | } | 1393 | } |
1394 | |||
1380 | #endif /* FEATURE_HTTPD_CGI */ | 1395 | #endif /* FEATURE_HTTPD_CGI */ |
1381 | 1396 | ||
1382 | /* | 1397 | /* |