dab972b907
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1114 lines
30 KiB
C
1114 lines
30 KiB
C
/****************************************************************************
|
|
* apps/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 © 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 <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.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.
|
|
*/
|
|
|
|
DEBUGASSERT(errno == EINTR || errno == ECANCELED);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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)
|
|
{
|
|
snprintf(cp2, l, "%s%s", httpd_root, hc->pathinfo);
|
|
setenv("PATH_TRANSLATED", cp2, TRUE);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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. */
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
snprintf(cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE,
|
|
"HTTP/1.0 %d %s\r\n", status, title);
|
|
httpd_write(cc->connfd, cc->inbuf.buffer,
|
|
strlen(cc->inbuf.buffer));
|
|
|
|
/* Write the saved cc->outbuf.buffer to the client. */
|
|
|
|
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 */
|
|
|
|
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_THTTPD_NFILE_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]);
|
|
|
|
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)
|
|
{
|
|
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, argp, NULL,
|
|
g_thttpdsymtab, g_thttpdnsymbols);
|
|
#else
|
|
child = exec(hc->expnfilename, argp, NULL, 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
|
|
{
|
|
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, cgi_child, 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 */
|