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 1
|
||||||
#define DEBUG 0
|
#define DEBUG 0
|
||||||
|
|
||||||
|
#define IOBUF_SIZE 8192 /* IO buffer */
|
||||||
|
|
||||||
/* amount of buffering in a pipe */
|
/* amount of buffering in a pipe */
|
||||||
#ifndef PIPE_BUF
|
#ifndef PIPE_BUF
|
||||||
# define PIPE_BUF 4096
|
# define PIPE_BUF 4096
|
||||||
#endif
|
#endif
|
||||||
|
#if PIPE_BUF >= IOBUF_SIZE
|
||||||
#define IOBUF_SIZE 8192 /* IO buffer */
|
# error "PIPE_BUF >= IOBUF_SIZE"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define HEADER_READ_TIMEOUT 60
|
#define HEADER_READ_TIMEOUT 60
|
||||||
|
|
||||||
@ -1029,6 +1032,173 @@ static int get_line(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_FEATURE_HTTPD_CGI
|
#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)
|
static void setenv1(const char *name, const char *value)
|
||||||
{
|
{
|
||||||
setenv(name, value ? value : "", 1);
|
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
|
* Spawn CGI script, forward CGI's stdin/out <=> network
|
||||||
*
|
*
|
||||||
* Environment variables are set up and the script is invoked with pipes
|
* 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).
|
* data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* const char *url The requested URL (with leading /).
|
* 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 *cookie For set HTTP_COOKIE.
|
||||||
* const char *content_type For set CONTENT_TYPE.
|
* const char *content_type For set CONTENT_TYPE.
|
||||||
*/
|
*/
|
||||||
static void send_cgi_and_exit(
|
static void send_cgi_and_exit(
|
||||||
const char *url,
|
const char *url,
|
||||||
const char *request,
|
const char *request,
|
||||||
int bodyLen,
|
int post_len,
|
||||||
const char *cookie,
|
const char *cookie,
|
||||||
const char *content_type) ATTRIBUTE_NORETURN;
|
const char *content_type) ATTRIBUTE_NORETURN;
|
||||||
static void send_cgi_and_exit(
|
static void send_cgi_and_exit(
|
||||||
const char *url,
|
const char *url,
|
||||||
const char *request,
|
const char *request,
|
||||||
int bodyLen,
|
int post_len,
|
||||||
const char *cookie,
|
const char *cookie,
|
||||||
const char *content_type)
|
const char *content_type)
|
||||||
{
|
{
|
||||||
@ -1065,9 +1235,7 @@ static void send_cgi_and_exit(
|
|||||||
char *fullpath;
|
char *fullpath;
|
||||||
char *script;
|
char *script;
|
||||||
char *purl;
|
char *purl;
|
||||||
int buf_count;
|
int pid;
|
||||||
int status;
|
|
||||||
int pid = 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We are mucking with environment _first_ and then vfork/exec,
|
* 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);
|
setenv1("HTTP_USER_AGENT", user_agent);
|
||||||
if (bodyLen)
|
if (post_len)
|
||||||
putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen));
|
putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
|
||||||
if (cookie)
|
if (cookie)
|
||||||
setenv1("HTTP_COOKIE", cookie);
|
setenv1("HTTP_COOKIE", cookie);
|
||||||
if (content_type)
|
if (content_type)
|
||||||
@ -1215,168 +1383,15 @@ static void send_cgi_and_exit(
|
|||||||
|
|
||||||
/* Parent process */
|
/* Parent process */
|
||||||
|
|
||||||
/* First, restore variables possibly changed by child */
|
/* Restore variables possibly changed by child */
|
||||||
xfunc_error_retval = 0;
|
xfunc_error_retval = 0;
|
||||||
|
|
||||||
/* Prepare for pumping data.
|
/* Pump data */
|
||||||
* iobuf is used for CGI -> network data,
|
|
||||||
* hdr_buf is for network -> CGI data (POSTDATA) */
|
|
||||||
buf_count = 0;
|
|
||||||
close(fromCgi.wr);
|
close(fromCgi.wr);
|
||||||
close(toCgi.rd);
|
close(toCgi.rd);
|
||||||
|
cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
|
||||||
/* 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* FEATURE_HTTPD_CGI */
|
#endif /* FEATURE_HTTPD_CGI */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user