diff options
Diffstat (limited to 'apps/netutils/thttpd/thttpd.c')
-rw-r--r-- | apps/netutils/thttpd/thttpd.c | 861 |
1 files changed, 861 insertions, 0 deletions
diff --git a/apps/netutils/thttpd/thttpd.c b/apps/netutils/thttpd/thttpd.c new file mode 100644 index 000000000..3b49c0141 --- /dev/null +++ b/apps/netutils/thttpd/thttpd.c @@ -0,0 +1,861 @@ +/**************************************************************************** + * netutils/thttpd/thttpd.c + * Tiny HTTP Server + * + * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Derived from the file of the same name 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 <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/compiler.h> +#include <nuttx/symtab.h> +#include <apps/netutils/thttpd.h> + +#include "config.h" +#include "fdwatch.h" +#include "libhttpd.h" +#include "thttpd_alloc.h" +#include "thttpd_strings.h" +#include "timers.h" + +#ifdef CONFIG_THTTPD + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef MAXPATHLEN +# define MAXPATHLEN 64 +#endif + +/* The connection states */ + +#define CNST_FREE 0 +#define CNST_READING 1 +#define CNST_SENDING 2 +#define CNST_LINGERING 3 + +#define SPARE_FDS 2 +#define AVAILABLE_FDS (CONFIG_NSOCKET_DESCRIPTORS - SPARE_FDS) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct connect_s +{ + struct connect_s *next; + int conn_state; + httpd_conn *hc; + time_t active_at; + Timer *wakeup_timer; + Timer *linger_timer; + off_t end_offset; /* The final offset+1 of the file to send */ + off_t offset; /* The current offset into the file to send */ + bool eof; /* Set true when length==0 read from file */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static httpd_server *hs; +static struct connect_s *free_connections; +static struct connect_s *connects; +static struct fdwatch_s *fw; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void shut_down(void); +static int handle_newconnect(struct timeval *tv, int listen_fd); +static void handle_read(struct connect_s *conn, struct timeval *tv); +static void handle_send(struct connect_s *conn, struct timeval *tv); +static void handle_linger(struct connect_s *conn, struct timeval *tv); +static void finish_connection(struct connect_s *conn, struct timeval *tv); +static void clear_connection(struct connect_s *conn, struct timeval *tv); +static void really_clear_connection(struct connect_s *conn); +static void idle(ClientData client_data, struct timeval *nowP); +static void linger_clear_connection(ClientData client_data, struct timeval *nowP); +static void occasional(ClientData client_data, struct timeval *nowP); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void shut_down(void) +{ + int cnum; + + for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) + { + if (connects[cnum].conn_state != CNST_FREE) + { + httpd_close_conn(connects[cnum].hc); + } + + if (connects[cnum].hc != NULL) + { + httpd_destroy_conn(connects[cnum].hc); + httpd_free((void *)connects[cnum].hc); + connects[cnum].hc = NULL; + } + } + + if (hs) + { + httpd_server *ths = hs; + hs = NULL; + if (ths->listen_fd != -1) + { + fdwatch_del_fd(fw, ths->listen_fd); + } + httpd_terminate(ths); + } + + tmr_destroy(); + httpd_free((void *)connects); +} + +static int handle_newconnect(struct timeval *tv, int listen_fd) +{ + struct connect_s *conn; + ClientData client_data; + + /* This loops until the accept() fails, trying to start new connections as + * fast as possible so we don't overrun the listen queue. + */ + + nvdbg("New connection(s) on listen_fd %d\n", listen_fd); + for (;;) + { + /* Get the next free connection from the free list */ + + conn = free_connections; + + /* Are there any free connections? */ + + if (!conn) + { + /* Out of connection slots. Run the timers, then the existing + * connections, and maybe we'll free up a slot by the time we get + * back here. + */ + + ndbg("No free connections\n"); + tmr_run(tv); + return -1; + } + + /* Make the httpd_conn if necessary */ + + if (!conn->hc) + { + conn->hc = NEW(httpd_conn, 1); + if (conn->hc == NULL) + { + ndbg("out of memory allocating an httpd_conn\n"); + exit(1); + } + + conn->hc->initialized = 0; + } + + /* Get the connection */ + + switch (httpd_get_conn(hs, listen_fd, conn->hc)) + { + /* Some error happened. Run the timers, then the existing + * connections. Maybe the error will clear. + */ + + case GC_FAIL: + tmr_run(tv); + return -1; + + /* No more connections to accept for now */ + + case GC_NO_MORE: + return 0; + + default: + break; + } + + nvdbg("New connection fd %d\n", conn->hc->conn_fd); + + /* Remove the connection entry from the free list */ + + conn->conn_state = CNST_READING; + free_connections = conn->next; + conn->next = NULL; + + client_data.p = conn; + conn->active_at = tv->tv_sec; + conn->wakeup_timer = NULL; + conn->linger_timer = NULL; + conn->offset = 0; + + /* Set the connection file descriptor to no-delay mode */ + + httpd_set_ndelay(conn->hc->conn_fd); + fdwatch_add_fd(fw, conn->hc->conn_fd, conn); + } +} + +static void handle_read(struct connect_s *conn, struct timeval *tv) +{ + ClientData client_data; + httpd_conn *hc = conn->hc; + off_t actual; + int sz; + + /* Is there room in our buffer to read more bytes? */ + + if (hc->read_idx >= hc->read_size) + { + if (hc->read_size > CONFIG_THTTPD_MAXREALLOC) + { + BADREQUEST("MAXREALLOC"); + goto errout_with_400; + } + httpd_realloc_str(&hc->read_buf, &hc->read_size, hc->read_size + CONFIG_THTTPD_REALLOCINCR); + } + + /* Read some more bytes */ + + sz = read(hc->conn_fd, &(hc->read_buf[hc->read_idx]), hc->read_size - hc->read_idx); + if (sz == 0) + { + BADREQUEST("EOF"); + goto errout_with_400; + } + + if (sz < 0) + { + /* Ignore EINTR and EAGAIN. Also ignore EWOULDBLOCK. At first glance + * you would think that connections returned by fdwatch as readable + * should never give an EWOULDBLOCK; however, this apparently can + * happen if a packet gets garbled. + */ + + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + { + return; + } + + ndbg("read(fd=%d) failed: %d\n", hc->conn_fd, errno); + BADREQUEST("read"); + goto errout_with_400; + } + + hc->read_idx += sz; + conn->active_at = tv->tv_sec; + + /* Do we have a complete request yet? */ + + switch (httpd_got_request(hc)) + { + case GR_NO_REQUEST: + return; + case GR_BAD_REQUEST: + BADREQUEST("httpd_got_request"); + goto errout_with_400; + } + + /* Yes. Try parsing and resolving it */ + + if (httpd_parse_request(hc) < 0) + { + goto errout_with_connection; + } + + /* Start the connection going */ + + if (httpd_start_request(hc, tv) < 0) + { + /* Something went wrong. Close down the connection */ + + goto errout_with_connection; + } + + /* Set up the file offsets to read */ + + conn->eof = false; + if (hc->got_range) + { + conn->offset = hc->range_start; + conn->end_offset = hc->range_end + 1; + } + else + { + conn->offset = 0; + if (hc->bytes_to_send < 0) + { + conn->end_offset = 0; + } + else + { + conn->end_offset = hc->bytes_to_send; + } + } + + /* Check if it's already handled */ + + if (hc->file_fd < 0) + { + /* No file descriptor means someone else is handling it */ + + conn->offset = hc->bytes_sent; + goto errout_with_connection; + } + + if (conn->offset >= conn->end_offset) + { + /* There's nothing to send */ + + goto errout_with_connection; + } + + /* Seek to the offset of the next byte to send */ + + actual = lseek(hc->file_fd, conn->offset, SEEK_SET); + if (actual != conn->offset) + { + ndbg("fseek to %d failed: offset=%d errno=%d\n", conn->offset, actual, errno); + BADREQUEST("lseek"); + goto errout_with_400; + } + + /* We have a valid connection and a file to send to it */ + + conn->conn_state = CNST_SENDING; + client_data.p = conn; + fdwatch_del_fd(fw, hc->conn_fd); + return; + +errout_with_400: + BADREQUEST("errout"); + httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); + +errout_with_connection: + finish_connection(conn, tv); + return; +} + +static inline int read_buffer(struct connect_s *conn) +{ + httpd_conn *hc = conn->hc; + ssize_t nread = 0; + + if (hc->buflen < CONFIG_THTTPD_IOBUFFERSIZE && !conn->eof) + { + nread = read(hc->file_fd, &hc->buffer[hc->buflen], + CONFIG_THTTPD_IOBUFFERSIZE - hc->buflen); + if (nread == 0) + { + /* Reading zero bytes means we are at the end of file */ + + conn->end_offset = conn->offset; + conn->eof = true; + } + else if (nread > 0) + { + hc->buflen += nread; + } + } + return nread; +} + +static void handle_send(struct connect_s *conn, struct timeval *tv) +{ + httpd_conn *hc = conn->hc; + int nwritten; + int nread; + + /* Read until the entire file is sent -- this could take awhile!! */ + + while (conn->offset < conn->end_offset) + { + nvdbg("offset: %d end_offset: %d bytes_sent: %d\n", + conn->offset, conn->end_offset, conn->hc->bytes_sent); + + /* Fill the rest of the response buffer with file data */ + + nread = read_buffer(conn); + if (nread < 0) + { + ndbg("File read error: %d\n", errno); + goto errout_clear_connection; + } + nvdbg("Read %d bytes, buflen %d\n", nread, hc->buflen); + + /* Send the buffer */ + + if (hc->buflen > 0) + { + /* httpd_write does not return until all bytes have been sent + * (or an error occurs). + */ + + nwritten = httpd_write(hc->conn_fd, hc->buffer, hc->buflen); + if (nwritten < 0) + { + ndbg("Error sending %s: %d\n", hc->encodedurl, errno); + goto errout_clear_connection; + } + + /* We wrote one full buffer of data (httpd_write does not + * return until the full buffer is written (or an error occurs). + */ + + conn->active_at = tv->tv_sec; + hc->buflen = 0; + + /* And update how much of the file we wrote */ + + conn->offset += nwritten; + conn->hc->bytes_sent += nwritten; + nvdbg("Wrote %d bytes\n", nwritten); + } + } + + /* The file transfer is complete -- finish the connection */ + + nvdbg("Finish connection\n"); + finish_connection(conn, tv); + return; + +errout_clear_connection: + ndbg("Clear connection\n"); + clear_connection(conn, tv); + return; +} + +static void handle_linger(struct connect_s *conn, struct timeval *tv) +{ + httpd_conn *hc = conn->hc; + int ret; + + /* In lingering-close mode we just read and ignore bytes. An error or EOF + * ends things, otherwise we go until a timeout + */ + + ret = read(conn->hc->conn_fd, hc->buffer, CONFIG_THTTPD_IOBUFFERSIZE); + if (ret < 0 && (errno == EINTR || errno == EAGAIN)) + { + return; + } + + if (ret <= 0) + { + really_clear_connection(conn); + } +} + +static void finish_connection(struct connect_s *conn, struct timeval *tv) +{ + /* If we haven't actually sent the buffered response yet, do so now */ + + httpd_write_response(conn->hc); + + /* And clear */ + + clear_connection(conn, tv); +} + +static void clear_connection(struct connect_s *conn, struct timeval *tv) +{ + ClientData client_data; + + if (conn->wakeup_timer != NULL) + { + tmr_cancel(conn->wakeup_timer); + conn->wakeup_timer = 0; + } + + /* This is our version of Apache's lingering_close() routine, which is + * their version of the often-broken SO_LINGER socket option. For why + * this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html + * What we do is delay the actual closing for a few seconds, while reading + * any bytes that come over the connection. However, we don't want to do + * this unless it's necessary, because it ties up a connection slot and + * file descriptor which means our maximum connection-handling rateis + * lower. So, elsewhere we set a flag when we detect the few + * circumstances that make a lingering close necessary. If the flag isn't + * set we do the real close now. + */ + + if (conn->conn_state == CNST_LINGERING) + { + /* If we were already lingering, shut down for real */ + + tmr_cancel(conn->linger_timer); + conn->linger_timer = NULL; + conn->hc->should_linger = false; + } + else if (conn->hc->should_linger) + { + fdwatch_del_fd(fw, conn->hc->conn_fd); + conn->conn_state = CNST_LINGERING; + fdwatch_add_fd(fw, conn->hc->conn_fd, conn); + client_data.p = conn; + + conn->linger_timer = tmr_create(tv, linger_clear_connection, client_data, + CONFIG_THTTPD_LINGER_MSEC, 0); + if (conn->linger_timer != NULL) + { + return; + } + ndbg("tmr_create(linger_clear_connection) failed\n"); + } + + /* Either we are done lingering, we shouldn't linger, or we failed to setup the linger */ + + really_clear_connection(conn); +} + +static void really_clear_connection(struct connect_s *conn) +{ + fdwatch_del_fd(fw, conn->hc->conn_fd); + httpd_close_conn(conn->hc); + if (conn->linger_timer != NULL) + { + tmr_cancel(conn->linger_timer); + conn->linger_timer = 0; + } + + /* Put the connection structure back on the free list */ + + conn->conn_state = CNST_FREE; + conn->next = free_connections; + free_connections = conn; +} + +static void idle(ClientData client_data, struct timeval *nowP) +{ + int cnum; + struct connect_s *conn; + + for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) + { + conn = &connects[cnum]; + switch (conn->conn_state) + { + case CNST_READING: + if (nowP->tv_sec - conn->active_at >= CONFIG_THTTPD_IDLE_READ_LIMIT_SEC) + { + ndbg("%s connection timed out reading\n", httpd_ntoa(&conn->hc->client_addr)); + httpd_send_err(conn->hc, 408, httpd_err408title, "", + httpd_err408form, ""); + finish_connection(conn, nowP); + } + break; + + case CNST_SENDING: + if (nowP->tv_sec - conn->active_at >= CONFIG_THTTPD_IDLE_SEND_LIMIT_SEC) + { + ndbg("%s connection timed out sending\n", httpd_ntoa(&conn->hc->client_addr)); + clear_connection(conn, nowP); + } + break; + } + } +} + +static void linger_clear_connection(ClientData client_data, struct timeval *nowP) +{ + struct connect_s *conn; + + nvdbg("Clear connection\n"); + conn = (struct connect_s *) client_data.p; + conn->linger_timer = NULL; + really_clear_connection(conn); +} + +static void occasional(ClientData client_data, struct timeval *nowP) +{ + tmr_cleanup(); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: thttpd_main + * + * Description: + * This function is the entrypoint into the THTTPD server. It does not + * return. It may be called, the normal mechanism for starting the server + * is: + * + * 1) Set is g_thttpdsymtab and g_thttpdnsymbols. The user is required + * to provide a symbol table to use for binding CGI programs (if CGI + * is enabled. See examples/nxflat and examples/thttpd for examples of + * how such a symbol table may be created. + * 2) Call task_create() to start thttpd_main() + * + ****************************************************************************/ + +int thttpd_main(int argc, char **argv) +{ + int num_ready; + int cnum; + FAR struct connect_s *conn; + FAR httpd_conn *hc; + httpd_sockaddr sa; + struct timeval tv; +#ifdef CONFIG_THTTPD_DIR + int ret; +#endif + + nvdbg("THTTPD started\n"); + + /* Setup host address */ + +#ifdef CONFIG_NET_IPv6 +# error "IPv6 support not yet implemented" +#else + sa.sin_family = AF_INET; + sa.sin_port = HTONS(CONFIG_THTTPD_PORT); + sa.sin_addr.s_addr = HTONL(CONFIG_THTTPD_IPADDR); +#endif + + /* Initialize the fdwatch package to handle all of the configured + * socket descriptors + */ + + fw = fdwatch_initialize(CONFIG_NSOCKET_DESCRIPTORS); + if (!fw) + { + ndbg("fdwatch initialization failure\n"); + exit(1); + } + + /* Switch directories again if requested */ + +#ifdef CONFIG_THTTPD_DATADIR + if (chdir(CONFIG_THTTPD_DATADIR) < 0) + { + ndbg("chdir to %s: %d\n", CONFIG_THTTPD_DATADIR, errno); + exit(1); + } +#endif + + /* Initialize the timer package */ + + tmr_init(); + + /* Initialize the HTTP layer */ + + nvdbg("Calling httpd_initialize()\n"); + hs = httpd_initialize(&sa); + if (!hs) + { + ndbg("httpd_initialize() failed\n"); + exit(1); + } + + /* Set up the occasional timer */ + + if (tmr_create(NULL, occasional, JunkClientData, CONFIG_THTTPD_OCCASIONAL_MSEC * 1000L, 1) == NULL) + { + ndbg("tmr_create(occasional) failed\n"); + exit(1); + } + + /* Set up the idle timer */ + + if (tmr_create(NULL, idle, JunkClientData, 5 * 1000L, 1) == NULL) + { + ndbg("tmr_create(idle) failed\n"); + exit(1); + + } + + /* Initialize our connections table */ + + connects = NEW(struct connect_s, AVAILABLE_FDS); + if (connects == NULL) + { + ndbg("Out of memory allocating a struct connect_s\n"); + exit(1); + } + + for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) + { + connects[cnum].conn_state = CNST_FREE; + connects[cnum].next = &connects[cnum + 1]; + connects[cnum].hc = NULL; + } + + connects[AVAILABLE_FDS-1].next = NULL; /* End of link list */ + free_connections = connects; /* Beginning of the link list */ + + if (hs != NULL) + { + if (hs->listen_fd != -1) + { + fdwatch_add_fd(fw, hs->listen_fd, NULL); + } + } + + /* Main loop */ + + nvdbg("Entering the main loop\n"); + (void)gettimeofday(&tv, NULL); + for(;;) + { + /* Do the fd watch */ + + num_ready = fdwatch(fw, tmr_mstimeout(&tv)); + if (num_ready < 0) + { + if (errno == EINTR || errno == EAGAIN) + { + /* Not errors... try again */ + + continue; + } + + ndbg("fdwatch failed: %d\n", errno); + exit(1); + } + + (void)gettimeofday(&tv, NULL); + + if (num_ready == 0) + { + /* No fd's are ready - run the timers */ + + tmr_run(&tv); + continue; + } + + /* Is it a new connection? */ + + if (fdwatch_check_fd(fw, hs->listen_fd)) + { + if (!handle_newconnect(&tv, hs->listen_fd)) + { + /* Go around the loop and do another fdwatch, rather than + * dropping through and processing existing connections. New + * connections always get priority. + */ + + continue; + } + } + + /* Find the connections that need servicing */ + + while ((conn = (struct connect_s*)fdwatch_get_next_client_data(fw)) != (struct connect_s*)-1) + { + if (conn) + { + hc = conn->hc; + if (fdwatch_check_fd(fw, hc->conn_fd)) + { + nvdbg("Handle conn_state %d\n", conn->conn_state); + switch (conn->conn_state) + { + case CNST_READING: + { + handle_read(conn, &tv); + + /* If a GET request was received and a file is ready to + * be sent, then fall through to send the file. + */ + + if (conn->conn_state != CNST_SENDING) + { + break; + } + } + + case CNST_SENDING: + { + /* Send a file -- this really should be performed on a + * separate thread to keep the serve from locking up during + * the write. + */ + + handle_send(conn, &tv); + } + break; + + case CNST_LINGERING: + { + /* Linger close the connection */ + + handle_linger(conn, &tv); + } + break; + } + } + } + } + tmr_run(&tv); + } + + /* The main loop terminated */ + + shut_down(); + ndbg("Exiting\n"); + exit(0); +} + +#endif /* CONFIG_THTTPD */ + |