CGI needs to copy httpd_conn because it is volatile

git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@2058 42af7a65-404d-4744-a932-0658087f49c3
This commit is contained in:
patacongo 2009-09-15 19:48:13 +00:00
parent 8e0a8ee3bd
commit 075d96a70a
2 changed files with 172 additions and 117 deletions

View File

@ -156,6 +156,14 @@
# define CONFIG_THTTPD_MAXREALLOC 4096 # define CONFIG_THTTPD_MAXREALLOC 4096
# endif # endif
# ifndef CONFIG_THTTPD_CGIINBUFFERSIZE
# define CONFIG_THTTPD_CGIINBUFFERSIZE 512 /* Size of buffer to interpose input */
# endif
# ifndef CONFIG_THTTPD_CGIOUTBUFFERSIZE
# define CONFIG_THTTPD_CGIOUTBUFFERSIZE 512 /* Size of buffer to interpose output */
# endif
# if CONFIG_THTTPD_IOBUFFERSIZE > 65535 # if CONFIG_THTTPD_IOBUFFERSIZE > 65535
# error "Can't use uint16 for buffer size" # error "Can't use uint16 for buffer size"
# endif # endif

View File

@ -67,6 +67,14 @@
* Pre-processor Definitions * Pre-processor Definitions
****************************************************************************/ ****************************************************************************/
/* CONFIG_THTTPD_CGIDUMP will dump the contents of each transfer to and from the CGI task. */
#ifdef CONFIG_THTTPD_CGIDUMP
# define cgi_dumppacket(m,a,n) lib_dumpbuffer(m,a,n)
#else
# define cgi_dumppacket(m,a,n)
#endif
/**************************************************************************** /****************************************************************************
* Private Types * Private Types
****************************************************************************/ ****************************************************************************/
@ -88,15 +96,35 @@ struct cgi_outbuffer_s
size_t len; /* Amount of valid data in the allocated buffer */ size_t len; /* Amount of valid data in the allocated buffer */
}; };
struct cgi_inbuffer_s
{
int contentlength; /* Size of content to send to CGI task */
int nbytes; /* Number of bytes sent */
char buffer[CONFIG_THTTPD_CGIINBUFFERSIZE]; /* Fixed size input buffer */
};
struct cgi_conn_s
{
/* Descriptors */
int connfd; /* Socket connect to CGI client */
int rdfd; /* Pipe read fd */
int wrfd; /* Pipe write fd */
/* Buffering */
struct cgi_outbuffer_s outbuf; /* Dynamically sized output buffer */
struct cgi_inbuffer_s inbuf; /* Fixed size input buffer */
};
/**************************************************************************** /****************************************************************************
* Private Function Prototypes * Private Function Prototypes
****************************************************************************/ ****************************************************************************/
static void create_environment(httpd_conn *hc); static void create_environment(httpd_conn *hc);
static char **make_argp(httpd_conn *hc); static char **make_argp(httpd_conn *hc);
static inline int cgi_interpose_input(httpd_conn *hc, int wfd, char *buffer); static inline int cgi_interpose_input(struct cgi_conn_s *cc);
static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer, static inline int cgi_interpose_output(struct cgi_conn_s *cc);
struct cgi_outbuffer_s *hdr);
static int cgi_child(int argc, char **argv); static int cgi_child(int argc, char **argv);
/**************************************************************************** /****************************************************************************
@ -329,28 +357,18 @@ static FAR char **make_argp(httpd_conn *hc)
* requests. It reads the data from the client and sends it to the child thread. * requests. It reads the data from the client and sends it to the child thread.
*/ */
static inline int cgi_interpose_input(httpd_conn *hc, int wfd, char *buffer) static inline int cgi_interpose_input(struct cgi_conn_s *cc)
{ {
size_t nbytes;
ssize_t nbytes_read; ssize_t nbytes_read;
ssize_t nbytes_written; ssize_t nbytes_written;
nbytes = hc->read_idx - hc->checked_idx; llvdbg("nbytes: %d contentlength: %d\n", cc->inbuf.nbytes, cc->inbuf.contentlength);
llvdbg("nbytes: %d contentlength: %d\n", nbytes, hc->contentlength); if (cc->inbuf.nbytes < cc->inbuf.contentlength)
if (nbytes > 0)
{
if (httpd_write(wfd, &(hc->read_buf[hc->checked_idx]), nbytes) != nbytes)
{
lldbg("httpd_write failed\n");
return 1;
}
}
if (nbytes < hc->contentlength)
{ {
do do
{ {
nbytes_read = read(hc->conn_fd, buffer, MIN(sizeof(buffer), hc->contentlength - nbytes)); nbytes_read = read(cc->connfd, cc->inbuf.buffer,
MIN(CONFIG_THTTPD_CGIINBUFFERSIZE, cc->inbuf.contentlength - cc->inbuf.nbytes));
llvdbg("nbytes_read: %d\n", nbytes_read); llvdbg("nbytes_read: %d\n", nbytes_read);
if (nbytes_read < 0) if (nbytes_read < 0)
{ {
@ -365,17 +383,20 @@ static inline int cgi_interpose_input(httpd_conn *hc, int wfd, char *buffer)
if (nbytes_read > 0) if (nbytes_read > 0)
{ {
nbytes_written = httpd_write(wfd, buffer, nbytes_read); nbytes_written = httpd_write(cc->wrfd, cc->inbuf.buffer, nbytes_read);
llvdbg("nbytes_written: %d\n", nbytes_written); llvdbg("nbytes_written: %d\n", nbytes_written);
if (nbytes_written != nbytes_read) if (nbytes_written != nbytes_read)
{ {
lldbg("httpd_write failed\n"); lldbg("httpd_write failed\n");
return 1; return 1;
} }
cgi_dumppacket("Sent to CGI:", cc->inbuf.buffer, nbytes_written);
} }
cc->inbuf.nbytes += nbytes_read;
} }
if (nbytes >= hc->contentlength) if (cc->inbuf.nbytes >= cc->inbuf.contentlength)
{ {
/* Special hack to deal with broken browsers that send a LF or CRLF /* Special hack to deal with broken browsers that send a LF or CRLF
* after POST data, causing TCP resets - we just read and discard up * after POST data, causing TCP resets - we just read and discard up
@ -388,11 +409,11 @@ static inline int cgi_interpose_input(httpd_conn *hc, int wfd, char *buffer)
/* Turn on no-delay mode in case we previously cleared it. */ /* Turn on no-delay mode in case we previously cleared it. */
httpd_set_ndelay(hc->conn_fd); httpd_set_ndelay(cc->connfd);
/* And read up to 2 bytes. */ /* And read up to 2 bytes. */
(void)read(hc->conn_fd, buffer, sizeof(buffer)); (void)read(cc->connfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
return 1; return 1;
} }
return 0; return 0;
@ -406,8 +427,7 @@ static inline int cgi_interpose_input(httpd_conn *hc, int wfd, char *buffer)
* out the saved headers and proceed to echo the rest of the response. * out the saved headers and proceed to echo the rest of the response.
*/ */
static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer, static inline int cgi_interpose_output(struct cgi_conn_s *cc)
struct cgi_outbuffer_s *hdr)
{ {
ssize_t nbytes_read; ssize_t nbytes_read;
char *br = NULL; char *br = NULL;
@ -419,12 +439,12 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
* blocking, but we might as well be sure. * blocking, but we might as well be sure.
*/ */
httpd_clear_ndelay(hc->conn_fd); httpd_clear_ndelay(cc->connfd);
/* Loop while there are things we can do without waiting for more input */ /* Loop while there are things we can do without waiting for more input */
llvdbg("state: %d\n", hdr->state); llvdbg("state: %d\n", cc->outbuf.state);
switch (hdr->state) switch (cc->outbuf.state)
{ {
case CGI_OUTBUFFER_READHEADER: case CGI_OUTBUFFER_READHEADER:
{ {
@ -436,8 +456,8 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
* EAGAIN is not an error, but it is still cause to return. * EAGAIN is not an error, but it is still cause to return.
*/ */
nbytes_read = read(hc->conn_fd, inbuffer, sizeof(inbuffer)); nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
nllvdbg("Read %d bytes from fd %d\n", nbytes_read, hc->conn_fd); nllvdbg("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);
if (nbytes_read < 0) if (nbytes_read < 0)
{ {
@ -450,6 +470,10 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
return 1; return 1;
} }
} }
else
{
cgi_dumppacket("Received from CGI:", cc->inbuf.buffer, nbytes_read);
}
} }
while (nbytes_read < 0); while (nbytes_read < 0);
@ -458,26 +482,26 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
if (nbytes_read <= 0) if (nbytes_read <= 0)
{ {
nllvdbg("End-of-file\n"); nllvdbg("End-of-file\n");
br = &(hdr->buffer[hdr->len]); br = &(cc->outbuf.buffer[cc->outbuf.len]);
hdr->state = CGI_OUTBUFFER_HEADERREAD; cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
} }
else else
{ {
/* Accumulate more header data */ /* Accumulate more header data */
httpd_realloc_str(&hdr->buffer, &hdr->size, hdr->len + nbytes_read); httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, cc->outbuf.len + nbytes_read);
(void)memcpy(&(hdr->buffer[hdr->len]), inbuffer, nbytes_read); (void)memcpy(&(cc->outbuf.buffer[cc->outbuf.len]), cc->inbuf.buffer, nbytes_read);
hdr->len += nbytes_read; cc->outbuf.len += nbytes_read;
hdr->buffer[hdr->len] = '\0'; cc->outbuf.buffer[cc->outbuf.len] = '\0';
nllvdbg("Header bytes accumulated: %d\n", hdr->len); nllvdbg("Header bytes accumulated: %d\n", cc->outbuf.len);
/* Check for end of header */ /* Check for end of header */
if ((br = strstr(hdr->buffer, "\r\n\r\n")) != NULL || if ((br = strstr(cc->outbuf.buffer, "\r\n\r\n")) != NULL ||
(br = strstr(hdr->buffer, "\012\012")) != NULL) (br = strstr(cc->outbuf.buffer, "\012\012")) != NULL)
{ {
nllvdbg("End-of-header\n"); nllvdbg("End-of-header\n");
hdr->state = CGI_OUTBUFFER_HEADERREAD; cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
} }
else else
{ {
@ -495,9 +519,9 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
{ {
/* If there were no headers, bail. */ /* If there were no headers, bail. */
if (hdr->buffer[0] == '\0') if (cc->outbuf.buffer[0] == '\0')
{ {
hdr->state = CGI_OUTBUFFER_DONE; cc->outbuf.state = CGI_OUTBUFFER_DONE;
return 1; return 1;
} }
@ -506,23 +530,23 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
*/ */
status = 200; status = 200;
if (strncmp(hdr->buffer, "HTTP/", 5) == 0) if (strncmp(cc->outbuf.buffer, "HTTP/", 5) == 0)
{ {
cp = hdr->buffer; cp = cc->outbuf.buffer;
cp += strcspn(cp, " \t"); cp += strcspn(cp, " \t");
status = atoi(cp); status = atoi(cp);
} }
if ((cp = strstr(hdr->buffer, "Status:")) != NULL && if ((cp = strstr(cc->outbuf.buffer, "Status:")) != NULL &&
cp < br && (cp == hdr->buffer || *(cp - 1) == '\012')) cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
{ {
cp += 7; cp += 7;
cp += strspn(cp, " \t"); cp += strspn(cp, " \t");
status = atoi(cp); status = atoi(cp);
} }
if ((cp = strstr(hdr->buffer, "Location:")) != NULL && if ((cp = strstr(cc->outbuf.buffer, "Location:")) != NULL &&
cp < br && (cp == hdr->buffer || *(cp - 1) == '\012')) cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
{ {
status = 302; status = 302;
} }
@ -586,12 +610,12 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
break; break;
} }
(void)snprintf(inbuffer, sizeof(inbuffer), "HTTP/1.0 %d %s\r\n", status, title); (void)snprintf(cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE, "HTTP/1.0 %d %s\r\n", status, title);
(void)httpd_write(hc->conn_fd, inbuffer, strlen(inbuffer)); (void)httpd_write(cc->connfd, cc->inbuf.buffer, strlen(cc->inbuf.buffer));
/* Write the saved hdr->buffer to the client. */ /* Write the saved cc->outbuf.buffer to the client. */
(void)httpd_write(hc->conn_fd, hdr->buffer, hdr->len); (void)httpd_write(cc->connfd, cc->outbuf.buffer, cc->outbuf.len);
} }
/* Then set up to read the data following the header from the CGI program and /* Then set up to read the data following the header from the CGI program and
@ -599,7 +623,7 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
* data is available on the pipe. * data is available on the pipe.
*/ */
hdr->state = CGI_OUTBUFFER_READDATA; cc->outbuf.state = CGI_OUTBUFFER_READDATA;
return 0; return 0;
case CGI_OUTBUFFER_READDATA: case CGI_OUTBUFFER_READDATA:
@ -612,8 +636,8 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
* EAGAIN is not an error, but it is still cause to return. * EAGAIN is not an error, but it is still cause to return.
*/ */
nbytes_read = read(rfd, inbuffer, sizeof(inbuffer)); nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
nllvdbg("Read %d bytes from fd %d\n", nbytes_read, rfd); nllvdbg("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);
if (nbytes_read < 0) if (nbytes_read < 0)
{ {
@ -634,16 +658,16 @@ static inline int cgi_interpose_output(httpd_conn *hc, int rfd, char *inbuffer,
if (nbytes_read == 0) if (nbytes_read == 0)
{ {
nllvdbg("End-of-file\n"); nllvdbg("End-of-file\n");
close(hc->conn_fd); close(cc->connfd);
close(rfd); close(cc->rdfd);
hdr->state = CGI_OUTBUFFER_DONE; cc->outbuf.state = CGI_OUTBUFFER_DONE;
return 1; return 1;
} }
else else
{ {
/* Forward the data from the CGI program to the client */ /* Forward the data from the CGI program to the client */
(void)httpd_write(hc->conn_fd, inbuffer, strlen(inbuffer)); (void)httpd_write(cc->connfd, cc->inbuf.buffer, strlen(cc->inbuf.buffer));
} }
} }
break; break;
@ -664,20 +688,18 @@ static int cgi_child(int argc, char **argv)
ClientData client_data; ClientData client_data;
#endif #endif
FAR char **argp; FAR char **argp;
struct cgi_outbuffer_s hdr; FAR struct cgi_conn_s *cc;
struct fdwatch_s *fw; FAR struct fdwatch_s *fw;
char *buffer; FAR char *directory;
char *directory; FAR char *dupname;
char *dupname; boolean indone;
boolean indone; boolean outdone;
boolean outdone; int child;
int child; int pipefd[2];
int pipefd[2]; int nbytes;
int wfd = -1; int fd;
int rfd = -1; int ret;
int fd; int err = 1;
int ret;
int err = 1;
/* Use low-level debug out (because the low-level output may survive closing /* Use low-level debug out (because the low-level output may survive closing
* all file descriptors * all file descriptors
@ -685,6 +707,21 @@ static int cgi_child(int argc, char **argv)
nllvdbg("Started: %s\n", argv[1]); nllvdbg("Started: %s\n", argv[1]);
/* Allocate memory and initialize memory for interposing */
cc = (FAR struct cgi_conn_s*)httpd_malloc(sizeof(struct cgi_conn_s));
if (!cc)
{
nlldbg("cgi_conn allocation failed\n");
close(hc->conn_fd);
goto errout;
}
cc->connfd = hc->conn_fd;
cc->wrfd = -1;
cc->rdfd = -1;
memset(&cc->outbuf, 0, sizeof(struct cgi_outbuffer_s));
/* Update all of the environment variable settings, these will be inherited /* Update all of the environment variable settings, these will be inherited
* by the CGI task. * by the CGI task.
*/ */
@ -724,7 +761,7 @@ static int cgi_child(int argc, char **argv)
if (ret < 0) if (ret < 0)
{ {
nlldbg("STDIN pipe: %d\n", errno); nlldbg("STDIN pipe: %d\n", errno);
goto errout_with_descriptors; goto errout_with_cgiconn;
} }
else else
{ {
@ -733,15 +770,15 @@ static int cgi_child(int argc, char **argv)
*/ */
ret = dup2(pipefd[0], 0); ret = dup2(pipefd[0], 0);
cc->wrfd = pipefd[1];
close(pipefd[0]);
if (ret < 0) if (ret < 0)
{ {
nlldbg("STDIN dup2: %d\n", errno); nlldbg("STDIN dup2: %d\n", errno);
close(pipefd[1]);
goto errout_with_descriptors; goto errout_with_descriptors;
} }
wfd = pipefd[1];
close(pipefd[0]);
} }
/* Set up the STDOUT pipe - a pipe to transfer data received from the CGI program /* Set up the STDOUT pipe - a pipe to transfer data received from the CGI program
@ -764,15 +801,15 @@ static int cgi_child(int argc, char **argv)
*/ */
ret = dup2(pipefd[1], 1); ret = dup2(pipefd[1], 1);
cc->rdfd = pipefd[0];
close(pipefd[1]);
if (ret < 0) if (ret < 0)
{ {
nlldbg("STDOUT dup2: %d\n", errno); nlldbg("STDOUT dup2: %d\n", errno);
close(pipefd[1]);
goto errout_with_descriptors; goto errout_with_descriptors;
} }
rfd = pipefd[0];
close(pipefd[1]);
} }
} }
@ -791,35 +828,27 @@ static int cgi_child(int argc, char **argv)
httpd_free(dupname); httpd_free(dupname);
} }
/* Allocate memory for buffering */ /* Allocate memory for output buffering */
memset(&hdr, 0, sizeof(struct cgi_outbuffer_s)); httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, CONFIG_THTTPD_CGIOUTBUFFERSIZE);
httpd_realloc_str(&hdr.buffer, &hdr.size, 500); if (!cc->outbuf.buffer)
if (!hdr.buffer)
{ {
nlldbg("hdr allocation failed\n"); nlldbg("hdr allocation failed\n");
goto errout_with_descriptors; goto errout_with_descriptors;
} }
buffer = (char*)httpd_malloc(CONFIG_THTTPD_IOBUFFERSIZE);
if (!buffer)
{
nlldbg("buffer allocation failed\n");
goto errout_with_header;
}
/* Create fdwatch structures */ /* Create fdwatch structures */
fw = fdwatch_initialize(2); fw = fdwatch_initialize(2);
if (!fw) if (!fw)
{ {
nlldbg("fdwatch allocation failed\n"); nlldbg("fdwatch allocation failed\n");
goto errout_with_buffer; goto errout_with_outbuffer;
} }
/* Run the CGI program. */ /* Run the CGI program. */
nllvdbg("Starting CGI\n"); nllvdbg("Starting CGI: %s\n", hc->expnfilename);
child = exec(hc->expnfilename, (FAR const char **)argp, g_thttpdsymtab, g_thttpdnsymbols); child = exec(hc->expnfilename, (FAR const char **)argp, g_thttpdsymtab, g_thttpdnsymbols);
if (child < 0) if (child < 0)
{ {
@ -842,8 +871,24 @@ static int cgi_child(int argc, char **argv)
/* Add the read descriptors to the watch */ /* Add the read descriptors to the watch */
fdwatch_add_fd(fw, hc->conn_fd, NULL); fdwatch_add_fd(fw, cc->connfd, NULL);
fdwatch_add_fd(fw, rfd, NULL); fdwatch_add_fd(fw, cc->rdfd, NULL);
/* Send any data that is already buffer to the CGI task */
nbytes = hc->read_idx - hc->checked_idx;
llvdbg("nbytes: %d contentlength: %d\n", nbytes, hc->contentlength);
if (nbytes > 0)
{
if (httpd_write(cc->wrfd, &(hc->read_buf[hc->checked_idx]), nbytes) != nbytes)
{
lldbg("httpd_write failed\n");
return 1;
}
}
cc->inbuf.contentlength = hc->contentlength;
cc->inbuf.nbytes = nbytes;
/* Then perform the interposition */ /* Then perform the interposition */
@ -851,29 +896,29 @@ static int cgi_child(int argc, char **argv)
outdone = FALSE; outdone = FALSE;
nllvdbg("Interposing\n"); nllvdbg("Interposing\n");
cgi_semgive(); cgi_semgive(); /* Not safe to reference hc after this point */
do do
{ {
(void)fdwatch(fw, 5000); (void)fdwatch(fw, 5000);
if (!indone && fdwatch_check_fd(fw, hc->conn_fd)) if (!indone && fdwatch_check_fd(fw, cc->connfd))
{ {
/* Transfer data from the client to the CGI program (POST) */ /* Transfer data from the client to the CGI program (POST) */
nllvdbg("Interpose input\n"); nllvdbg("Interpose input\n");
indone = cgi_interpose_input(hc, wfd, buffer); indone = cgi_interpose_input(cc);
if (indone) if (indone)
{ {
fdwatch_del_fd(fw, hc->conn_fd); fdwatch_del_fd(fw, cc->connfd);
} }
} }
if (fdwatch_check_fd(fw, rfd)) if (fdwatch_check_fd(fw, cc->rdfd))
{ {
/* Handle receipt of headers and CGI program response (GET) */ /* Handle receipt of headers and CGI program response (GET) */
nllvdbg("Interpose output\n"); nllvdbg("Interpose output\n");
outdone = cgi_interpose_output(hc, rfd, buffer, &hdr); outdone = cgi_interpose_output(cc);
} }
} }
while (!outdone); while (!outdone);
@ -884,27 +929,29 @@ static int cgi_child(int argc, char **argv)
errout_with_watch: errout_with_watch:
fdwatch_uninitialize(fw); fdwatch_uninitialize(fw);
/* Free memory */ /* Free output buffer memory */
errout_with_buffer: errout_with_outbuffer:
httpd_free(buffer); httpd_free(cc->outbuf.buffer);
errout_with_header:
httpd_free(hdr.buffer);
/* Close all descriptors */ /* Close all descriptors */
errout_with_descriptors: errout_with_descriptors:
close(wfd); close(cc->wrfd);
close(rfd); close(cc->rdfd);
close(hc->conn_fd);
INTERNALERROR("errout"); errout_with_cgiconn:
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); close(cc->connfd);
httpd_write_response(hc); httpd_free(cc);
nllvdbg("Return %d\n", ret);
errout:
nllvdbg("Return %d\n", err);
if (err != 0) if (err != 0)
{ {
INTERNALERROR("errout");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
httpd_write_response(hc);
cgi_semgive(); cgi_semgive();
} }
return err; return err;