nuttx-apps/netutils/thttpd/libhttpd.c
Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

3519 lines
90 KiB
C

/****************************************************************************
* netutils/thttpd/libhttpd.c
* HTTP Protocol Library
*
* Copyright (C) 2009, 2011, 2013, 2015-2016 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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 <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <strings.h>
#include <stdarg.h>
#include <ctype.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <sched.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/lib/regex.h>
#include "netutils/thttpd.h"
#include "config.h"
#include "timers.h"
#include "libhttpd.h"
#include "thttpd_alloc.h"
#include "thttpd_strings.h"
#include "thttpd_cgi.h"
#include "tdate_parse.h"
#include "fdwatch.h"
#ifdef CONFIG_THTTPD
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef STDIN_FILENO
# define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif
#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif
#define NAMLEN(dirent) strlen((dirent)->d_name)
extern CODE char *crypt(const char *key, const char *setting);
#ifndef MAX
# define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
/* Conditional macro to allow two alternate forms for use in the built-in
* error pages. If EXPLICIT_ERROR_PAGES is defined, the second and more
* explicit error form is used; otherwise, the first and more generic
* form is used.
*/
#ifdef EXPLICIT_ERROR_PAGES
# define ERROR_FORM(a,b) b
#else
# define ERROR_FORM(a,b) a
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void free_httpd_server(httpd_server *hs);
static int initialize_listen_socket(httpd_sockaddr *saP);
static void add_response(httpd_conn *hc, const char *str);
static void send_mime(httpd_conn *hc, int status, const char *title, const char *encodings,
const char *extraheads, const char *type, off_t length, time_t mod);
static void send_response(httpd_conn *hc, int status, const char *title,
const char *extraheads, const char *form, const char *arg);
static void send_response_tail(httpd_conn *hc);
static void defang(const char *str, char *dfstr, int dfsize);
#ifdef CONFIG_THTTPD_ERROR_DIRECTORY
static int send_err_file(httpd_conn *hc, int status, char *title,
char *extraheads, char *filename);
#endif
#ifdef CONFIG_THTTPD_AUTH_FILE
static void send_authenticate(httpd_conn *hc, char *realm);
static int b64_decode(const char *str, unsigned char *space, int size);
static int auth_check(httpd_conn *hc, char *dirname);
static int auth_check2(httpd_conn *hc, char *dirname);
#endif
static void send_dirredirect(httpd_conn *hc);
#ifdef CONFIG_THTTPD_TILDE_MAP1
static int httpd_tilde_map1(httpd_conn *hc);
#endif
#ifdef CONFIG_THTTPD_TILDE_MAP2
static int httpd_tilde_map2(httpd_conn *hc);
#endif
#ifdef CONFIG_THTTPD_VHOST
static int vhost_map(httpd_conn *hc);
#endif
static char *expand_filename(char *path, char **restP, bool tildemapped);
static char *bufgets(httpd_conn *hc);
static void de_dotdot(char *file);
static void init_mime(void);
static void figure_mime(httpd_conn *hc);
#ifdef CONFIG_THTTPD_GENERATE_INDICES
static void ls_child(int argc, char **argv);
static int ls(httpd_conn *hc);
#endif
#ifdef SERVER_NAME_LIST
static char *hostname_map(char *hostname);
#endif
static int check_referer(httpd_conn *hc);
#ifdef CONFIG_THTTPD_URLPATTERN
static int really_check_referer(httpd_conn *hc);
#endif
#ifdef CONFIG_DEBUG_FEATURES_FEATURES
static int sockaddr_check(httpd_sockaddr *saP);
#else
# define sockaddr_check(saP) (1)
#endif
static size_t sockaddr_len(httpd_sockaddr *saP);
/****************************************************************************
* Private Data
****************************************************************************/
/* This global keeps track of whether we are in the main task or a
* sub-task. The reason is that httpd_write_response() can get called
* in either context; when it is called from the main task it must use
* non-blocking I/O to avoid stalling the server, but when it is called
* from a sub-task it wants to use blocking I/O so that the whole
* response definitely gets written. So, it checks this variable. A bit
* of a hack but it seems to do the right thing.
*/
static pid_t main_thread;
/* Include MIME encodings and types */
#include "mime_types.h"
/* Names for index file */
static const char *index_names[] = { CONFIG_THTTPD_INDEX_NAMES };
/****************************************************************************
* Private Functions
****************************************************************************/
static void free_httpd_server(httpd_server * hs)
{
if (hs)
{
if (hs->hostname)
{
httpd_free(hs->hostname);
}
httpd_free(hs);
}
}
static int initialize_listen_socket(httpd_sockaddr *saP)
{
int listen_fd;
int on;
int flags;
/* Check sockaddr. */
#ifdef CONFIG_DEBUG_FEATURES_FEATURES
if (!sockaddr_check(saP))
{
nerr("ERROR: unknown sockaddr family on listen socket\n");
return -1;
}
#endif
/* Create socket. */
ninfo("Create listen socket\n");
listen_fd = socket(saP->sin_family, SOCK_STREAM, 0);
if (listen_fd < 0)
{
nerr("ERROR: socket failed: %d\n", errno);
return -1;
}
/* Allow reuse of local addresses. */
on = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
{
nerr("ERROR: setsockopt(SO_REUSEADDR) failed: %d\n", errno);
}
/* Bind to it. */
if (bind(listen_fd, (struct sockaddr*)saP, sockaddr_len(saP)) < 0)
{
nerr("ERROR: bind to %s failed: %d\n", httpd_ntoa(saP), errno);
close(listen_fd);
return -1;
}
/* Set the listen file descriptor to no-delay / non-blocking mode. */
flags = fcntl(listen_fd, F_GETFL, 0);
if (flags == -1)
{
nerr("ERROR: fcntl(F_GETFL) failed: %d\n", errno);
close(listen_fd);
return -1;
}
if (fcntl(listen_fd, F_SETFL, flags | O_NDELAY) < 0)
{
nerr("ERROR: fcntl(O_NDELAY) failed: %d\n", errno);
close(listen_fd);
return -1;
}
/* Start a listen going. */
if (listen(listen_fd, CONFIG_THTTPD_LISTEN_BACKLOG) < 0)
{
nerr("ERROR: listen failed: %d\n", errno);
close(listen_fd);
return -1;
}
return listen_fd;
}
/* Append a string to the buffer waiting to be sent as response. */
static void add_response(httpd_conn *hc, const char *str)
{
int resplen;
int len;
len = strlen(str);
resplen = hc->buflen + len;
if (resplen > CONFIG_THTTPD_IOBUFFERSIZE)
{
nwarn("WARNING: resplen(%d) > buffer size(%d)\n",
resplen, CONFIG_THTTPD_IOBUFFERSIZE);
resplen = CONFIG_THTTPD_IOBUFFERSIZE;
len = resplen - hc->buflen;
}
memcpy(&(hc->buffer[hc->buflen]), str, len);
hc->buflen = resplen;
}
static void send_mime(httpd_conn *hc, int status, const char *title, const char *encodings,
const char *extraheads, const char *type, off_t length, time_t mod)
{
struct timeval now;
const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";
char tmbuf[72];
#ifdef CONFIG_THTTPD_MAXAGE
time_t expires;
char expbuf[72];
#endif
char fixed_type[72];
char buf[128];
int partial_content;
int s100;
hc->bytes_to_send = length;
if (hc->mime_flag)
{
if (status == 200 && hc->got_range &&
(hc->range_end >= hc->range_start) &&
((hc->range_end != length - 1) ||
(hc->range_start != 0)) &&
(hc->range_if == (time_t) - 1 || hc->range_if == hc->sb.st_mtime))
{
partial_content = 1;
status = 206;
title = ok206title;
}
else
{
partial_content = 0;
hc->got_range = false;
}
gettimeofday(&now, NULL);
if (mod == (time_t)0)
{
mod = now.tv_sec;
}
snprintf(fixed_type, sizeof(fixed_type), type, CONFIG_THTTPD_CHARSET);
snprintf(buf, sizeof(buf), "%.20s %d %s\r\n", hc->protocol, status, title);
add_response(hc, buf);
snprintf(buf, sizeof(buf), "Server: %s\r\n", "thttpd");
add_response(hc, buf);
snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", fixed_type);
add_response(hc, buf);
strftime(tmbuf, sizeof(tmbuf), rfc1123fmt, gmtime(&now.tv_sec));
snprintf(buf, sizeof(buf), "Date: %s\r\n", tmbuf);
add_response(hc, buf);
strftime(tmbuf, sizeof(tmbuf), rfc1123fmt, gmtime(&mod));
snprintf(buf, sizeof(buf), "Last-Modified: %s\r\n", tmbuf);
add_response(hc, buf);
add_response(hc, "Accept-Ranges: bytes\r\n");
add_response(hc, "Connection: close\r\n");
s100 = status / 100;
if (s100 != 2 && s100 != 3)
{
snprintf(buf, sizeof(buf), "Cache-Control: no-cache,no-store\r\n");
add_response(hc, buf);
}
if (encodings[0] != '\0')
{
snprintf(buf, sizeof(buf), "Content-Encoding: %s\r\n", encodings);
add_response(hc, buf);
}
if (partial_content)
{
snprintf(buf, sizeof(buf),"Content-Range: bytes %ld-%ld/%ld\r\n",
(long)hc->range_start, (long)hc->range_end, (long)length);
add_response(hc, buf);
snprintf(buf, sizeof(buf),"Content-Length: %ld\r\n",
(long)(hc->range_end - hc->range_start + 1));
add_response(hc, buf);
}
else if (length >= 0)
{
snprintf(buf, sizeof(buf), "Content-Length: %ld\r\n", (long)length);
add_response(hc, buf);
}
#ifdef CONFIG_THTTPD_P3P
snprintf(buf, sizeof(buf), "P3P: %s\r\n", CONFIG_THTTPD_P3P);
add_response(hc, buf);
#endif
#ifdef CONFIG_THTTPD_MAXAGE
expires = now + CONFIG_THTTPD_MAXAGE;
strftime(expbuf, sizeof(expbuf), rfc1123fmt, gmtime(&expires));
snprintf(buf, sizeof(buf),
"Cache-Control: max-age=%d\r\nExpires: %s\r\n",
CONFIG_THTTPD_MAXAGE, expbuf);
add_response(hc, buf);
#endif
if (extraheads[0] != '\0')
{
add_response(hc, extraheads);
}
add_response(hc, "\r\n");
}
}
static void send_response(httpd_conn *hc, int status, const char *title, const char *extraheads,
const char *form, const char *arg)
{
char defanged[72];
char buf[128];
ninfo("title: \"%s\" form: \"%s\"\n", title, form);
send_mime(hc, status, title, "", extraheads, "text/html; charset=%s", (off_t)-1, (time_t)0);
add_response(hc, html_html);
add_response(hc, html_hdtitle);
snprintf(buf, sizeof(buf), "%d %s", status, title);
add_response(hc, buf);
add_response(hc, html_titlehd);
add_response(hc, html_body);
add_response(hc, html_hdr2);
add_response(hc, buf);
add_response(hc, html_endhdr2);
defang(arg, defanged, sizeof(defanged));
snprintf(buf, sizeof(buf), form, defanged);
add_response(hc, buf);
if (match("**MSIE**", hc->useragent))
{
int n;
add_response(hc, "<!--\n");
for (n = 0; n < 6; ++n)
add_response(hc,
"Padding so that MSIE deigns to show this error instead of its own canned one.\n");
add_response(hc, "-->\n");
}
send_response_tail(hc);
}
static void send_response_tail(httpd_conn *hc)
{
add_response(hc, "<HR>\r\n<ADDRESS><A HREF=\"");
add_response(hc, CONFIG_THTTPD_SERVER_ADDRESS);
add_response(hc, "\">");
add_response(hc, "thttpd");
add_response(hc, "</A></ADDRESS>\r\n");
add_response(hc, html_endbody);
add_response(hc, html_endhtml);
}
static void defang(const char *str, char *dfstr, int dfsize)
{
const char *cp1;
char *cp2;
for (cp1 = str, cp2 = dfstr;
*cp1 != '\0' && cp2 - dfstr < dfsize - 5; ++cp1, ++cp2)
{
switch (*cp1)
{
case '<':
*cp2++ = '&';
*cp2++ = 'l';
*cp2++ = 't';
*cp2 = ';';
break;
case '>':
*cp2++ = '&';
*cp2++ = 'g';
*cp2++ = 't';
*cp2 = ';';
break;
default:
*cp2 = *cp1;
break;
}
}
*cp2 = '\0';
}
#ifdef CONFIG_THTTPD_ERROR_DIRECTORY
static int send_err_file(httpd_conn *hc, int status, char *title, char *extraheads,
char *filename)
{
FILE *fp;
char buf[1000];
size_t nread;
fp = fopen(filename, "r");
if (fp == NULL)
{
return 0;
}
send_mime(hc, status, title, "", extraheads, "text/html; charset=%s",
(off_t)-1, (time_t)0);
for (;;)
{
nread = fread(buf, 1, sizeof(buf) - 1, fp);
if (nread == 0)
break;
buf[nread] = '\0';
add_response(hc, buf);
}
fclose(fp);
#ifdef ERR_APPEND_SERVER_INFO
send_response_tail(hc);
#endif
return 1;
}
#endif /* CONFIG_THTTPD_ERROR_DIRECTORY */
#ifdef CONFIG_THTTPD_AUTH_FILE
static void send_authenticate(httpd_conn *hc, char *realm)
{
static char *header;
static size_t maxheader = 0;
static char headstr[] = "WWW-Authenticate: Basic realm=\"";
httpd_realloc_str(&header, &maxheader, sizeof(headstr) + strlen(realm) + 3);
snprintf(header, maxheader, "%s%s\"\r\n", headstr, realm);
httpd_send_err(hc, 401, err401title, header, err401form, hc->encodedurl);
/* If the request was a POST then there might still be data to be read, so
* we need to do a lingering close.
*/
if (hc->method == METHOD_POST)
{
hc->should_linger = true;
}
}
/* Base-64 decoding. This represents binary data as printable ASCII
* characters. Three 8-bit binary bytes are turned into four 6-bit
* values, like so:
*
* [11111111][22222222][33333333] -> [111111][112222][222233][333333]
*
* Then the 6-bit values are represented using the characters "A-Za-z0-9+/".
*/
static inline b64_charmap(char *ch)
{
char bin6;
bin6 = -1;
if (c == 0x20) /* ' ' */
{
bin6 = 62; /* ' ' maps to 62 */
}
elseif (c == 0x2f) /* '/' */
{
bin6 = 63; /* '/' maps to 63 */
}
else if (c >= 0x30) /* '0' */
{
else if (c <= 0x39) /* '9' */
{
bin6 = (c - 0x39 + 52); /* '0'-'9' maps to 52-61 */
}
else if (c >= 0x41) /* 'A' */
{
if (c <= 0x5a) /* 'Z' */
{
bin6 = c - 0x41; /* 'A'-'Z' map to 0 - 25 */
}
else if (c >= 0x61) /* 'a' */
{
if (c <= 0x7a) /* 'z' */
{
bin6 = c - 0x61 + 26; /* 'a'-'z' map to 0 - 25 */
}
}
}
}
}
/* Do base-64 decoding on a string. Ignore any non-base64 bytes.
* Return the actual number of bytes generated. The decoded size will
* be at most 3/4 the size of the encoded, and may be smaller if there
* are padding characters (blanks, newlines).
*/
static int b64_decode(const char *str, unsigned char *space, int size)
{
const char *cp;
int ndx;
int phase;
int decoded;
int prev_decoded = 0;
unsigned char packed;
ndx = 0;
phase = 0;
for (cp = str; *cp != '\0', ndx < size; cp++)
{
/* Decode base-64 */
decoded = b64_charmap(*cp); /* Decode ASCII representations to 6-bit binary */
if (decoded != -1)
{
switch (phase)
{
case 0:
phase = 1;
break;
case 1:
space[ndx++] = ((prev_decoded << 2) | ((decoded & 0x30) >> 4));
phase = 2;
break;
case 2:
space[ndx++] =(((prev_decoded & 0xf) << 4) | ((decoded & 0x3packed) >> 2));
phase = 3;
break;
case 3:
space[ndx++] =(((prev_decoded & 0x03) << 6) | decoded);
phase = 0;
break;
}
prev_decoded = decoded;
}
}
return ndx;
}
/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
static int auth_check(httpd_conn *hc, char *dirname)
{
#ifdef CONFIG_THTTPD_GLOBALPASSWD
char *topdir;
#ifdef CONFIG_THTTPD_VHOST
if (hc->hostdir[0] != '\0')
{
topdir = hc->hostdir;
}
else
#endif
{
topdir = httpd_root;
}
switch (auth_check2(hc, topdir))
{
case -1:
return -1;
case 1:
return 1;
}
#endif /* CONFIG_THTTPD_GLOBALPASSWD */
return auth_check2(hc, dirname);
}
/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
static int auth_check2(httpd_conn *hc, char *dirname)
{
static char *authpath;
static size_t maxauthpath = 0;
struct stat sb;
char authinfo[500];
char *authpass;
char *colon;
int l;
FILE *fp;
char line[500];
char *cryp;
static char *prevauthpath;
static size_t maxprevauthpath = 0;
static time_t prevmtime;
static char *prevuser;
static size_t maxprevuser = 0;
static char *prevcryp;
static size_t maxprevcryp = 0;
/* Construct auth filename. */
httpd_realloc_str(&authpath, &maxauthpath,
strlen(dirname) + 1 + sizeof(CONFIG_THTTPD_AUTH_FILE));
snprintf(authpath, maxauthpath, "%s/%s", dirname, CONFIG_THTTPD_AUTH_FILE);
/* Does this directory have an auth file? */
if (stat(authpath, &sb) < 0)
{
/* Nope, let the request go through. */
return 0;
}
/* Does this request contain basic authorization info? */
if (hc->authorization[0] == '\0' || strncmp(hc->authorization, "Basic ", 6) != 0)
{
/* Nope, return a 401 Unauthorized. */
send_authenticate(hc, dirname);
return -1;
}
/* Decode it. */
l = b64_decode(&(hc->authorization[6]), (unsigned char *)authinfo, sizeof(authinfo) - 1);
authinfo[l] = '\0';
/* Split into user and password. */
authpass = strchr(authinfo, ':');
if (!authpass)
{
/* No colon? Bogus auth info. */
send_authenticate(hc, dirname);
return -1;
}
*authpass++ = '\0';
/* If there are more fields, cut them off. */
colon = strchr(authpass, ':');
if (colon)
{
*colon = '\0';
}
/* See if we have a cached entry and can use it. */
if (maxprevauthpath != 0 &&
strcmp(authpath, prevauthpath) == 0 &&
sb.st_mtime == prevmtime && strcmp(authinfo, prevuser) == 0)
{
/* Yes. Check against the cached encrypted password. */
if (strcmp(crypt(authpass, prevcryp), prevcryp) == 0)
{
/* Ok! */
httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser,
strlen(authinfo));
strcpy(hc->remoteuser, authinfo);
return 1;
}
else
{
/* No. */
send_authenticate(hc, dirname);
return -1;
}
}
/* Open the password file. */
fp = fopen(authpath, "r");
if (fp == NULL)
{
/* The file exists but we can't open it? Disallow access. */
nerr("ERROR: %s auth file %s could not be opened: %d\n",
httpd_ntoa(&hc->client_addr), authpath, errno);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' is protected by an authentication file, "
"but the authentication file cannot be opened.\n"),
hc->encodedurl);
return -1;
}
/* Read it. */
while (fgets(line, sizeof(line), fp) != NULL)
{
/* Nuke newline. */
l = strlen(line);
if (line[l - 1] == '\n')
{
line[l - 1] = '\0';
}
/* Split into user and encrypted password. */
cryp = strchr(line, ':');
if (!cryp)
{
continue;
}
*cryp++ = '\0';
/* Is this the right user? */
if (strcmp(line, authinfo) == 0)
{
/* Yes. */
fclose(fp);
/* So is the password right? */
if (strcmp(crypt(authpass, cryp), cryp) == 0)
{
/* Ok! */
httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser, strlen(line));
strcpy(hc->remoteuser, line);
/* And cache this user's info for next time. */
httpd_realloc_str(&prevauthpath, &maxprevauthpath, strlen(authpath));
strcpy(prevauthpath, authpath);
prevmtime = sb.st_mtime;
httpd_realloc_str(&prevuser, &maxprevuser, strlen(authinfo));
strcpy(prevuser, authinfo);
httpd_realloc_str(&prevcryp, &maxprevcryp, strlen(cryp));
strcpy(prevcryp, cryp);
return 1;
}
else
{
/* No. */
send_authenticate(hc, dirname);
return -1;
}
}
}
/* Didn't find that user. Access denied. */
fclose(fp);
send_authenticate(hc, dirname);
return -1;
}
#endif /* CONFIG_THTTPD_AUTH_FILE */
static void send_dirredirect(httpd_conn *hc)
{
static char *location;
static char *header;
static size_t maxlocation = 0;
static size_t maxheader = 0;
static char headstr[] = "Location: ";
if (hc->query[0] != '\0')
{
char *cp = strchr(hc->encodedurl, '?');
if (cp)
{
*cp = '\0';
}
httpd_realloc_str(&location, &maxlocation, strlen(hc->encodedurl) + 2 + strlen(hc->query));
snprintf(location, maxlocation, "%s/?%s", hc->encodedurl, hc->query);
}
else
{
httpd_realloc_str(&location, &maxlocation, strlen(hc->encodedurl) + 1);
snprintf(location, maxlocation, "%s/", hc->encodedurl);
}
httpd_realloc_str(&header, &maxheader, sizeof(headstr) + strlen(location));
snprintf(header, maxheader, "%s%s\r\n", headstr, location);
send_response(hc, 302, err302title, header, err302form, location);
}
/* Map a ~username/whatever URL into <prefix>/username. */
#ifdef CONFIG_THTTPD_TILDE_MAP1
static int httpd_tilde_map1(httpd_conn *hc)
{
static char *temp;
static size_t maxtemp = 0;
int len;
static char *prefix = CONFIG_THTTPD_TILDE_MAP1;
len = strlen(hc->expnfilename) - 1;
httpd_realloc_str(&temp, &maxtemp, len);
strcpy(temp, &hc->expnfilename[1]);
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(prefix) + 1 + len);
strcpy(hc->expnfilename, prefix);
if (prefix[0] != '\0')
{
strcat(hc->expnfilename, "/");
}
strcat(hc->expnfilename, temp);
return 1;
}
#endif /* CONFIG_THTTPD_TILDE_MAP1 */
/* Map a ~username/whatever URL into <user's homedir>/<postfix>. */
#ifdef CONFIG_THTTPD_TILDE_MAP2
static int httpd_tilde_map2(httpd_conn *hc)
{
static char *temp;
static size_t maxtemp = 0;
static char *postfix = CONFIG_THTTPD_TILDE_MAP2;
char *cp;
struct passwd *pw;
char *alt;
char *rest;
/* Get the username. */
httpd_realloc_str(&temp, &maxtemp, strlen(hc->expnfilename) - 1);
strcpy(temp, &hc->expnfilename[1]);
cp = strchr(temp, '/');
if (cp)
{
*cp++ = '\0';
}
else
{
cp = "";
}
/* Get the passwd entry. */
pw = getpwnam(temp);
if (!pw)
{
return 0;
}
/* Set up altdir. */
httpd_realloc_str(&hc->altdir, &hc->maxaltdir, strlen(pw->pw_dir) + 1 + strlen(postfix));
strcpy(hc->altdir, pw->pw_dir);
if (postfix[0] != '\0')
{
strcat(hc->altdir, "/");
strcat(hc->altdir, postfix);
}
alt = expand_filename(hc->altdir, &rest, true);
if (rest[0] != '\0')
{
return 0;
}
httpd_realloc_str(&hc->altdir, &hc->maxaltdir, strlen(alt));
strcpy(hc->altdir, alt);
/* And the filename becomes altdir plus the post-~ part of the original. */
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(hc->altdir) + 1 + strlen(cp));
snprintf(hc->expnfilename, hc->maxexpnfilename, "%s/%s", hc->altdir, cp);
/* For this type of tilde mapping, we want to defeat vhost mapping. */
hc->tildemapped = true;
return 1;
}
#endif /* CONFIG_THTTPD_TILDE_MAP2 */
/* Virtual host mapping. */
#ifdef CONFIG_THTTPD_VHOST
static int vhost_map(httpd_conn *hc)
{
httpd_sockaddr sa;
socklen_t sz;
static char *tempfilename;
static size_t maxtempfilename = 0;
char *cp1;
int len;
#ifdef VHOST_DIRLEVELS
int i;
char *cp2;
#endif
/* Figure out the virtual hostname. */
if (hc->reqhost[0] != '\0')
{
hc->vhostname = hc->reqhost;
}
else if (hc->hdrhost[0] != '\0')
{
hc->vhostname = hc->hdrhost;
}
else
{
sz = sizeof(sa);
if (getsockname(hc->conn_fd, &sa.sa, &sz) < 0)
{
nerr("ERROR: getsockname: %d\n", errno);
return 0;
}
hc->vhostname = httpd_ntoa(&sa);
}
/* Pound it to lower case. */
for (cp1 = hc->vhostname; *cp1 != '\0'; ++cp1)
{
if (isupper(*cp1))
{
*cp1 = tolower(*cp1);
}
}
if (hc->tildemapped)
{
return 1;
}
/* Figure out the host directory. */
#ifdef VHOST_DIRLEVELS
httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, strlen(hc->vhostname) + 2 * VHOST_DIRLEVELS);
if (strncmp(hc->vhostname, "www.", 4) == 0)
{
cp1 = &hc->vhostname[4];
}
else
{
cp1 = hc->vhostname;
}
for (cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i)
{
/* Skip dots in the hostname. If we don't, then we get vhost
* directories in higher level of filestructure if dot gets involved
* into path construction. It's `while' used here instead of `if' for
* it's possible to have a hostname formed with two dots at the end of
* it.
*/
while (*cp1 == '.')
{
++cp1;
}
/* Copy a character from the hostname, or '_' if we ran out. */
if (*cp1 != '\0')
{
*cp2++ = *cp1++;
}
else
{
*cp2++ = '_';
}
/* Copy a slash. */
*cp2++ = '/';
}
strcpy(cp2, hc->vhostname);
#else /* VHOST_DIRLEVELS */
httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, strlen(hc->vhostname));
strcpy(hc->hostdir, hc->vhostname);
#endif /* VHOST_DIRLEVELS */
/* Prepend hostdir to the filename. */
len = strlen(hc->expnfilename);
httpd_realloc_str(&tempfilename, &maxtempfilename, len);
strcpy(tempfilename, hc->expnfilename);
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(hc->hostdir) + 1 + len);
strcpy(hc->expnfilename, hc->hostdir);
strcat(hc->expnfilename, "/");
strcat(hc->expnfilename, tempfilename);
return 1;
}
#endif
/* Expands filename, deleting ..'s and leading /'s.
* Returns the expanded path (pointer to static string), or NULL on
* errors. Also returns, in the string pointed to by restP, any trailing
* parts of the path that don't exist.
*/
static char *expand_filename(char *path, char **restP, bool tildemapped)
{
static char *checked;
static char *rest;
static size_t maxchecked = 0, maxrest = 0;
size_t checkedlen;
size_t restlen;
#if 0 // REVISIT
struct stat sb;
#endif
char *r;
char *cp1;
char *cp2;
int i;
ninfo("path: \"%s\"\n", path);
#if 0 // REVISIT
/* We need to do the pathinfo check. we do a single stat() of the whole
* filename - if it exists, then we return it as is with nothing in restP.
* If it doesn't exist, we fall through to the existing code.
*/
if (stat(path, &sb) != -1)
{
checkedlen = strlen(path);
httpd_realloc_str(&checked, &maxchecked, checkedlen);
strcpy(checked, path);
/* Trim trailing slashes. */
while (checked[checkedlen - 1] == '/')
{
checked[checkedlen - 1] = '\0';
--checkedlen;
}
httpd_realloc_str(&rest, &maxrest, 0);
rest[0] = '\0';
*restP = rest;
return checked;
}
#endif /* 0 */
/* Handle leading / or . and relative pathes by copying the default directory into checked */
if ((path[0] == '/' && strncmp(path, httpd_root, strlen(httpd_root)) != 0) || path[0] != '/')
{
/* Start out with httpd_root in checked. Allow space in the reallocation
* include NULL terminator and possibly a '/'
*/
checkedlen = strlen(httpd_root);
httpd_realloc_str(&checked, &maxchecked, checkedlen+2);
strcpy(checked, httpd_root);
/* Skip over leading '.' */
if (path[0] == '.')
{
path++;
}
/* Add '/' to separate relative pathes */
else if (path[0] != '/')
{
checked[checkedlen] = '/';
checked[checkedlen+1] = '\0';
}
}
else
{
/* Start out with nothing in checked */
httpd_realloc_str(&checked, &maxchecked, 1);
checked[0] = '\0';
checkedlen = 0;
}
/* Copy the whole filename (minus the leading '.') into rest. */
restlen = strlen(path);
httpd_realloc_str(&rest, &maxrest, restlen+1);
strcpy(rest, path);
/* trim trailing slash */
if (rest[restlen - 1] == '/')
{
rest[--restlen] = '\0';
}
r = rest;
/* While there are still components to check... */
while (restlen > 0)
{
/* Grab one component from r and transfer it to checked. */
cp1 = strchr(r, '/');
if (cp1)
{
i = cp1 - r;
if (i == 0)
{
/* Special case for absolute paths. */
httpd_realloc_str(&checked, &maxchecked, checkedlen + 1);
strncpy(&checked[checkedlen], r, 1);
checkedlen += 1;
}
else if (strncmp(r, "..", MAX(i, 2)) == 0)
{
/* Ignore ..'s that go above the start of the path. */
if (checkedlen != 0)
{
cp2 = strrchr(checked, '/');
if (!cp2)
{
checkedlen = 0;
}
else if (cp2 == checked)
{
checkedlen = 1;
}
else
{
checkedlen = cp2 - checked;
}
}
}
else
{
httpd_realloc_str(&checked, &maxchecked, checkedlen + 1 + i);
if (checkedlen > 0 && checked[checkedlen - 1] != '/')
{
checked[checkedlen++] = '/';
}
strncpy(&checked[checkedlen], r, i);
checkedlen += i;
}
checked[checkedlen] = '\0';
r += i + 1;
restlen -= i + 1;
}
else
{
/* No slashes remaining, r is all one component. */
if (strcmp(r, "..") == 0)
{
/* Ignore ..'s that go above the start of the path. */
if (checkedlen != 0)
{
cp2 = strrchr(checked, '/');
if (!cp2)
{
checkedlen = 0;
}
else if (cp2 == checked)
{
checkedlen = 1;
}
else
{
checkedlen = cp2 - checked;
}
checked[checkedlen] = '\0';
}
}
else
{
httpd_realloc_str(&checked, &maxchecked, checkedlen + 1 + restlen);
if (checkedlen > 0 && checked[checkedlen - 1] != '/')
{
checked[checkedlen++] = '/';
}
strcpy(&checked[checkedlen], r);
checkedlen += restlen;
}
r += restlen;
restlen = 0;
}
}
/* Ok. */
*restP = r;
if (checked[0] == '\0')
{
strcpy(checked, httpd_root);
}
ninfo("checked: \"%s\"\n", checked);
return checked;
}
static char *bufgets(httpd_conn *hc)
{
int i;
char c;
for (i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx)
{
c = hc->read_buf[hc->checked_idx];
if (c == '\012' || c == '\015')
{
hc->read_buf[hc->checked_idx] = '\0';
++hc->checked_idx;
if (c == '\015' && hc->checked_idx < hc->read_idx &&
hc->read_buf[hc->checked_idx] == '\012')
{
hc->read_buf[hc->checked_idx] = '\0';
++hc->checked_idx;
}
return &(hc->read_buf[i]);
}
}
return NULL;
}
static void de_dotdot(char *file)
{
char *cp;
char *cp2;
int l;
/* Collapse any multiple / sequences. */
while ((cp = strstr(file, "//")) != NULL)
{
for (cp2 = cp + 2; *cp2 == '/'; ++cp2)
{
continue;
}
strcpy(cp + 1, cp2);
}
/* Remove leading ./ and any /./ sequences. */
while (strncmp(file, "./", 2) == 0)
{
strcpy(file, file + 2);
}
while ((cp = strstr(file, "/./")) != NULL)
{
strcpy(cp, cp + 2);
}
/* Alternate between removing leading ../ and removing xxx/../ */
for (;;)
{
while (strncmp(file, "../", 3) == 0)
{
strcpy(file, file + 3);
}
cp = strstr(file, "/../");
if (!cp)
{
break;
}
for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
{
continue;
}
strcpy(cp2 + 1, cp + 4);
}
/* Also elide any xxx/.. at the end. */
while ((l = strlen(file)) > 3 && strcmp((cp = file + l - 3), "/..") == 0)
{
for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
{
continue;
}
if (cp2 < file)
{
break;
}
*cp2 = '\0';
}
}
static void init_mime(void)
{
int i;
/* Fill in the lengths. */
for (i = 0; i < n_enc_tab; ++i)
{
enc_tab[i].ext_len = strlen(enc_tab[i].ext);
enc_tab[i].val_len = strlen(enc_tab[i].val);
}
for (i = 0; i < n_typ_tab; ++i)
{
typ_tab[i].ext_len = strlen(typ_tab[i].ext);
typ_tab[i].val_len = strlen(typ_tab[i].val);
}
}
/* Figure out MIME encodings and type based on the filename. Multiple
* encodings are separated by commas, and are listed in the order in
* which they were applied to the file.
*/
static void figure_mime(httpd_conn *hc)
{
char *prev_dot;
char *dot;
char *ext;
int me_indexes[100], n_me_indexes;
size_t ext_len, encodings_len;
int i, top, bot, mid;
int r;
char *default_type = "text/plain; charset=%s";
/* Peel off encoding extensions until there aren't any more. */
n_me_indexes = 0;
for (prev_dot = &hc->expnfilename[strlen(hc->expnfilename)];; prev_dot = dot)
{
for (dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot)
;
if (dot < hc->expnfilename)
{
/* No dot found. No more encoding extensions, and no type
* extension either.
*/
hc->type = default_type;
goto done;
}
ext = dot + 1;
ext_len = prev_dot - ext;
/* Search the encodings table. Linear search is fine here, there are
* only a few entries.
*/
for (i = 0; i < n_enc_tab; ++i)
{
if (ext_len == enc_tab[i].ext_len &&
strncasecmp(ext, enc_tab[i].ext, ext_len) == 0)
{
if (n_me_indexes < sizeof(me_indexes) / sizeof(*me_indexes))
{
me_indexes[n_me_indexes] = i;
++n_me_indexes;
}
goto next;
}
}
/* No encoding extension found. Break and look for a type extension. */
break;
next:;
}
/* Binary search for a matching type extension. */
top = n_typ_tab - 1;
bot = 0;
while (top >= bot)
{
mid = (top + bot) / 2;
r = strncasecmp(ext, typ_tab[mid].ext, ext_len);
if (r < 0)
{
top = mid - 1;
}
else if (r > 0)
{
bot = mid + 1;
}
else if (ext_len < typ_tab[mid].ext_len)
{
top = mid - 1;
}
else if (ext_len > typ_tab[mid].ext_len)
{
bot = mid + 1;
}
else
{
hc->type = typ_tab[mid].val;
goto done;
}
}
hc->type = default_type;
done:
/* The last thing we do is actually generate the mime-encoding header. */
hc->encodings[0] = '\0';
encodings_len = 0;
for (i = n_me_indexes - 1; i >= 0; --i)
{
httpd_realloc_str(&hc->encodings, &hc->maxencodings,
encodings_len + enc_tab[me_indexes[i]].val_len + 1);
if (hc->encodings[0] != '\0')
{
strcpy(&hc->encodings[encodings_len], ",");
++encodings_len;
}
strcpy(&hc->encodings[encodings_len], enc_tab[me_indexes[i]].val);
encodings_len += enc_tab[me_indexes[i]].val_len;
}
}
/* qsort comparison routine. */
#ifdef CONFIG_THTTPD_GENERATE_INDICES
static int name_compare(FAR const void *a, FAR const void *b)
{
return strcmp(*((FAR char **)a), *((FAR char **)b));
}
static void ls_child(int argc, char **argv)
{
FAR httpd_conn *hc = (FAR httpd_conn*)strtoul(argv[1], NULL, 16);
DIR *dirp;
struct dirent *de;
int namlen;
static int maxnames = 0;
int oldmax;
int nnames;
static char *names;
static char **nameptrs;
static char *name;
static size_t maxname = 0;
static char *rname;
static size_t maxrname = 0;
static char *encrname;
static size_t maxencrname = 0;
FILE *fp;
struct stat sb;
char modestr[20];
char *linkprefix;
#if 0
char link[PATH_MAX + 1];
#else
char link[1];
#endif
char *fileclass;
time_t now;
char *timestr;
int i;
httpd_unlisten(hc->hs);
send_mime(hc, 200, ok200title, "", "", "text/html; charset=%s",
(off_t) - 1, hc->sb.st_mtime);
httpd_write_response(hc);
/* Open a stdio stream so that we can use fprintf, which is more
* efficient than a bunch of separate write()s. We don't have to
* worry about double closes or file descriptor leaks cause we're
* in a sub-task.
*/
fp = fdopen(hc->conn_fd, "w");
if (fp == NULL)
{
nerr("ERROR: fdopen: %d\n", errno);
INTERNALERROR("fdopen");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
httpd_write_response(hc);
closedir(dirp);
exit(1);
}
fputs(html_html, fp);
fputs(html_hdtitle, fp);
fprintf(fp, "Index of %s", hc->encodedurl, hc->encodedurl);
fputs(html_titlehd, fp);
fputs(html_body, fp);
fputs(html_hdr2, fp);
fprintf(fp, "Index of %s", hc->encodedurl, hc->encodedurl);
fputs(html_endhdr2, fp);
fputs(html_crlf, fp);
fputs("<PRE>\r\nmode links bytes last-changed name\r\n<HR>", fp);
/* Read in names. */
nnames = 0;
while ((de = readdir(dirp)) != 0) /* dirent or direct */
{
if (nnames >= maxnames)
{
if (maxnames == 0)
{
maxnames = 100;
names = NEW(char, maxnames * (PATH_MAX + 1));
nameptrs = NEW(char*, maxnames);
}
else
{
oldmax = maxnames;
maxnames *= 2;
names = RENEW(names, char, oldmax*(PATH_MAX+1), maxnames*(PATH_MAX + 1));
nameptrs = RENEW(nameptrs, char*, oldmax, maxnames);
}
if (!names || !nameptrs)
{
nerr("ERROR: out of memory reallocating directory names\n");
exit(1);
}
for (i = 0; i < maxnames; ++i)
{
nameptrs[i] = &names[i * (PATH_MAX + 1)];
}
}
namlen = NAMLEN(de);
strncpy(nameptrs[nnames], de->d_name, namlen);
nameptrs[nnames][namlen] = '\0';
++nnames;
}
closedir(dirp);
/* Sort the names. */
qsort(nameptrs, nnames, sizeof(*nameptrs), name_compare);
/* Generate output. */
for (i = 0; i < nnames; ++i)
{
httpd_realloc_str(&name, &maxname,
strlen(hc->expnfilename) + 1 +
strlen(nameptrs[i]));
httpd_realloc_str(&rname, &maxrname,
strlen(hc->origfilename) + 1 +
strlen(nameptrs[i]));
if (hc->expnfilename[0] == '\0' || strcmp(hc->expnfilename, ".") == 0)
{
strcpy(name, nameptrs[i]);
strcpy(rname, nameptrs[i]);
}
else
{
snprintf(name, maxname, "%s/%s", hc->expnfilename, nameptrs[i]);
if (strcmp(hc->origfilename, ".") == 0)
{
snprintf(rname, maxrname, "%s", nameptrs[i]);
}
else
{
snprintf(rname, maxrname, "%s%s", hc->origfilename, nameptrs[i]);
}
}
httpd_realloc_str(&encrname, &maxencrname, 3 * strlen(rname) + 1);
httpd_strencode(encrname, maxencrname, rname);
if (stat(name, &sb) < 0)
{
continue;
}
linkprefix = "";
link[0] = '\0';
/* Break down mode word. First the file type. */
switch (sb.st_mode & S_IFMT)
{
case S_IFIFO:
modestr[0] = 'p';
break;
case S_IFCHR:
modestr[0] = 'c';
break;
case S_IFDIR:
modestr[0] = 'd';
break;
case S_IFBLK:
modestr[0] = 'b';
break;
case S_IFREG:
modestr[0] = '-';
break;
case S_IFSOCK:
modestr[0] = 's';
break;
case S_IFLNK:
default:
modestr[0] = '?';
break;
}
/* Now the world permissions. Owner and group permissions are
* not of interest to web clients.
*/
modestr[1] = (sb.st_mode & S_IROTH) ? 'r' : '-';
modestr[2] = (sb.st_mode & S_IWOTH) ? 'w' : '-';
modestr[3] = (sb.st_mode & S_IXOTH) ? 'x' : '-';
modestr[4] = '\0';
/* We also leave out the owner and group name */
/* Get time string. */
now = time(NULL);
timestr = ctime(&sb.st_mtime);
timestr[0] = timestr[4];
timestr[1] = timestr[5];
timestr[2] = timestr[6];
timestr[3] = ' ';
timestr[4] = timestr[8];
timestr[5] = timestr[9];
timestr[6] = ' ';
if (now - sb.st_mtime > 60 * 60 * 24 * 182) /* 1/2 year */
{
timestr[7] = ' ';
timestr[8] = timestr[20];
timestr[9] = timestr[21];
timestr[10] = timestr[22];
timestr[11] = timestr[23];
}
else
{
timestr[7] = timestr[11];
timestr[8] = timestr[12];
timestr[9] = ':';
timestr[10] = timestr[14];
timestr[11] = timestr[15];
}
timestr[12] = '\0';
/* The ls -F file class. */
switch (sb.st_mode & S_IFMT)
{
case S_IFDIR:
fileclass = "/";
break;
case S_IFSOCK:
fileclass = "=";
break;
case S_IFLNK:
fileclass = "@";
break;
default:
fileclass = (sb.st_mode & S_IXOTH) ? "*" : "";
break;
}
/* And print. */
fprintf(fp,
"%s %3ld %10lld %s <A HREF=\"/%.500s%s\">%s</A>%s%s%s\n",
modestr, 0, (int16_t)sb.st_size, timestr, encrname,
S_ISDIR(sb.st_mode) ? "/" : "", nameptrs[i], linkprefix,
link, fileclass);
}
fputs("</PRE>", fp);
fputs(html_endbody, fp);
fputs(html_endhtml, fp);
fclose(fp);
exit(0);
}
static int ls(httpd_conn *hc)
{
DIR *dirp;
int child;
char arg[16];
char *argv[1];
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
ClientData client_data;
#endif
dirp = opendir(hc->expnfilename);
if (dirp == NULL)
{
nerr("ERROR: opendir %s: %d\n", hc->expnfilename, errno);
httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl);
return -1;
}
if (hc->method == METHOD_HEAD)
{
closedir(dirp);
send_mime(hc, 200, ok200title, "", "", "text/html; charset=%s",
(off_t) - 1, hc->sb.st_mtime);
}
else if (hc->method == METHOD_GET)
{
#ifdef CONFIG_THTTPD_CGILIMIT
if (hc->hs->cgi_count >= CONFIG_THTTPD_CGILIMIT)
{
closedir(dirp);
httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form,
hc->encodedurl);
return -1;
}
#endif
++hc->hs->cgi_count;
/* Start the child task. */
snprintf(arg, 16, "%p", hc); /* task_create doesn't handle binary arguments. */
argv[0] = arg;
child = task_create("CGI child", CONFIG_THTTPD_CGI_PRIORITY,
CONFIG_THTTPD_CGI_STACKSIZE,
(main_t)ls_child, (FAR char * const *)argv);
if (child < 0)
{
nerr("ERROR: task_create: %d\n", errno);
closedir(dirp);
INTERNALERROR("task_create");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
closedir(dirp);
nerr("ERROR: spawned indexing task %d for directory '%s'\n",
child, hc->expnfilename);
/* 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 ls) failed\n");
exit(1);
}
#endif
hc->bytes_sent = CONFIG_THTTPD_CGI_BYTECOUNT;
hc->should_linger = false;
}
else
{
closedir(dirp);
NOTIMPLEMENTED(httpd_method_str(hc->method));
httpd_send_err(hc, 501, err501title, "", err501form, httpd_method_str(hc->method));
return -1;
}
return 0;
}
#endif /* CONFIG_THTTPD_GENERATE_INDICES */
/* Returns 1 if ok to serve the url, 0 if not. */
static int check_referer(httpd_conn *hc)
{
/* Are we doing referer checking at all? */
#ifdef CONFIG_THTTPD_URLPATTERN
char *cp;
int r;
int child;
child = really_check_referer(hc);
if (!r)
{
#ifdef CONFIG_THTTPD_VHOST
if (hc->vhostname != NULL)
{
cp = hc->vhostname;
}
else
#endif
{
cp = hc->hs->hostname;
}
if (cp == NULL)
{
cp = "";
}
ninfo("%s non-local referer \"%s%s\" \"%s\"\n",
httpd_ntoa(&hc->client_addr), cp, hc->encodedurl, hc->referer);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"You must supply a local referer to get URL '%s' from this server.\n"),
hc->encodedurl);
}
return r;
#else /* CONFIG_THTTPD_URLPATTERN */
return 1;
#endif /* CONFIG_THTTPD_URLPATTERN */
}
/* Returns 1 if ok to serve the url, 0 if not. */
#ifdef CONFIG_THTTPD_URLPATTERN
static int really_check_referer(httpd_conn *hc)
{
httpd_server *hs;
char *cp1;
char *cp2;
char *cp3;
static char *refhost = NULL;
static size_t refhost_size = 0;
char *lp;
hs = hc->hs;
/* Check for an empty referer. */
if (hc->referer == NULL || hc->referer[0] == '\0' ||
(cp1 = strstr(hc->referer, "//")) == NULL)
{
/* Disallow if the url matches. */
if (match(CONFIG_THTTPD_URLPATTERN, hc->origfilename))
{
return 0;
}
/* Otherwise ok. */
return 1;
}
/* Extract referer host. */
cp1 += 2;
for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2)
{
continue;
}
httpd_realloc_str(&refhost, &refhost_size, cp2 - cp1);
for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3)
if (isupper(*cp1))
{
*cp3 = tolower(*cp1);
}
else
{
*cp3 = *cp1;
}
*cp3 = '\0';
/* Local pattern? */
#ifdef CONFIG_THTTPD_LOCALPATTERN
lp = CONFIG_THTTPD_LOCALPATTERN;
#else
/* No local pattern. What's our hostname? */
#ifndef CONFIG_THTTPD_VHOST
/* Not vhosting, use the server name. */
lp = hs->hostname;
if (!lp)
{
/* Couldn't figure out local hostname - give up. */
return 1;
}
#else
/* We are vhosting, use the hostname on this connection. */
lp = hc->vhostname;
if (!lp)
{
/* Oops, no hostname. Maybe it's an old browser that doesn't
* send a Host: header. We could figure out the default
* hostname for this IP address, but it's not worth it for the
* few requests like this.
*/
return 1;
}
#endif /* CONFIG_THTTPD_VHOST */
#endif /* CONFIG_THTTPD_LOCALPATTERN */
/* If the referer host doesn't match the local host pattern, and the
* filename does match the url pattern, it's an illegal reference.
*/
if (!match(lp, refhost) && match(CONFIG_THTTPD_URLPATTERN, hc->origfilename))
{
return 0;
}
/* Otherwise ok. */
return 1;
}
#endif /* CONFIG_THTTPD_URLPATTERN */
#ifdef CONFIG_DEBUG_FEATURES_FEATURES
static int sockaddr_check(httpd_sockaddr *saP)
{
switch (saP->sin_family)
{
case AF_INET:
return 1;
#ifdef CONFIG_NET_IPv6
case AF_INET6:
return 1;
#endif
default:
return 0;
}
}
#endif /* CONFIG_DEBUG_FEATURES_FEATURES */
static size_t sockaddr_len(httpd_sockaddr *saP)
{
switch (saP->sin_family)
{
case AF_INET:
return sizeof(struct sockaddr_in);
#ifdef CONFIG_NET_IPv6
case AF_INET6:
return sizeof(struct sockaddr_in6);
#endif
default:
break;
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
FAR httpd_server *httpd_initialize(FAR httpd_sockaddr *sa)
{
FAR httpd_server *hs;
/* Save the PID of the main thread */
main_thread = getpid();
/* Allocate the server structure */
hs = (FAR httpd_server *)zalloc(sizeof(httpd_server));
if (!hs)
{
nerr("ERROR: out of memory allocating an httpd_server\n");
return NULL;
}
#ifdef CONFIG_THTTPD_HOSTNAME
hs->hostname = httpd_strdup(CONFIG_THTTPD_HOSTNAME);
#else
hs->hostname = httpd_strdup(httpd_ntoa(sa));
#endif
ninfo("hostname: %s\n", hs->hostname);
if (!hs->hostname)
{
nerr("ERROR: out of memory copying hostname\n");
return NULL;
}
hs->cgi_count = 0;
/* Initialize listen sockets */
hs->listen_fd = initialize_listen_socket(sa);
if (hs->listen_fd == -1)
{
nerr("ERROR: Failed to create listen socket\n");
free_httpd_server(hs);
return NULL;
}
init_mime();
/* Done initializing. */
ninfo("%s starting on port %d\n", CONFIG_THTTPD_SERVER_SOFTWARE, (int)CONFIG_THTTPD_PORT);
return hs;
}
void httpd_terminate(httpd_server * hs)
{
httpd_unlisten(hs);
free_httpd_server(hs);
}
void httpd_unlisten(httpd_server * hs)
{
if (hs->listen_fd != -1)
{
close(hs->listen_fd);
hs->listen_fd = -1;
}
}
/* Send the buffered response. */
void httpd_write_response(httpd_conn *hc)
{
/* If we are in a sub-task, turn off no-delay mode. */
if (main_thread != getpid())
{
httpd_clear_ndelay(hc->conn_fd);
}
/* Send the response, if necessary. */
if (hc->buflen > 0)
{
httpd_write(hc->conn_fd, hc->buffer, hc->buflen);
hc->buflen = 0;
}
}
/* Set no-delay / non-blocking mode on a socket. */
void httpd_set_ndelay(int fd)
{
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if (flags != -1)
{
newflags = flags | (int)O_NDELAY;
if (newflags != flags)
fcntl(fd, F_SETFL, newflags);
}
}
/* Clear no-delay / non-blocking mode on a socket. */
void httpd_clear_ndelay(int fd)
{
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if (flags != -1)
{
newflags = flags & ~(int)O_NDELAY;
if (newflags != flags)
{
fcntl(fd, F_SETFL, newflags);
}
}
}
void httpd_send_err(httpd_conn *hc, int status, const char *title, const char *extraheads,
const char *form, const char *arg)
{
#ifdef CONFIG_THTTPD_ERROR_DIRECTORY
char filename[1000];
/* Try virtual host error page. */
ninfo("title: \"%s\" form: \"%s\"\n", title, form);
#ifdef CONFIG_THTTPD_VHOST
if (hc->hostdir[0] != '\0')
{
snprintf(filename, sizeof(filename),
"%s/%s/err%d.html", hc->hostdir, CONFIG_THTTPD_ERROR_DIRECTORY, status);
if (send_err_file(hc, status, title, extraheads, filename))
{
ninfo("Sent VHOST error file\n");
return;
}
}
#endif /* CONFIG_THTTPD_VHOST */
/* Try server-wide error page. */
snprintf(filename, sizeof(filename), "%s/err%d.html", CONFIG_THTTPD_ERROR_DIRECTORY, status);
if (send_err_file(hc, status, title, extraheads, filename))
{
ninfo("Sent server-wide error page\n");
return;
}
/* Fall back on built-in error page. */
send_response(hc, status, title, extraheads, form, arg);
#else /* CONFIG_THTTPD_ERROR_DIRECTORY */
send_response(hc, status, title, extraheads, form, arg);
#endif /* CONFIG_THTTPD_ERROR_DIRECTORY */
}
const char *httpd_method_str(int method)
{
switch (method)
{
case METHOD_GET:
return "GET";
case METHOD_HEAD:
return "HEAD";
case METHOD_POST:
return "POST";
default:
return "UNKNOWN";
}
}
int httpd_get_conn(httpd_server *hs, int listen_fd, httpd_conn *hc)
{
httpd_sockaddr sa;
socklen_t sz;
if (!hc->initialized)
{
hc->read_size = 0;
httpd_realloc_str(&hc->read_buf, &hc->read_size, CONFIG_THTTPD_IOBUFFERSIZE);
hc->maxdecodedurl =
hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings =
hc->maxpathinfo = hc->maxquery = hc->maxaccept =
hc->maxaccepte = hc->maxreqhost = hc->maxhostdir =
hc->maxremoteuser = 0;
#ifdef CONFIG_THTTPD_TILDE_MAP2
hc->maxaltdir = 0;
#endif
httpd_realloc_str(&hc->decodedurl, &hc->maxdecodedurl, 1);
httpd_realloc_str(&hc->origfilename, &hc->maxorigfilename, 1);
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, 0);
httpd_realloc_str(&hc->encodings, &hc->maxencodings, 0);
httpd_realloc_str(&hc->pathinfo, &hc->maxpathinfo, 0);
httpd_realloc_str(&hc->query, &hc->maxquery, 0);
httpd_realloc_str(&hc->accept, &hc->maxaccept, 0);
httpd_realloc_str(&hc->accepte, &hc->maxaccepte, 0);
httpd_realloc_str(&hc->reqhost, &hc->maxreqhost, 0);
httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, 0);
httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser, 0);
#ifdef CONFIG_THTTPD_TILDE_MAP2
httpd_realloc_str(&hc->altdir, &hc->maxaltdir, 0);
#endif
hc->initialized = 1;
}
/* Accept the new connection. */
ninfo("accept() new connection on listen_fd %d\n", listen_fd);
sz = sizeof(sa);
hc->conn_fd = accept(listen_fd, (struct sockaddr*)&sa, &sz);
if (hc->conn_fd < 0)
{
if (errno == EWOULDBLOCK)
{
return GC_NO_MORE;
}
nerr("ERROR: accept failed: %d\n", errno);
return GC_FAIL;
}
#ifdef CONFIG_DEBUG_FEATURES_FEATURES
if (!sockaddr_check(&sa))
{
nerr("ERROR: unknown sockaddr family\n");
close(hc->conn_fd);
hc->conn_fd = -1;
return GC_FAIL;
}
#endif
hc->hs = hs;
memset(&hc->client_addr, 0, sizeof(hc->client_addr));
memmove(&hc->client_addr, &sa, sockaddr_len(&sa));
hc->read_idx = 0;
hc->checked_idx = 0;
hc->checked_state = CHST_FIRSTWORD;
hc->method = METHOD_UNKNOWN;
hc->bytes_to_send = 0;
hc->bytes_sent = 0;
hc->encodedurl = "";
hc->decodedurl[0] = '\0';
hc->protocol = "UNKNOWN";
hc->origfilename[0] = '\0';
hc->expnfilename[0] = '\0';
hc->encodings[0] = '\0';
hc->pathinfo[0] = '\0';
hc->query[0] = '\0';
hc->referer = "";
hc->useragent = "";
hc->accept[0] = '\0';
hc->accepte[0] = '\0';
hc->acceptl = "";
hc->cookie = "";
hc->contenttype = "";
hc->reqhost[0] = '\0';
hc->hdrhost = "";
hc->hostdir[0] = '\0';
hc->authorization = "";
hc->remoteuser[0] = '\0';
hc->buffer[0] = '\0';
#ifdef CONFIG_THTTPD_TILDE_MAP2
hc->altdir[0] = '\0';
#endif
hc->buflen = 0;
hc->if_modified_since = (time_t) - 1;
hc->range_if = (time_t)-1;
hc->contentlength = -1;
hc->type = "";
#ifdef CONFIG_THTTPD_VHOST
hc->vhostname = NULL;
#endif
hc->mime_flag = true;
hc->one_one = false;
hc->got_range = false;
hc->tildemapped = false;
hc->range_start = 0;
hc->range_end = -1;
hc->keep_alive = false;
hc->should_linger = false;
hc->file_fd = -1;
ninfo("New connection accepted on %d\n", hc->conn_fd);
return GC_OK;
}
/* Checks hc->read_buf to see whether a complete request has been read so far;
* either the first line has two words (an HTTP/0.9 request), or the first
* line has three words and there's a blank line present.
*
* hc->read_idx is how much has been read in; hc->checked_idx is how much we
* have checked so far; and hc->checked_state is the current state of the
* finite state machine.
*/
int httpd_got_request(httpd_conn *hc)
{
char c;
for (; hc->checked_idx < hc->read_idx; ++hc->checked_idx)
{
c = hc->read_buf[hc->checked_idx];
switch (hc->checked_state)
{
case CHST_FIRSTWORD:
switch (c)
{
case ' ':
case '\t':
hc->checked_state = CHST_FIRSTWS;
break;
case '\012':
case '\015':
hc->checked_state = CHST_BOGUS;
return GR_BAD_REQUEST;
}
break;
case CHST_FIRSTWS:
switch (c)
{
case ' ':
case '\t':
break;
case '\012':
case '\015':
hc->checked_state = CHST_BOGUS;
return GR_BAD_REQUEST;
default:
hc->checked_state = CHST_SECONDWORD;
break;
}
break;
case CHST_SECONDWORD:
switch (c)
{
case ' ':
case '\t':
hc->checked_state = CHST_SECONDWS;
break;
case '\012':
case '\015':
/* The first line has only two words - an HTTP/0.9 request. */
return GR_GOT_REQUEST;
}
break;
case CHST_SECONDWS:
switch (c)
{
case ' ':
case '\t':
break;
case '\012':
case '\015':
hc->checked_state = CHST_BOGUS;
return GR_BAD_REQUEST;
default:
hc->checked_state = CHST_THIRDWORD;
break;
}
break;
case CHST_THIRDWORD:
switch (c)
{
case ' ':
case '\t':
hc->checked_state = CHST_THIRDWS;
break;
case '\012':
hc->checked_state = CHST_LF;
break;
case '\015':
hc->checked_state = CHST_CR;
break;
}
break;
case CHST_THIRDWS:
switch (c)
{
case ' ':
case '\t':
break;
case '\012':
hc->checked_state = CHST_LF;
break;
case '\015':
hc->checked_state = CHST_CR;
break;
default:
hc->checked_state = CHST_BOGUS;
return GR_BAD_REQUEST;
}
break;
case CHST_LINE:
switch (c)
{
case '\012':
hc->checked_state = CHST_LF;
break;
case '\015':
hc->checked_state = CHST_CR;
break;
}
break;
case CHST_LF:
switch (c)
{
case '\012':
/* Two newlines in a row - a blank line - end of request. */
return GR_GOT_REQUEST;
case '\015':
hc->checked_state = CHST_CR;
break;
default:
hc->checked_state = CHST_LINE;
break;
}
break;
case CHST_CR:
switch (c)
{
case '\012':
hc->checked_state = CHST_CRLF;
break;
case '\015':
/* Two returns in a row - end of request. */
return GR_GOT_REQUEST;
default:
hc->checked_state = CHST_LINE;
break;
}
break;
case CHST_CRLF:
switch (c)
{
case '\012':
/* Two newlines in a row - end of request. */
return GR_GOT_REQUEST;
case '\015':
hc->checked_state = CHST_CRLFCR;
break;
default:
hc->checked_state = CHST_LINE;
break;
}
break;
case CHST_CRLFCR:
switch (c)
{
case '\012':
case '\015':
/* Two CRLFs or two CRs in a row - end of request. */
return GR_GOT_REQUEST;
default:
hc->checked_state = CHST_LINE;
break;
}
break;
case CHST_BOGUS:
return GR_BAD_REQUEST;
}
}
return GR_NO_REQUEST;
}
int httpd_parse_request(httpd_conn *hc)
{
char *buf;
char *method_str;
char *url;
char *protocol;
char *reqhost;
char *eol;
char *cp;
char *pi;
hc->checked_idx = 0; /* reset */
method_str = bufgets(hc);
ninfo("method_str: \"%s\"\n", method_str);
url = strpbrk(method_str, " \t\012\015");
if (!url)
{
BADREQUEST("url-1");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
*url++ = '\0';
url += strspn(url, " \t\012\015");
ninfo("url: \"%s\"\n", url);
protocol = strpbrk(url, " \t\012\015");
ninfo("protocol: \"%s\"\n", protocol ? protocol : "<null>");
if (!protocol)
{
protocol = "HTTP/0.9";
hc->mime_flag = false;
}
else
{
*protocol++ = '\0';
protocol += strspn(protocol, " \t\012\015");
if (*protocol != '\0')
{
eol = strpbrk(protocol, " \t\012\015");
if (eol)
{
*eol = '\0';
}
if (strcasecmp(protocol, "HTTP/1.0") != 0)
{
hc->one_one = true;
}
}
}
hc->protocol = protocol;
/* Check for HTTP/1.1 absolute URL. */
if (strncasecmp(url, "http://", 7) == 0)
{
if (!hc->one_one)
{
BADREQUEST("one_one");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
reqhost = url + 7;
url = strchr(reqhost, '/');
if (!url)
{
BADREQUEST("reqhost-1");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
*url = '\0';
if (strchr(reqhost, '/') != NULL || reqhost[0] == '.')
{
BADREQUEST("reqhost-2");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
httpd_realloc_str(&hc->reqhost, &hc->maxreqhost, strlen(reqhost));
strcpy(hc->reqhost, reqhost);
*url = '/';
}
if (*url != '/')
{
BADREQUEST("url-2");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
if (strcasecmp(method_str, httpd_method_str(METHOD_GET)) == 0)
{
hc->method = METHOD_GET;
}
else if (strcasecmp(method_str, httpd_method_str(METHOD_HEAD)) == 0)
{
hc->method = METHOD_HEAD;
}
else if (strcasecmp(method_str, httpd_method_str(METHOD_POST)) == 0)
{
hc->method = METHOD_POST;
}
else
{
NOTIMPLEMENTED(method_str);
httpd_send_err(hc, 501, err501title, "", err501form, method_str);
return -1;
}
hc->encodedurl = url;
httpd_realloc_str(&hc->decodedurl, &hc->maxdecodedurl, strlen(hc->encodedurl));
httpd_strdecode(hc->decodedurl, hc->encodedurl);
httpd_realloc_str(&hc->origfilename, &hc->maxorigfilename, strlen(hc->decodedurl));
strcpy(hc->origfilename, &hc->decodedurl[1]);
/* Special case for top-level URL. */
if (hc->origfilename[0] == '\0')
{
strcpy(hc->origfilename, ".");
}
/* Extract query string from encoded URL. */
cp = strchr(hc->encodedurl, '?');
if (cp)
{
++cp;
httpd_realloc_str(&hc->query, &hc->maxquery, strlen(cp));
strcpy(hc->query, cp);
/* Remove query from (decoded) origfilename. */
cp = strchr(hc->origfilename, '?');
if (cp)
{
*cp = '\0';
}
}
de_dotdot(hc->origfilename);
if (hc->origfilename[0] == '/' ||
(hc->origfilename[0] == '.' && hc->origfilename[1] == '.' &&
(hc->origfilename[2] == '\0' || hc->origfilename[2] == '/')))
{
BADREQUEST("origfilename");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
if (hc->mime_flag)
{
/* Read the MIME headers. */
while ((buf = bufgets(hc)) != NULL)
{
if (buf[0] == '\0')
{
break;
}
if (strncasecmp(buf, "Referer:", 8) == 0)
{
cp = &buf[8];
cp += strspn(cp, " \t");
hc->referer = cp;
}
else if (strncasecmp(buf, "User-Agent:", 11) == 0)
{
cp = &buf[11];
cp += strspn(cp, " \t");
hc->useragent = cp;
}
else if (strncasecmp(buf, "Host:", 5) == 0)
{
cp = &buf[5];
cp += strspn(cp, " \t");
hc->hdrhost = cp;
cp = strchr(hc->hdrhost, ':');
if (cp)
{
*cp = '\0';
}
if (strchr(hc->hdrhost, '/') != NULL || hc->hdrhost[0] == '.')
{
BADREQUEST("hdrhost");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
}
else if (strncasecmp(buf, "Accept:", 7) == 0)
{
cp = &buf[7];
cp += strspn(cp, " \t");
if (hc->accept[0] != '\0')
{
if (strlen(hc->accept) > CONFIG_THTTPD_MAXREALLOC)
{
nerr("ERROR: %s way too much Accept: data\n",
httpd_ntoa(&hc->client_addr));
continue;
}
httpd_realloc_str(&hc->accept, &hc->maxaccept, strlen(hc->accept) + 2 + strlen(cp));
strcat(hc->accept, ", ");
}
else
{
httpd_realloc_str(&hc->accept, &hc->maxaccept, strlen(cp));
}
strcat(hc->accept, cp);
}
else if (strncasecmp(buf, "Accept-Encoding:", 16) == 0)
{
cp = &buf[16];
cp += strspn(cp, " \t");
if (hc->accepte[0] != '\0')
{
if (strlen(hc->accepte) > CONFIG_THTTPD_MAXREALLOC)
{
nerr("ERROR: %s way too much Accept-Encoding: data\n",
httpd_ntoa(&hc->client_addr));
continue;
}
httpd_realloc_str(&hc->accepte, &hc->maxaccepte, strlen(hc->accepte) + 2 + strlen(cp));
strcat(hc->accepte, ", ");
}
else
{
httpd_realloc_str(&hc->accepte, &hc->maxaccepte, strlen(cp));
}
strcpy(hc->accepte, cp);
}
else if (strncasecmp(buf, "Accept-Language:", 16) == 0)
{
cp = &buf[16];
cp += strspn(cp, " \t");
hc->acceptl = cp;
}
else if (strncasecmp(buf, "If-Modified-Since:", 18) == 0)
{
cp = &buf[18];
hc->if_modified_since = tdate_parse(cp);
if (hc->if_modified_since == (time_t) - 1)
{
nerr("ERROR: unparsable time: %s\n", cp);
}
}
else if (strncasecmp(buf, "Cookie:", 7) == 0)
{
cp = &buf[7];
cp += strspn(cp, " \t");
hc->cookie = cp;
}
else if (strncasecmp(buf, "Range:", 6) == 0)
{
/* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */
if (strchr(buf, ',') == NULL)
{
char *cp_dash;
cp = strpbrk(buf, "=");
if (cp)
{
cp_dash = strchr(cp + 1, '-');
if (cp_dash != NULL && cp_dash != cp + 1)
{
*cp_dash = '\0';
hc->got_range = true;
hc->range_start = atoll(cp + 1);
if (hc->range_start < 0)
{
hc->range_start = 0;
}
if (isdigit((int)cp_dash[1]))
{
hc->range_end = atoll(cp_dash + 1);
if (hc->range_end < 0)
hc->range_end = -1;
}
}
}
}
}
else if (strncasecmp(buf, "Range-If:", 9) == 0 ||
strncasecmp(buf, "If-Range:", 9) == 0)
{
cp = &buf[9];
hc->range_if = tdate_parse(cp);
if (hc->range_if == (time_t) - 1)
{
nerr("ERROR: unparsable time: %s\n", cp);
}
}
else if (strncasecmp(buf, "Content-Type:", 13) == 0)
{
cp = &buf[13];
cp += strspn(cp, " \t");
hc->contenttype = cp;
}
else if (strncasecmp(buf, "Content-Length:", 15) == 0)
{
cp = &buf[15];
hc->contentlength = atol(cp);
}
else if (strncasecmp(buf, "Authorization:", 14) == 0)
{
cp = &buf[14];
cp += strspn(cp, " \t");
hc->authorization = cp;
}
else if (strncasecmp(buf, "Connection:", 11) == 0)
{
cp = &buf[11];
cp += strspn(cp, " \t");
if (strcasecmp(cp, "keep-alive") == 0)
{
hc->keep_alive = true;
}
}
#ifdef LOG_UNKNOWN_HEADERS
else if (strncasecmp(buf, "Accept-Charset:", 15) == 0 ||
strncasecmp(buf, "Accept-Language:", 16) == 0 ||
strncasecmp(buf, "Agent:", 6) == 0 ||
strncasecmp(buf, "Cache-Control:", 14) == 0 ||
strncasecmp(buf, "Cache-Info:", 11) == 0 ||
strncasecmp(buf, "Charge-To:", 10) == 0 ||
strncasecmp(buf, "Client-IP:", 10) == 0 ||
strncasecmp(buf, "Date:", 5) == 0 ||
strncasecmp(buf, "Extension:", 10) == 0 ||
strncasecmp(buf, "Forwarded:", 10) == 0 ||
strncasecmp(buf, "From:", 5) == 0 ||
strncasecmp(buf, "HTTP-Version:", 13) == 0 ||
strncasecmp(buf, "Max-Forwards:", 13) == 0 ||
strncasecmp(buf, "Message-Id:", 11) == 0 ||
strncasecmp(buf, "MIME-Version:", 13) == 0 ||
strncasecmp(buf, "Negotiate:", 10) == 0 ||
strncasecmp(buf, "Pragma:", 7) == 0 ||
strncasecmp(buf, "Proxy-Agent:", 12) == 0 ||
strncasecmp(buf, "Proxy-Connection:", 17) == 0 ||
strncasecmp(buf, "Security-Scheme:", 16) == 0 ||
strncasecmp(buf, "Session-Id:", 11) == 0 ||
strncasecmp(buf, "UA-Color:", 9) == 0 ||
strncasecmp(buf, "UA-CPU:", 7) == 0 ||
strncasecmp(buf, "UA-Disp:", 8) == 0 ||
strncasecmp(buf, "UA-OS:", 6) == 0 ||
strncasecmp(buf, "UA-Pixels:", 10) == 0 ||
strncasecmp(buf, "User:", 5) == 0 ||
strncasecmp(buf, "Via:", 4) == 0 ||
strncasecmp(buf, "X-", 2) == 0)
; /* ignore */
else
{
nwarn("WARNING: unknown request header: %s\n", buf);
}
#endif /* LOG_UNKNOWN_HEADERS */
}
}
if (hc->one_one)
{
/* Check that HTTP/1.1 requests specify a host, as required. */
if (hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0')
{
BADREQUEST("reqhost-3");
httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
return -1;
}
/* If the client wants to do keep-alives, it might also be doing
* pipelining. There's no way for us to tell. Since we don't
* implement keep-alives yet, if we close such a connection there
* might be unread pipelined requests waiting. So, we have to do a
* lingering close.
*/
if (hc->keep_alive)
{
hc->should_linger = true;
}
}
/* Ok, the request has been parsed. Now we resolve stuff that may require
* the entire request.
*/
/* Copy original filename to expanded filename. */
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename,
strlen(hc->origfilename));
strcpy(hc->expnfilename, hc->origfilename);
/* Tilde mapping. */
if (hc->expnfilename[0] == '~')
{
#ifdef CONFIG_THTTPD_TILDE_MAP1
if (!httpd_tilde_map1(hc))
{
httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl);
return -1;
}
#endif
#ifdef CONFIG_THTTPD_TILDE_MAP2
if (!httpd_tilde_map2(hc))
{
httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl);
return -1;
}
#endif
}
/* Virtual host mapping. */
#ifdef CONFIG_THTTPD_VHOST
if (!vhost_map(hc))
{
INTERNALERROR("VHOST");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
#endif
/* Expand the filename */
cp = expand_filename(hc->expnfilename, &pi, hc->tildemapped);
if (!cp)
{
INTERNALERROR(hc->expnfilename);
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(cp));
strcpy(hc->expnfilename, cp);
httpd_realloc_str(&hc->pathinfo, &hc->maxpathinfo, strlen(pi));
strcpy(hc->pathinfo, pi);
ninfo("expnfilename: \"%s\" pathinfo: \"%s\"\n", hc->expnfilename, hc->pathinfo);
/* Remove pathinfo stuff from the original filename too. */
if (hc->pathinfo[0] != '\0')
{
int i;
i = strlen(hc->origfilename) - strlen(hc->pathinfo);
if (i > 0 && strcmp(&hc->origfilename[i], hc->pathinfo) == 0)
{
hc->origfilename[i - 1] = '\0';
}
}
/* If the expanded filename is an absolute path, check that it's still
* within the current directory or the alternate directory.
*/
if (hc->expnfilename[0] == '/')
{
if (strncmp(hc->expnfilename, httpd_root, strlen(httpd_root)) == 0)
{
}
#ifdef CONFIG_THTTPD_TILDE_MAP2
else if (hc->altdir[0] != '\0' &&
(strncmp(hc->expnfilename, hc->altdir, strlen(hc->altdir)) == 0 &&
(hc->expnfilename[strlen(hc->altdir)] == '\0' ||
hc->expnfilename[strlen(hc->altdir)] == '/')))
{
}
#endif
else
{
nwarn("WARNING: %s URL \"%s\" goes outside the web tree\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to a file outside the permitted web server directory tree.\n"),
hc->encodedurl);
return -1;
}
}
return 0;
}
void httpd_close_conn(httpd_conn *hc)
{
if (hc->file_fd >= 0)
{
close(hc->file_fd);
hc->file_fd = -1;
}
if (hc->conn_fd >= 0)
{
close(hc->conn_fd);
hc->conn_fd = -1;
}
}
void httpd_destroy_conn(httpd_conn *hc)
{
if (hc->initialized)
{
httpd_free((void *)hc->read_buf);
httpd_free((void *)hc->decodedurl);
httpd_free((void *)hc->origfilename);
httpd_free((void *)hc->expnfilename);
httpd_free((void *)hc->encodings);
httpd_free((void *)hc->pathinfo);
httpd_free((void *)hc->query);
httpd_free((void *)hc->accept);
httpd_free((void *)hc->accepte);
httpd_free((void *)hc->reqhost);
httpd_free((void *)hc->hostdir);
httpd_free((void *)hc->remoteuser);
httpd_free((void *)hc->buffer);
#ifdef CONFIG_THTTPD_TILDE_MAP2
httpd_free((void *)hc->altdir);
#endif /*CONFIG_THTTPD_TILDE_MAP2 */
hc->initialized = 0;
}
}
int httpd_start_request(httpd_conn *hc, struct timeval *nowP)
{
static char *indexname;
static size_t maxindexname = 0;
#ifdef CONFIG_THTTPD_AUTH_FILE
static char *dirname;
static size_t maxdirname = 0;
#endif /* CONFIG_THTTPD_AUTH_FILE */
size_t expnlen, indxlen;
char *cp;
char *pi;
int i;
ninfo("File: \"%s\"\n", hc->expnfilename);
expnlen = strlen(hc->expnfilename);
if (hc->method != METHOD_GET && hc->method != METHOD_HEAD &&
hc->method != METHOD_POST)
{
NOTIMPLEMENTED("start");
httpd_send_err(hc, 501, err501title, "", err501form,
httpd_method_str(hc->method));
return -1;
}
/* Stat the file. */
if (stat(hc->expnfilename, &hc->sb) < 0)
{
INTERNALERROR(hc->expnfilename);
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
/* Is it world-readable or world-executable? We check explicitly instead
* of just trying to open it, so that no one ever gets surprised by a file
* that's not set world-readable and yet somehow is readable by the HTTP
* server and therefore the *whole* world.
*/
if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH)))
{
nwarn("WARNING: %s URL \"%s\" resolves to a non world-readable file\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to a file that is not world-readable.\n"),
hc->encodedurl);
return -1;
}
/* Is it a directory? */
if (S_ISDIR(hc->sb.st_mode))
{
/* If there's pathinfo, it's just a non-existent file. */
if (hc->pathinfo[0] != '\0')
{
httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl);
return -1;
}
/* Special handling for directory URLs that don't end in a slash. We
* send back an explicit redirect with the slash, because otherwise
* many clients can't build relative URLs properly.
*/
if (strcmp(hc->origfilename, "") != 0 &&
strcmp(hc->origfilename, ".") != 0 &&
hc->origfilename[strlen(hc->origfilename) - 1] != '/')
{
send_dirredirect(hc);
return -1;
}
/* Check for an index file. */
for (i = 0; i < sizeof(index_names) / sizeof(char *); ++i)
{
httpd_realloc_str(&indexname, &maxindexname,
expnlen + 1 + strlen(index_names[i]));
strcpy(indexname, hc->expnfilename);
indxlen = strlen(indexname);
if (indxlen == 0 || indexname[indxlen - 1] != '/')
{
strcat(indexname, "/");
}
if (strcmp(indexname, "./") == 0)
{
indexname[0] = '\0';
}
strcat(indexname, index_names[i]);
if (stat(indexname, &hc->sb) >= 0)
{
goto got_one;
}
}
/* Nope, no index file, so it's an actual directory request. */
#ifdef CONFIG_THTTPD_GENERATE_INDICES
/* Directories must be readable for indexing. */
if (!(hc->sb.st_mode & S_IROTH))
{
nwarn("WARNING: %s URL \"%s\" tried to index a non-readable directory\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to a directory that has indexing disabled.\n"),
hc->encodedurl);
return -1;
}
# ifdef CONFIG_THTTPD_AUTH_FILE
/* Check authorization for this directory. */
if (auth_check(hc, hc->expnfilename) == -1)
{
return -1;
}
# endif /* CONFIG_THTTPD_AUTH_FILE */
/* Referer check. */
if (!check_referer(hc))
{
return -1;
}
/* Ok, generate an index. */
return ls(hc);
#else /* CONFIG_THTTPD_GENERATE_INDICES */
/* Indexing is disabled */
nwarn("WARNING: %s URL \"%s\" tried to index a directory with indexing disabled\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' is a directory, and directory indexing is disabled on this server.\n"),
hc->encodedurl);
return -1;
#endif /* CONFIG_THTTPD_GENERATE_INDICES */
got_one:
/* Got an index file. Expand again. More pathinfo means
* something went wrong.
*/
cp = expand_filename(indexname, &pi, hc->tildemapped);
if (cp == NULL || pi[0] != '\0')
{
INTERNALERROR(indexname);
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
expnlen = strlen(cp);
httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, expnlen);
strcpy(hc->expnfilename, cp);
/* Now, is the index version world-readable or world-executable? */
if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH)))
{
nwarn("WARNING: %s URL \"%s\" resolves to a non-world-readable index file\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to an index file that is not world-readable.\n"),
hc->encodedurl);
return -1;
}
}
/* Check authorization for this directory. */
#ifdef CONFIG_THTTPD_AUTH_FILE
httpd_realloc_str(&dirname, &maxdirname, expnlen);
strcpy(dirname, hc->expnfilename);
cp = strrchr(dirname, '/');
if (!cp)
{
strcpy(dirname, httpd_root);
}
else
{
*cp = '\0';
}
if (auth_check(hc, dirname) == -1)
{
return -1;
}
/* Check if the filename is the CONFIG_THTTPD_AUTH_FILE itself - that's verboten. */
if (expnlen == sizeof(CONFIG_THTTPD_AUTH_FILE) - 1)
{
if (strcmp(hc->expnfilename, CONFIG_THTTPD_AUTH_FILE) == 0)
{
nwarn("WARNING: %s URL \"%s\" tried to retrieve an auth file\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' is an authorization file, retrieving it is not permitted.\n"),
hc->encodedurl);
return -1;
}
}
else if (expnlen >= sizeof(CONFIG_THTTPD_AUTH_FILE) &&
strcmp(&(hc->expnfilename[expnlen - sizeof(CONFIG_THTTPD_AUTH_FILE) + 1]),
CONFIG_THTTPD_AUTH_FILE) == 0 &&
hc->expnfilename[expnlen - sizeof(CONFIG_THTTPD_AUTH_FILE)] == '/')
{
nwarn("WARNING: %s URL \"%s\" tried to retrieve an auth file\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' is an authorization file, retrieving it is not permitted.\n"),
hc->encodedurl);
return -1;
}
#endif /* CONFIG_THTTPD_AUTH_FILE */
/* Referer check. */
if (!check_referer(hc))
return -1;
/* Is it in the CGI area? */
#ifdef CONFIG_THTTPD_CGI_PATTERN
if (match(CONFIG_THTTPD_CGI_PATTERN, hc->expnfilename))
{
return cgi(hc);
}
#endif
/* It's not CGI. If it's executable or there's pathinfo, someone's trying
* to either serve or run a non-CGI file as CGI. Either case is
* prohibited.
*/
if (hc->sb.st_mode & S_IXOTH)
{
nwarn("WARNING: %s URL \"%s\" is executable but isn't CGI\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n"),
hc->encodedurl);
return -1;
}
if (hc->pathinfo[0] != '\0')
{
nwarn("WARNING: %s URL \"%s\" has pathinfo but isn't CGI\n",
httpd_ntoa(&hc->client_addr), hc->encodedurl);
httpd_send_err(hc, 403, err403title, "",
ERROR_FORM(err403form,
"The requested URL '%s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n"),
hc->encodedurl);
return -1;
}
/* Fill in range_end, if necessary. */
if (hc->got_range &&
(hc->range_end == -1 || hc->range_end >= hc->sb.st_size))
{
hc->range_end = hc->sb.st_size - 1;
}
figure_mime(hc);
if (hc->method == METHOD_HEAD)
{
send_mime(hc, 200, ok200title, hc->encodings, "", hc->type,
hc->sb.st_size, hc->sb.st_mtime);
}
else if (hc->if_modified_since != (time_t) - 1 &&
hc->if_modified_since >= hc->sb.st_mtime)
{
send_mime(hc, 304, err304title, hc->encodings, "", hc->type, (off_t) - 1,
hc->sb.st_mtime);
}
else
{
hc->file_fd = open(hc->expnfilename, O_RDONLY);
if (hc->file_fd < 0)
{
INTERNALERROR(hc->expnfilename);
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
return -1;
}
send_mime(hc, 200, ok200title, hc->encodings, "", hc->type,
hc->sb.st_size, hc->sb.st_mtime);
}
return 0;
}
char *httpd_ntoa(httpd_sockaddr *saP)
{
#ifdef CONFIG_NET_IPv6
static char str[200];
if (getnameinfo
(&saP->sa, sockaddr_len(saP), str, sizeof(str), 0, 0,
NI_NUMERICHOST) != 0)
{
str[0] = '?';
str[1] = '\0';
}
else if (IN6_IS_ADDR_V4MAPPED(&saP->sa_in6.sin6_addr) &&
strncmp(str, "::ffff:", 7) == 0)
{
/* Elide IPv6ish prefix for IPv4 addresses. */
strcpy(str, &str[7]);
}
return str;
#else /* CONFIG_NET_IPv6 */
return inet_ntoa(saP->sin_addr);
#endif /* CONFIG_NET_IPv6 */
}
/* Read to requested buffer, accounting for interruptions and EOF */
int httpd_read(int fd, const void *buf, size_t nbytes)
{
ssize_t nread;
int ntotal;
ntotal = 0;
do
{
nread = read(fd, (char*)buf + ntotal, nbytes - ntotal);
if (nread < 0)
{
if (errno == EAGAIN)
{
usleep(100000); /* 100MS */
}
else if (errno != EINTR)
{
nerr("ERROR: Error sending: %d\n", errno);
return nread;
}
}
else
{
ntotal += nread;
}
}
while (ntotal < nbytes && nread != 0);
return ntotal;
}
/* Write the requested buffer completely, accounting for interruptions */
int httpd_write(int fd, const void *buf, size_t nbytes)
{
ssize_t nwritten;
int ntotal;
ntotal = 0;
do
{
nwritten = write(fd, (char*)buf + ntotal, nbytes - ntotal);
if (nwritten < 0)
{
if (errno == EAGAIN)
{
usleep(100000); /* 100MS */
}
else if (errno != EINTR)
{
nerr("ERROR: Error sending: %d\n", errno);
return nwritten;
}
}
else
{
ntotal += nwritten;
}
}
while (ntotal < nbytes);
return ntotal;
}
#endif /* CONFIG_THTTPD */