httpd: simplify CGI i/o loop. -200 bytes.
This commit is contained in:
parent
9a4e08eaa8
commit
32a471e4db
@ -104,12 +104,15 @@
|
||||
//#define DEBUG 1
|
||||
#define DEBUG 0
|
||||
|
||||
#define IOBUF_SIZE 8192 /* IO buffer */
|
||||
|
||||
/* amount of buffering in a pipe */
|
||||
#ifndef PIPE_BUF
|
||||
# define PIPE_BUF 4096
|
||||
#endif
|
||||
|
||||
#define IOBUF_SIZE 8192 /* IO buffer */
|
||||
#if PIPE_BUF >= IOBUF_SIZE
|
||||
# error "PIPE_BUF >= IOBUF_SIZE"
|
||||
#endif
|
||||
|
||||
#define HEADER_READ_TIMEOUT 60
|
||||
|
||||
@ -1029,6 +1032,173 @@ static int get_line(void)
|
||||
}
|
||||
|
||||
#if ENABLE_FEATURE_HTTPD_CGI
|
||||
|
||||
/* gcc 4.2.1 fares better with NOINLINE */
|
||||
static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) ATTRIBUTE_NORETURN;
|
||||
static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
|
||||
{
|
||||
enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
|
||||
struct pollfd pfd[3];
|
||||
int out_cnt; /* we buffer a bit of initial CGI output */
|
||||
int count;
|
||||
|
||||
/* iobuf is used for CGI -> network data,
|
||||
* hdr_buf is for network -> CGI data (POSTDATA) */
|
||||
|
||||
/* If CGI dies, we still want to correctly finish reading its output
|
||||
* and send it to the peer. So please no SIGPIPEs! */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
/* NB: breaking out of this loop jumps to log_and_exit() */
|
||||
out_cnt = 0;
|
||||
while (1) {
|
||||
memset(pfd, 0, sizeof(pfd));
|
||||
|
||||
pfd[FROM_CGI].fd = fromCgi_rd;
|
||||
pfd[FROM_CGI].events = POLLIN;
|
||||
|
||||
if (toCgi_wr) {
|
||||
pfd[TO_CGI].fd = toCgi_wr;
|
||||
if (hdr_cnt > 0) {
|
||||
pfd[TO_CGI].events = POLLOUT;
|
||||
} else if (post_len > 0) {
|
||||
pfd[0].events = POLLIN;
|
||||
} else {
|
||||
/* post_len <= 0 && hdr_cnt <= 0:
|
||||
* no more POST data to CGI,
|
||||
* let CGI see EOF on CGI's stdin */
|
||||
close(toCgi_wr);
|
||||
toCgi_wr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now wait on the set of sockets */
|
||||
count = poll(pfd, 3, -1);
|
||||
if (count <= 0) {
|
||||
#if 0
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
#endif
|
||||
#if 0
|
||||
if (waitpid(pid, &status, WNOHANG) <= 0) {
|
||||
/* Weird. CGI didn't exit and no fd's
|
||||
* are ready, yet poll returned?! */
|
||||
continue;
|
||||
}
|
||||
if (DEBUG && WIFEXITED(status))
|
||||
bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
|
||||
if (DEBUG && WIFSIGNALED(status))
|
||||
bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfd[TO_CGI].revents) {
|
||||
/* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
|
||||
/* Have data from peer and can write to CGI */
|
||||
count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
|
||||
/* Doesn't happen, we dont use nonblocking IO here
|
||||
*if (count < 0 && errno == EAGAIN) {
|
||||
* ...
|
||||
*} else */
|
||||
if (count > 0) {
|
||||
hdr_ptr += count;
|
||||
hdr_cnt -= count;
|
||||
} else {
|
||||
/* EOF/broken pipe to CGI, stop piping POST data */
|
||||
hdr_cnt = post_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfd[0].revents) {
|
||||
/* post_len > 0 && hdr_cnt == 0 here */
|
||||
/* We expect data, prev data portion is eaten by CGI
|
||||
* and there *is* data to read from the peer
|
||||
* (POSTDATA) */
|
||||
//count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
|
||||
//count = safe_read(0, hdr_buf, count);
|
||||
count = safe_read(0, hdr_buf, sizeof(hdr_buf));
|
||||
if (count > 0) {
|
||||
hdr_cnt = count;
|
||||
hdr_ptr = hdr_buf;
|
||||
post_len -= count;
|
||||
} else {
|
||||
/* no more POST data can be read */
|
||||
post_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfd[FROM_CGI].revents) {
|
||||
/* There is something to read from CGI */
|
||||
char *rbuf = iobuf;
|
||||
|
||||
/* Are we still buffering CGI output? */
|
||||
if (out_cnt >= 0) {
|
||||
/* HTTP_200[] has single "\r\n" at the end.
|
||||
* According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
|
||||
* CGI scripts MUST send their own header terminated by
|
||||
* empty line, then data. That's why we have only one
|
||||
* <cr><lf> pair here. We will output "200 OK" line
|
||||
* if needed, but CGI still has to provide blank line
|
||||
* between header and body */
|
||||
|
||||
/* Must use safe_read, not full_read, because
|
||||
* CGI may output a few first bytes and then wait
|
||||
* for POSTDATA without closing stdout.
|
||||
* With full_read we may wait here forever. */
|
||||
count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
|
||||
if (count <= 0) {
|
||||
/* eof (or error) and there was no "HTTP",
|
||||
* so write it, then write received data */
|
||||
if (out_cnt) {
|
||||
full_write(1, HTTP_200, sizeof(HTTP_200)-1);
|
||||
full_write(1, rbuf, out_cnt);
|
||||
}
|
||||
break; /* CGI stdout is closed, exiting */
|
||||
}
|
||||
out_cnt += count;
|
||||
count = 0;
|
||||
/* "Status" header format is: "Status: 302 Redirected\r\n" */
|
||||
if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
|
||||
/* send "HTTP/1.0 " */
|
||||
if (full_write(1, HTTP_200, 9) != 9)
|
||||
break;
|
||||
rbuf += 8; /* skip "Status: " */
|
||||
count = out_cnt - 8;
|
||||
out_cnt = -1; /* buffering off */
|
||||
} else if (out_cnt >= 4) {
|
||||
/* Did CGI add "HTTP"? */
|
||||
if (memcmp(rbuf, HTTP_200, 4) != 0) {
|
||||
/* there is no "HTTP", do it ourself */
|
||||
if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
|
||||
break;
|
||||
}
|
||||
/* Commented out:
|
||||
if (!strstr(rbuf, "ontent-")) {
|
||||
full_write(s, "Content-type: text/plain\r\n\r\n", 28);
|
||||
}
|
||||
* Counter-example of valid CGI without Content-type:
|
||||
* echo -en "HTTP/1.0 302 Found\r\n"
|
||||
* echo -en "Location: http://www.busybox.net\r\n"
|
||||
* echo -en "\r\n"
|
||||
*/
|
||||
count = out_cnt;
|
||||
out_cnt = -1; /* buffering off */
|
||||
}
|
||||
} else {
|
||||
count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
|
||||
if (count <= 0)
|
||||
break; /* eof (or error) */
|
||||
}
|
||||
if (full_write(1, rbuf, count) != count)
|
||||
break;
|
||||
if (DEBUG)
|
||||
fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
|
||||
} /* if (pfd[FROM_CGI].revents) */
|
||||
} /* while (1) */
|
||||
log_and_exit();
|
||||
}
|
||||
|
||||
static void setenv1(const char *name, const char *value)
|
||||
{
|
||||
setenv(name, value ? value : "", 1);
|
||||
@ -1038,25 +1208,25 @@ static void setenv1(const char *name, const char *value)
|
||||
* Spawn CGI script, forward CGI's stdin/out <=> network
|
||||
*
|
||||
* Environment variables are set up and the script is invoked with pipes
|
||||
* for stdin/stdout. If a post is being done the script is fed the POST
|
||||
* for stdin/stdout. If a POST is being done the script is fed the POST
|
||||
* data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
|
||||
*
|
||||
* Parameters:
|
||||
* const char *url The requested URL (with leading /).
|
||||
* int bodyLen Length of the post body.
|
||||
* int post_len Length of the POST body.
|
||||
* const char *cookie For set HTTP_COOKIE.
|
||||
* const char *content_type For set CONTENT_TYPE.
|
||||
*/
|
||||
static void send_cgi_and_exit(
|
||||
const char *url,
|
||||
const char *request,
|
||||
int bodyLen,
|
||||
int post_len,
|
||||
const char *cookie,
|
||||
const char *content_type) ATTRIBUTE_NORETURN;
|
||||
static void send_cgi_and_exit(
|
||||
const char *url,
|
||||
const char *request,
|
||||
int bodyLen,
|
||||
int post_len,
|
||||
const char *cookie,
|
||||
const char *content_type)
|
||||
{
|
||||
@ -1065,9 +1235,7 @@ static void send_cgi_and_exit(
|
||||
char *fullpath;
|
||||
char *script;
|
||||
char *purl;
|
||||
int buf_count;
|
||||
int status;
|
||||
int pid = 0;
|
||||
int pid;
|
||||
|
||||
/*
|
||||
* We are mucking with environment _first_ and then vfork/exec,
|
||||
@ -1138,8 +1306,8 @@ static void send_cgi_and_exit(
|
||||
}
|
||||
}
|
||||
setenv1("HTTP_USER_AGENT", user_agent);
|
||||
if (bodyLen)
|
||||
putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen));
|
||||
if (post_len)
|
||||
putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
|
||||
if (cookie)
|
||||
setenv1("HTTP_COOKIE", cookie);
|
||||
if (content_type)
|
||||
@ -1215,168 +1383,15 @@ static void send_cgi_and_exit(
|
||||
|
||||
/* Parent process */
|
||||
|
||||
/* First, restore variables possibly changed by child */
|
||||
/* Restore variables possibly changed by child */
|
||||
xfunc_error_retval = 0;
|
||||
|
||||
/* Prepare for pumping data.
|
||||
* iobuf is used for CGI -> network data,
|
||||
* hdr_buf is for network -> CGI data (POSTDATA) */
|
||||
buf_count = 0;
|
||||
/* Pump data */
|
||||
close(fromCgi.wr);
|
||||
close(toCgi.rd);
|
||||
|
||||
/* If CGI dies, we still want to correctly finish reading its output
|
||||
* and send it to the peer. So please no SIGPIPEs! */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
/* This loop still looks messy. What is an exit criteria?
|
||||
* "CGI's output closed"? Or "CGI has exited"?
|
||||
* What to do if CGI has closed both input and output, but
|
||||
* didn't exit? etc... */
|
||||
|
||||
/* NB: breaking out of this loop jumps to log_and_exit() */
|
||||
while (1) {
|
||||
fd_set readSet;
|
||||
fd_set writeSet;
|
||||
int nfound;
|
||||
int count;
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
FD_SET(fromCgi.rd, &readSet);
|
||||
if (bodyLen > 0 || hdr_cnt > 0) {
|
||||
FD_SET(toCgi.wr, &writeSet);
|
||||
nfound = toCgi.wr > fromCgi.rd ? toCgi.wr : fromCgi.rd;
|
||||
if (hdr_cnt <= 0)
|
||||
FD_SET(0, &readSet);
|
||||
/* Now wait on the set of sockets! */
|
||||
nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL);
|
||||
} else {
|
||||
if (!bodyLen) {
|
||||
close(toCgi.wr); /* no more POST data to CGI */
|
||||
bodyLen = -1;
|
||||
}
|
||||
nfound = select(fromCgi.rd + 1, &readSet, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (nfound <= 0) {
|
||||
if (waitpid(pid, &status, WNOHANG) <= 0) {
|
||||
/* Weird. CGI didn't exit and no fd's
|
||||
* are ready, yet select returned?! */
|
||||
continue;
|
||||
}
|
||||
close(fromCgi.rd);
|
||||
if (DEBUG && WIFEXITED(status))
|
||||
bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
|
||||
if (DEBUG && WIFSIGNALED(status))
|
||||
bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
|
||||
break;
|
||||
}
|
||||
|
||||
if (hdr_cnt > 0 && FD_ISSET(toCgi.wr, &writeSet)) {
|
||||
/* Have data from peer and can write to CGI */
|
||||
count = safe_write(toCgi.wr, hdr_ptr, hdr_cnt);
|
||||
/* Doesn't happen, we dont use nonblocking IO here
|
||||
*if (count < 0 && errno == EAGAIN) {
|
||||
* ...
|
||||
*} else */
|
||||
if (count > 0) {
|
||||
hdr_ptr += count;
|
||||
hdr_cnt -= count;
|
||||
} else {
|
||||
hdr_cnt = bodyLen = 0; /* EOF/broken pipe to CGI */
|
||||
}
|
||||
} else if (bodyLen > 0 && hdr_cnt == 0
|
||||
&& FD_ISSET(0, &readSet)
|
||||
) {
|
||||
/* We expect data, prev data portion is eaten by CGI
|
||||
* and there *is* data to read from the peer
|
||||
* (POSTDATA?) */
|
||||
count = bodyLen > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : bodyLen;
|
||||
count = safe_read(0, hdr_buf, count);
|
||||
if (count > 0) {
|
||||
hdr_cnt = count;
|
||||
hdr_ptr = hdr_buf;
|
||||
bodyLen -= count;
|
||||
} else {
|
||||
bodyLen = 0; /* closed */
|
||||
}
|
||||
}
|
||||
|
||||
#define PIPESIZE PIPE_BUF
|
||||
#if PIPESIZE >= IOBUF_SIZE
|
||||
# error "PIPESIZE >= IOBUF_SIZE"
|
||||
#endif
|
||||
if (FD_ISSET(fromCgi.rd, &readSet)) {
|
||||
/* There is something to read from CGI */
|
||||
char *rbuf = iobuf;
|
||||
|
||||
/* Are we still buffering CGI output? */
|
||||
if (buf_count >= 0) {
|
||||
/* HTTP_200[] has single "\r\n" at the end.
|
||||
* According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
|
||||
* CGI scripts MUST send their own header terminated by
|
||||
* empty line, then data. That's why we have only one
|
||||
* <cr><lf> pair here. We will output "200 OK" line
|
||||
* if needed, but CGI still has to provide blank line
|
||||
* between header and body */
|
||||
|
||||
/* Must use safe_read, not full_read, because
|
||||
* CGI may output a few first bytes and then wait
|
||||
* for POSTDATA without closing stdout.
|
||||
* With full_read we may wait here forever. */
|
||||
count = safe_read(fromCgi.rd, rbuf + buf_count, PIPESIZE - 8);
|
||||
if (count <= 0) {
|
||||
/* eof (or error) and there was no "HTTP",
|
||||
* so write it, then write received data */
|
||||
if (buf_count) {
|
||||
full_write(1, HTTP_200, sizeof(HTTP_200)-1);
|
||||
full_write(1, rbuf, buf_count);
|
||||
}
|
||||
break; /* CGI stdout is closed, exiting */
|
||||
}
|
||||
buf_count += count;
|
||||
count = 0;
|
||||
/* "Status" header format is: "Status: 302 Redirected\r\n" */
|
||||
if (buf_count >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
|
||||
/* send "HTTP/1.0 " */
|
||||
if (full_write(1, HTTP_200, 9) != 9)
|
||||
break;
|
||||
rbuf += 8; /* skip "Status: " */
|
||||
count = buf_count - 8;
|
||||
buf_count = -1; /* buffering off */
|
||||
} else if (buf_count >= 4) {
|
||||
/* Did CGI add "HTTP"? */
|
||||
if (memcmp(rbuf, HTTP_200, 4) != 0) {
|
||||
/* there is no "HTTP", do it ourself */
|
||||
if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
|
||||
break;
|
||||
}
|
||||
/* Commented out:
|
||||
if (!strstr(rbuf, "ontent-")) {
|
||||
full_write(s, "Content-type: text/plain\r\n\r\n", 28);
|
||||
}
|
||||
* Counter-example of valid CGI without Content-type:
|
||||
* echo -en "HTTP/1.0 302 Found\r\n"
|
||||
* echo -en "Location: http://www.busybox.net\r\n"
|
||||
* echo -en "\r\n"
|
||||
*/
|
||||
count = buf_count;
|
||||
buf_count = -1; /* buffering off */
|
||||
}
|
||||
} else {
|
||||
count = safe_read(fromCgi.rd, rbuf, PIPESIZE);
|
||||
if (count <= 0)
|
||||
break; /* eof (or error) */
|
||||
}
|
||||
if (full_write(1, rbuf, count) != count)
|
||||
break;
|
||||
if (DEBUG)
|
||||
fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
|
||||
} /* if (FD_ISSET(fromCgi.rd)) */
|
||||
} /* while (1) */
|
||||
log_and_exit();
|
||||
cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
|
||||
}
|
||||
|
||||
#endif /* FEATURE_HTTPD_CGI */
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user