nuttx-apps/netutils/thttpd/thttpd_cgi.c

1087 lines
30 KiB
C
Raw Normal View History

/****************************************************************************
* netutils/thttpd/thttpd_cgi.c
* CGI support
*
* Copyright (C) 2009, 2011, 2016 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Derived from the file libhttpd.c in the original THTTPD package:
*
* Copyright <EFBFBD> 1995,1998,1999,2000,2001 by Jef Poskanzer <jef@mail.acme.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <libgen.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/binfmt/symtab.h>
#include <nuttx/binfmt/binfmt.h>
#include "netutils/thttpd.h"
#include "config.h"
#include "timers.h"
#include "libhttpd.h"
#include "thttpd_alloc.h"
#include "thttpd_strings.h"
#include "fdwatch.h"
#if defined(CONFIG_THTTPD) && defined(CONFIG_THTTPD_CGI_PATTERN)
/****************************************************************************
* 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_dumpbuffer(m,a,n) lib_dumpbuffer(m,(FAR const uint8_t*)a,n)
#else
# define cgi_dumpbuffer(m,a,n)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
enum cgi_outbuffer_e
{
CGI_OUTBUFFER_READHEADER = 0, /* Reading header from HTTP client */
CGI_OUTBUFFER_HEADERREAD, /* Header has been read */
CGI_OUTBUFFER_HEADERSENT, /* Header has been sent to the CGI program */
CGI_OUTBUFFER_READDATA, /* Transferring data from CGI to client */
CGI_OUTBUFFER_DONE, /* Finished */
};
struct cgi_outbuffer_s
{
enum cgi_outbuffer_e state; /* State of the transfer */
char *buffer; /* Allocated I/O buffer */
size_t size; /* Size of the allocation */
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
****************************************************************************/
static void create_environment(httpd_conn *hc);
static char **make_argp(httpd_conn *hc);
static inline int cgi_interpose_input(struct cgi_conn_s *cc);
static inline int cgi_interpose_output(struct cgi_conn_s *cc);
static int cgi_child(int argc, char **argv);
/****************************************************************************
* Private Data
****************************************************************************/
/* Used to hold off the main task until the CGI tasks have been configured */
static sem_t g_cgisem;
/****************************************************************************
* Private Functions
****************************************************************************/
/* semaphore helpers */
static inline void cgi_semtake(void)
{
while (sem_wait(&g_cgisem) != 0)
{
/* The only case that an error should occr here is if the wait was\
* awakened by a signal.
*/
ASSERT(errno == EINTR);
}
}
static inline void cgi_semgive(void)
{
sem_post(&g_cgisem);
}
/* Set up environment variables. Be real careful here to avoid
* letting malicious clients overrun a buffer. We don't have
* to worry about freeing stuff since we're a sub-task.
*/
static void create_environment(httpd_conn *hc)
{
char *cp;
char buf[256];
setenv("PATH", CONFIG_THTTPD_CGI_PATH, TRUE);
#ifdef CGI_LD_LIBRARY_PATH
setenv("LD_LIBRARY_PATH", CGI_LD_LIBRARY_PATH, TRUE);
#endif /* CGI_LD_LIBRARY_PATH */
setenv("SERVER_SOFTWARE", CONFIG_THTTPD_SERVER_SOFTWARE, TRUE);
/* If vhosting, use that server-name here. */
#ifdef CONFIG_THTTPD_VHOST
if (hc->vhostname)
{
cp = hc->vhostname;
}
else
#endif
{
cp = hc->hs->hostname;
}
if (cp)
{
setenv("SERVER_NAME", cp, TRUE);
}
setenv("GATEWAY_INTERFACE", "CGI/1.1", TRUE);
setenv("SERVER_PROTOCOL", hc->protocol, TRUE);
(void)snprintf(buf, sizeof(buf), "%d", (int)CONFIG_THTTPD_PORT);
setenv("SERVER_PORT", buf, TRUE);
setenv("REQUEST_METHOD", httpd_method_str(hc->method), TRUE);
if (hc->pathinfo[0] != '\0')
{
char *cp2;
size_t l;
(void)snprintf(buf, sizeof(buf), "/%s", hc->pathinfo);
setenv("PATH_INFO", buf, TRUE);
l = strlen(httpd_root) + strlen(hc->pathinfo) + 1;
cp2 = NEW(char, l);
if (cp2)
{
(void)snprintf(cp2, l, "%s%s", httpd_root, hc->pathinfo);
setenv("PATH_TRANSLATED", cp2, TRUE);
}
}
(void)snprintf(buf, sizeof(buf), "/%s",strcmp(hc->origfilename, ".") == 0 ? "" : hc->origfilename);
setenv("SCRIPT_NAME", buf, TRUE);
if (hc->query[0] != '\0')
{
setenv("QUERY_STRING", hc->query, TRUE);
}
setenv("REMOTE_ADDR", httpd_ntoa(&hc->client_addr), TRUE);
if (hc->referer[0] != '\0')
{
setenv("HTTP_REFERER", hc->referer, TRUE);
}
if (hc->useragent[0] != '\0')
{
setenv("HTTP_USER_AGENT", hc->useragent, TRUE);
}
if (hc->accept[0] != '\0')
{
setenv("HTTP_ACCEPT", hc->accept, TRUE);
}
if (hc->accepte[0] != '\0')
{
setenv("HTTP_ACCEPT_ENCODING", hc->accepte, TRUE);
}
if (hc->acceptl[0] != '\0')
{
setenv("HTTP_ACCEPT_LANGUAGE", hc->acceptl, TRUE);
}
if (hc->cookie[0] != '\0')
{
setenv("HTTP_COOKIE", hc->cookie, TRUE);
}
if (hc->contenttype[0] != '\0')
{
setenv("CONTENT_TYPE", hc->contenttype, TRUE);
}
if (hc->hdrhost[0] != '\0')
{
setenv("HTTP_HOST", hc->hdrhost, TRUE);
}
if (hc->contentlength != -1)
{
(void)snprintf(buf, sizeof(buf), "%lu", (unsigned long)hc->contentlength);
setenv("CONTENT_LENGTH", buf, TRUE);
}
if (hc->remoteuser[0] != '\0')
{
setenv("REMOTE_USER", hc->remoteuser, TRUE);
}
if (hc->authorization[0] != '\0')
{
setenv("AUTH_TYPE", "Basic", TRUE);
}
/* We only support Basic auth at the moment. */
if (getenv("TZ") != NULL)
{
setenv("TZ", getenv("TZ"), TRUE);
}
setenv("CGI_PATTERN", CONFIG_THTTPD_CGI_PATTERN, TRUE);
}
/* Set up argument vector */
static FAR char **make_argp(httpd_conn *hc)
{
FAR char **argp;
int argn;
char *cp1;
char *cp2;
/* By allocating an arg slot for every character in the query, plus one
* for the filename and one for the NULL, we are guaranteed to have
* enough. We could actually use strlen/2.
*/
argp = NEW(char *, strlen(hc->query) + 2);
if (!argp)
{
return NULL;
}
argp[0] = strrchr(hc->expnfilename, '/');
if (argp[0])
{
++argp[0];
}
else
{
argp[0] = hc->expnfilename;
}
argn = 1;
/* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html,
* "The server should search the query information for a non-encoded =
* character to determine if the command line is to be used, if it finds
* one, the command line is not to be used."
*/
if (strchr(hc->query, '=') == NULL)
{
for (cp1 = cp2 = hc->query; *cp2 != '\0'; ++cp2)
{
if (*cp2 == '+')
{
*cp2 = '\0';
httpd_strdecode(cp1, cp1);
argp[argn++] = cp1;
cp1 = cp2 + 1;
}
}
if (cp2 != cp1)
{
httpd_strdecode(cp1, cp1);
argp[argn++] = cp1;
}
}
argp[argn] = NULL;
return argp;
}
/* Data is available from the client socket. This routine is used only for POST
* requests. It reads the data from the client and sends it to the child thread.
*/
static inline int cgi_interpose_input(struct cgi_conn_s *cc)
{
ssize_t nbytes_read;
ssize_t nbytes_written;
ninfo("nbytes: %d contentlength: %d\n", cc->inbuf.nbytes, cc->inbuf.contentlength);
if (cc->inbuf.nbytes < cc->inbuf.contentlength)
{
do
{
nbytes_read = read(cc->connfd, cc->inbuf.buffer,
MIN(CONFIG_THTTPD_CGIINBUFFERSIZE, cc->inbuf.contentlength - cc->inbuf.nbytes));
ninfo("nbytes_read: %d\n", nbytes_read);
if (nbytes_read < 0)
{
if (errno != EINTR)
{
nerr("ERROR: read failed: %d\n", errno);
return 1;
}
}
}
while (nbytes_read < 0);
if (nbytes_read > 0)
{
nbytes_written = httpd_write(cc->wrfd, cc->inbuf.buffer, nbytes_read);
ninfo("nbytes_written: %d\n", nbytes_written);
if (nbytes_written != nbytes_read)
{
nerr("ERROR: httpd_write failed\n");
return 1;
}
cgi_dumpbuffer("Sent to CGI:", cc->inbuf.buffer, nbytes_written);
}
cc->inbuf.nbytes += nbytes_read;
}
if (cc->inbuf.nbytes >= cc->inbuf.contentlength)
{
/* 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
* to 2 bytes. Unfortunately this doesn't fix the problem for CGIs
* which avoid the interposer task due to their POST data being
* short. Creating an interposer task for all POST CGIs is
* unacceptably expensive. The eventual fix will come when interposing
* gets integrated into the main loop as a tasklet instead of a task.
*/
/* Turn on no-delay mode in case we previously cleared it. */
httpd_set_ndelay(cc->connfd);
/* And read up to 2 bytes. */
(void)read(cc->connfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
return 1;
}
return 0;
}
/* This routine is used for parsed-header CGIs. The idea here is that the
* CGI can return special headers such as "Status:" and "Location:" which
* change the return status of the response. Since the return status has to
* be the very first line written out, we have to accumulate all the headers
* and check for the special ones before writing the status. Then we write
* out the saved headers and proceed to echo the rest of the response.
*/
static inline int cgi_interpose_output(struct cgi_conn_s *cc)
{
ssize_t nbytes_read;
char *br = NULL;
int status;
const char *title;
char *cp;
/* Make sure the connection is in blocking mode. It should already be
* blocking, but we might as well be sure.
*/
httpd_clear_ndelay(cc->connfd);
/* Loop while there are things we can do without waiting for more input */
ninfo("state: %d\n", cc->outbuf.state);
switch (cc->outbuf.state)
{
case CGI_OUTBUFFER_READHEADER:
{
/* Slurp in all headers as they become available from the client. */
do
{
/* Read until we successfully read data or until an error occurs.
* EAGAIN is not an error, but it is still cause to return.
*/
nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);
if (nbytes_read < 0)
{
if (errno != EINTR)
{
if (errno != EAGAIN)
{
nerr("ERROR: read: %d\n", errno);
}
return 1;
}
}
else
{
cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer, nbytes_read);
}
}
while (nbytes_read < 0);
/* Check for end-of-file */
if (nbytes_read <= 0)
{
ninfo("End-of-file\n");
br = &(cc->outbuf.buffer[cc->outbuf.len]);
cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
}
else
{
/* Accumulate more header data */
httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, cc->outbuf.len + nbytes_read);
(void)memcpy(&(cc->outbuf.buffer[cc->outbuf.len]), cc->inbuf.buffer, nbytes_read);
cc->outbuf.len += nbytes_read;
cc->outbuf.buffer[cc->outbuf.len] = '\0';
ninfo("Header bytes accumulated: %d\n", cc->outbuf.len);
/* Check for end of header */
if ((br = strstr(cc->outbuf.buffer, "\r\n\r\n")) != NULL ||
(br = strstr(cc->outbuf.buffer, "\012\012")) != NULL)
{
ninfo("End-of-header\n");
cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
}
else
{
/* All of the headers have not yet been read ... Return. We
* will be called again when more data is available in the pipe
* connected to the CGI task.
*/
return 0;
}
}
}
/* Otherwise, fall through and parse status in the HTTP headers */
case CGI_OUTBUFFER_HEADERREAD:
{
/* If there were no headers, bail. */
if (cc->outbuf.buffer[0] == '\0')
{
cc->outbuf.state = CGI_OUTBUFFER_DONE;
return 1;
}
2014-04-14 00:24:28 +02:00
/* Figure out the status. Look for a Status: or Location: header; else if
* there's an HTTP header line, get it from there; else default to 200.
*/
status = 200;
if (strncmp(cc->outbuf.buffer, "HTTP/", 5) == 0)
{
cp = cc->outbuf.buffer;
cp += strcspn(cp, " \t");
status = atoi(cp);
}
if ((cp = strstr(cc->outbuf.buffer, "Status:")) != NULL &&
cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
{
cp += 7;
cp += strspn(cp, " \t");
status = atoi(cp);
}
if ((cp = strstr(cc->outbuf.buffer, "Location:")) != NULL &&
cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
{
status = 302;
}
/* Write the status line. */
ninfo("Status: %d\n", status);
switch (status)
{
case 200:
title = ok200title;
break;
case 302:
title = err302title;
break;
case 304:
title = err304title;
break;
case 400:
BADREQUEST("status");
title = httpd_err400title;
break;
#ifdef CONFIG_THTTPD_AUTH_FILE
case 401:
title = err401title;
break;
#endif
case 403:
title = err403title;
break;
case 404:
title = err404title;
break;
case 408:
title = httpd_err408title;
break;
case 500:
INTERNALERROR("status");
title = err500title;
break;
case 501:
NOTIMPLEMENTED("status");
title = err501title;
break;
case 503:
title = httpd_err503title;
break;
default:
title = "Something";
break;
}
(void)snprintf(cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE, "HTTP/1.0 %d %s\r\n", status, title);
(void)httpd_write(cc->connfd, cc->inbuf.buffer, strlen(cc->inbuf.buffer));
/* Write the saved cc->outbuf.buffer to the client. */
(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
* pass it back to the client. We return now; we will be called again when
* data is available on the pipe.
*/
cc->outbuf.state = CGI_OUTBUFFER_READDATA;
return 0;
case CGI_OUTBUFFER_READDATA:
{
/* Read data from the pipe. */
do
{
/* Read until we successfully read data or until an error occurs.
* EAGAIN is not an error, but it is still cause to return.
*/
nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE);
ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);
if (nbytes_read < 0)
{
if (errno != EINTR)
{
if (errno != EAGAIN)
{
nerr("ERROR: read: %d\n", errno);
}
return 1;
}
}
else
{
cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer, nbytes_read);
}
}
while (nbytes_read < 0);
/* Check for end of file */
if (nbytes_read == 0)
{
ninfo("End-of-file\n");
cc->outbuf.state = CGI_OUTBUFFER_DONE;
return 1;
}
else
{
/* Forward the data from the CGI program to the client */
(void)httpd_write(cc->connfd, cc->inbuf.buffer, nbytes_read);
}
}
break;
case CGI_OUTBUFFER_DONE:
default:
return 1;
}
return 0;
}
/* CGI child task. */
static int cgi_child(int argc, char **argv)
{
FAR httpd_conn *hc = (FAR httpd_conn*)strtoul(argv[1], NULL, 16);
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
ClientData client_data;
#endif
FAR char **argp;
FAR struct cgi_conn_s *cc;
FAR struct fdwatch_s *fw;
FAR char *directory;
FAR char *dupname;
bool indone;
bool outdone;
int child;
int pipefd[2];
int nbytes;
int fd;
int ret;
int errcode = 1;
/* Use low-level debug out (because the low-level output may survive closing
* all file descriptors
*/
ninfo("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)
{
nerr("ERROR: 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
* by the CGI task.
*/
create_environment(hc);
/* Make the argument vector. */
argp = make_argp(hc);
/* Close all file descriptors EXCEPT for stdin, stdout, stderr and
* hc->conn_fd. We'll keep stderr open for error reporting; stdin and
* stdout will be closed later by dup2(). Keeping stdin and stdout open
* now prevents re-use of fd=0 and 1 by pipe().
*/
ninfo("Closing descriptors\n");
for (fd = 3; fd < (CONFIG_NFILE_DESCRIPTORS + CONFIG_NSOCKET_DESCRIPTORS); fd++)
{
/* Keep hc->conn_fd open for obvious reasons */
if (fd != hc->conn_fd)
{
close(fd);
}
}
/* Create pipes that will be interposed between the CGI task's stdin or
* stdout and the socket.
*
* Setup up the STDIN pipe - a pipe to transfer data received on the
* socket to the CGI program.
*/
ninfo("Create STDIN pipe\n");
ret = pipe(pipefd);
if (ret < 0)
{
nerr("ERROR: STDIN pipe: %d\n", errno);
goto errout_with_cgiconn;
}
else
{
/* Then map the receiving end the pipe to stdin, save the sending end, and
* closing the original receiving end
*/
ret = dup2(pipefd[0], 0);
cc->wrfd = pipefd[1];
close(pipefd[0]);
if (ret < 0)
{
nerr("ERROR: STDIN dup2: %d\n", errno);
goto errout_with_descriptors;
}
}
/* Set up the STDOUT pipe - a pipe to transfer data received from the CGI program
* to the client.
*/
if (ret == 0)
{
ninfo("Create STDOUT pipe\n");
ret = pipe(pipefd);
if (ret < 0)
{
nerr("ERROR: STDOUT pipe: %d\n", errno);
goto errout_with_descriptors;
}
else
{
/* Then map the sending end the pipe to stdout, save the receiving end, and
* closing the original sending end
*/
ret = dup2(pipefd[1], 1);
cc->rdfd = pipefd[0];
close(pipefd[1]);
2014-04-14 00:24:28 +02:00
if (ret < 0)
{
nerr("ERROR: STDOUT dup2: %d\n", errno);
goto errout_with_descriptors;
}
}
}
/* chdir to the directory containing the binary. This isn't in the CGI 1.1
* spec, but it's what other HTTP servers do.
*/
dupname = httpd_strdup(hc->expnfilename);
if (dupname)
{
directory = dirname(dupname);
if (directory)
{
(void)chdir(directory); /* ignore errors */
}
httpd_free(dupname);
}
/* Allocate memory for output buffering */
httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, CONFIG_THTTPD_CGIOUTBUFFERSIZE);
if (!cc->outbuf.buffer)
{
nerr("ERROR: hdr allocation failed\n");
goto errout_with_descriptors;
}
/* Create fdwatch structures */
fw = fdwatch_initialize(2);
if (!fw)
{
nerr("ERROR: fdwatch allocation failed\n");
goto errout_with_outbuffer;
}
/* Run the CGI program. */
ninfo("Starting CGI: %s\n", hc->expnfilename);
#ifdef CONFIG_THTTPD_NXFLAT
child = exec(hc->expnfilename, (FAR char * const *)argp, g_thttpdsymtab, g_thttpdnsymbols);
#else
child = exec(hc->expnfilename, (FAR char * const *)argp, NULL, 0);
#endif
if (child < 0)
{
/* Something went wrong. */
nerr("ERROR: execve %s: %d\n", hc->expnfilename, errno);
goto errout_with_watch;
}
/* Schedule a kill for the child task in case it runs too long. */
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
client_data.i = child;
if (tmr_create(NULL, cgi_kill, client_data, CONFIG_THTTPD_CGI_TIMELIMIT * 1000L, 0) == NULL)
{
nerr("ERROR: tmr_create(cgi_kill child) failed\n");
goto errout_with_watch;
}
#endif
/* Add the read descriptors to the watch */
fdwatch_add_fd(fw, cc->connfd, 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;
ninfo("nbytes: %d contentlength: %d\n", nbytes, hc->contentlength);
if (nbytes > 0)
{
if (httpd_write(cc->wrfd, &(hc->read_buf[hc->checked_idx]), nbytes) != nbytes)
{
nerr("ERROR: httpd_write failed\n");
return 1;
}
}
cc->inbuf.contentlength = hc->contentlength;
cc->inbuf.nbytes = nbytes;
/* Then perform the interposition */
indone = false;
outdone = false;
ninfo("Interposing\n");
cgi_semgive(); /* Not safe to reference hc after this point */
do
{
(void)fdwatch(fw, 1000);
/* Check for incoming data from the remote client to the CGI task */
if (!indone && fdwatch_check_fd(fw, cc->connfd))
{
/* Transfer data from the client to the CGI program (POST) */
ninfo("Interpose input\n");
indone = cgi_interpose_input(cc);
if (indone)
{
fdwatch_del_fd(fw, cc->connfd);
}
}
/* Check for outgoing data from the CGI task to the remote client */
if (fdwatch_check_fd(fw, cc->rdfd))
{
/* Handle receipt of headers and CGI program response (GET) */
ninfo("Interpose output\n");
outdone = cgi_interpose_output(cc);
}
/* No outgoing data... is the child task still running? Use kill()
* kill() with signal number == 0 does not actually send a signal, but
* can be used to check if the target task exists. If the task exists
* but is hung, then you might enable CONFIG_THTTPD_CGI_TIMELIMIT to
* kill the task. However, killing the task could cause other problems
* (consider resetting the microprocessor instead).
*/
else if (kill(child, 0) != 0)
{
ninfo("CGI no longer running: %d\n", errno);
outdone = true;
}
}
while (!outdone);
errcode = 0;
/* Get rid of watch structures */
errout_with_watch:
fdwatch_uninitialize(fw);
/* Free output buffer memory */
errout_with_outbuffer:
httpd_free(cc->outbuf.buffer);
/* Close all descriptors */
errout_with_descriptors:
close(cc->wrfd);
close(cc->rdfd);
errout_with_cgiconn:
close(cc->connfd);
httpd_free(cc);
errout:
ninfo("Return %d\n", errcode);
if (errcode != 0)
{
INTERNALERROR("errout");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
httpd_write_response(hc);
cgi_semgive();
}
return errcode;
}
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
int cgi(httpd_conn *hc)
{
char arg[16];
char *argv[2];
pid_t child;
int retval = ERROR;
/* Set up a semaphore to hold off the make THTTPD thread until the CGI
* threads are configured (basically until the file descriptors are all
* dup'ed and can be closed by the main thread).
*/
sem_init(&g_cgisem, 0, 0);
if (hc->method == METHOD_GET || hc->method == METHOD_POST)
{
#ifdef CONFIG_THTTPD_CGILIMIT
if (hc->hs->cgi_count >= CONFIG_THTTPD_CGILIMIT)
{
httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form,
hc->encodedurl);
goto errout_with_sem;
}
#endif
++hc->hs->cgi_count;
httpd_clear_ndelay(hc->conn_fd);
/* Start the child task. We use a trampoline task here so that we can
* safely muck with the file descriptors before actually started the CGI
* task.
*/
snprintf(arg, 16, "%p", hc); /* task_create doesn't handle binary arguments. */
argv[0] = arg;
argv[1] = NULL;
child = task_create("CGI child", CONFIG_THTTPD_CGI_PRIORITY,
CONFIG_THTTPD_CGI_STACKSIZE,
(main_t)cgi_child, (FAR char * const *)argv);
if (child < 0)
{
nerr("ERROR: task_create: %d\n", errno);
INTERNALERROR("task_create");
httpd_send_err(hc, 500, err500title, "", err500form,
hc->encodedurl);
goto errout_with_sem;
}
ninfo("Started CGI task %d for file '%s'\n", child, hc->expnfilename);
/* Wait for the CGI threads to become initialized */
cgi_semtake();
hc->bytes_sent = CONFIG_THTTPD_CGI_BYTECOUNT;
hc->should_linger = false;
}
else
{
NOTIMPLEMENTED("CGI");
httpd_send_err(hc, 501, err501title, "", err501form,
httpd_method_str(hc->method));
goto errout_with_sem;
}
/* Successfully started */
retval = OK;
errout_with_sem:
sem_destroy(&g_cgisem);
return retval;
}
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
static void cgi_kill(ClientData client_data, struct timeval *nowP)
{
pid_t pid = (pid_t)client_data.i;
/* task_delete() is a very evil API. It can leave memory stranded! */
ninfo("Killing CGI child: %d\n", pid);
if (task_delete(pid) != 0)
{
nerr("ERROR: task_delete() failed: %d\n", errno);
}
}
#endif
#endif /* CONFIG_THTTPD && CONFIG_THTTPD_CGI_PATTERN */