http: document index.cgi usage. no code changes
[oweals/busybox.git] / networking / httpd.c
index 8834c1c71b9c2403befcceca7f5f54de2e169aed..eded7b63f05dc882b741055fbdb06499f864b322 100644 (file)
@@ -5,32 +5,33 @@
  * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
  * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
  *
- * simplify patch stolen from libbb without using strdup
- *
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  *
  *****************************************************************************
  *
  * Typical usage:
- *   for non root user
- * httpd -p 8080 -h $HOME/public_html
- *   or for daemon start from rc script with uid=0:
- * httpd -u www
- * This is equivalent if www user have uid=80 to
- * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication"
- *
+ * For non root user:
+ *      httpd -p 8080 -h $HOME/public_html
+ * For daemon start from rc script with uid=0:
+ *      httpd -u www
+ * which is equivalent to (assuming user www has uid 80):
+ *      httpd -p 80 -u 80 -h $PWD -c /etc/httpd.conf -r "Web Server Authentication"
  *
- * When an url starts by "/cgi-bin/" it is assumed to be a cgi script.  The
- * server changes directory to the location of the script and executes it
+ * When an url starts with "/cgi-bin/" it is assumed to be a cgi script.
+ * The server changes directory to the location of the script and executes it
  * after setting QUERY_STRING and other environment variables.
  *
+ * If directory URL is given, no index.html is found and CGI support is enabled,
+ * cgi-bin/index.cgi will be run. Directory to list is ../$QUERY_STRING.
+ * See httpd_indexcgi.c for an example GCI code.
+ *
  * Doc:
  * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
  *
  * The applet can also be invoked as an url arg decoder and html text encoder
  * as follows:
- *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
- *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
+ *      foo=`httpd -d $foo`             # decode "Hello%20World" as "Hello World"
+ *      bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
  * Note that url encoding for arguments is not the same as html encoding for
  * presentation.  -d decodes an url-encoded argument while -e encodes in html
  * for page display.
@@ -74,7 +75,7 @@
  *     D:2.3.4.        # deny from 2.3.4.0 - 2.3.4.255
  *     A:*             # (optional line added for clarity)
  *
- * If a sub directory contains a config file it is parsed and merged with
+ * If a sub directory contains config file, it is parsed and merged with
  * any existing settings as if it was appended to the original configuration.
  *
  * subdir paths are relative to the containing subdir and thus cannot
 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
 # include <sys/sendfile.h>
 #endif
-
-#define DEBUG 0
-
-#define IOBUF_SIZE 8192    /* IO buffer */
-
 /* amount of buffering in a pipe */
 #ifndef PIPE_BUF
 # define PIPE_BUF 4096
 #endif
+
+#define DEBUG 0
+
+#define IOBUF_SIZE 8192
 #if PIPE_BUF >= IOBUF_SIZE
 # error "PIPE_BUF >= IOBUF_SIZE"
 #endif
 static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc";
 static const char HTTPD_CONF[] ALIGN1 = "httpd.conf";
 static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
+static const char index_html[] ALIGN1 = "index.html";
 
 typedef struct has_next_ptr {
        struct has_next_ptr *next;
@@ -170,7 +171,6 @@ enum {
        HTTP_PAYMENT_REQUIRED = 402,
        HTTP_BAD_GATEWAY = 502,
        HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
-       HTTP_RESPONSE_SETSIZE = 0xffffffff
 #endif
 };
 
@@ -231,14 +231,11 @@ static const struct {
 #endif
 };
 
-static const char index_html[] ALIGN1 = "index.html";
-
-
 struct globals {
        int verbose;            /* must be int (used by getopt32) */
        smallint flg_deny_all;
 
-       unsigned rmt_ip;        /* used for IP-based allow/deny rules */
+       unsigned rmt_ip;        /* used for IP-based allow/deny rules */
        time_t last_mod;
        char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
        const char *bind_addr_or_port;
@@ -274,7 +271,7 @@ struct globals {
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
        Htaccess *script_i;     /* config script interpreters */
 #endif
-       char *iobuf;            /* [IOBUF_SIZE] */
+       char *iobuf;            /* [IOBUF_SIZE] */
 #define hdr_buf bb_common_bufsiz1
        char *hdr_ptr;
        int hdr_cnt;
@@ -284,6 +281,10 @@ struct globals {
 #if ENABLE_FEATURE_HTTPD_PROXY
        Htaccess_Proxy *proxy;
 #endif
+#if ENABLE_FEATURE_HTTPD_GZIP
+       /* client can handle gzip / we are going to send gzip */
+       smallint content_gzip;
+#endif
 };
 #define G (*ptr_to_globals)
 #define verbose           (G.verbose          )
@@ -326,6 +327,11 @@ enum {
 #define hdr_cnt           (G.hdr_cnt          )
 #define http_error_page   (G.http_error_page  )
 #define proxy             (G.proxy            )
+#if ENABLE_FEATURE_HTTPD_GZIP
+# define content_gzip     (G.content_gzip     )
+#else
+# define content_gzip     0
+#endif
 #define INIT_G() do { \
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
        IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
@@ -777,7 +783,7 @@ static char *encodeString(const char *string)
        char *p = out;
        char ch;
 
-       while ((ch = *string++)) {
+       while ((ch = *string++) != '\0') {
                /* very simple check for what to encode */
                if (isalnum(ch))
                        *p++ = ch;
@@ -787,7 +793,7 @@ static char *encodeString(const char *string)
        *p = '\0';
        return out;
 }
-#endif          /* FEATURE_HTTPD_ENCODE_URL_STR */
+#endif
 
 /*
  * Given a URL encoded string, convert it to plain ascii.
@@ -814,12 +820,12 @@ static unsigned hex_to_bin(unsigned char c)
        if (v <= 5)
                return v + 10;
        return ~0;
-}
 /* For testing:
 void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
 int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f');
 t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
 */
+}
 static char *decodeString(char *orig, int option_d)
 {
        /* note that decoded string is always shorter than original */
@@ -1034,10 +1040,14 @@ static void send_headers(int responseNum)
 #endif
                        "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n",
                                tmp_str,
-                               "Content-length:",
+                               content_gzip ? "Transfer-length:" : "Content-length:",
                                file_size
                );
        }
+
+       if (content_gzip)
+               len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
+
        iobuf[len++] = '\r';
        iobuf[len++] = '\n';
        if (infoString) {
@@ -1059,6 +1069,7 @@ static void send_headers(int responseNum)
 static void send_headers_and_exit(int responseNum) NORETURN;
 static void send_headers_and_exit(int responseNum)
 {
+       IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
        send_headers(responseNum);
        log_and_exit();
 }
@@ -1145,13 +1156,14 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post
                                /* post_len <= 0 && hdr_cnt <= 0:
                                 * no more POST data to CGI,
                                 * let CGI see EOF on CGI's stdin */
-                               close(toCgi_wr);
+                               if (toCgi_wr != fromCgi_rd)
+                                       close(toCgi_wr);
                                toCgi_wr = 0;
                        }
                }
 
                /* Now wait on the set of sockets */
-               count = safe_poll(pfd, 3, -1);
+               count = safe_poll(pfd, toCgi_wr ? TO_CGI+1 : FROM_CGI+1, -1);
                if (count <= 0) {
 #if 0
                        if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
@@ -1473,7 +1485,7 @@ static void send_cgi_and_exit(
                 * in the current directory */
                execv(argv[0], argv);
                if (verbose)
-                       bb_perror_msg("exec %s", argv[0]);
+                       bb_perror_msg("can't execute '%s'", argv[0]);
  error_execing_cgi:
                /* send to stdout
                 * (we are CGI here, our stdout is pumped to the net) */
@@ -1506,7 +1518,23 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
        int fd;
        ssize_t count;
 
-       fd = open(url, O_RDONLY);
+       if (content_gzip) {
+               /* does <url>.gz exist? Then use it instead */
+               char *gzurl = xasprintf("%s.gz", url);
+               fd = open(gzurl, O_RDONLY);
+               free(gzurl);
+               if (fd != -1) {
+                       struct stat sb;
+                       fstat(fd, &sb);
+                       file_size = sb.st_size;
+                       last_mod = sb.st_mtime;
+               } else {
+                       IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
+                       fd = open(url, O_RDONLY);
+               }
+       } else {
+               fd = open(url, O_RDONLY);
+       }
        if (fd < 0) {
                if (DEBUG)
                        bb_perror_msg("can't open '%s'", url);
@@ -1589,8 +1617,11 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
                        url, found_mime_type);
 
 #if ENABLE_FEATURE_HTTPD_RANGES
-       if (what == SEND_BODY)
-               range_start = 0; /* err pages and ranges don't mix */
+       if (what == SEND_BODY /* err pages and ranges don't mix */
+        || content_gzip /* we are sending compressed page: can't do ranges */  ///why?
+       ) {
+               range_start = 0;
+       }
        range_len = MAXINT(off_t);
        if (range_start) {
                if (!range_end) {
@@ -1963,7 +1994,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
        if (http_major_version >= '0') {
                /* Request was with "... HTTP/nXXX", and n >= 0 */
 
-               /* Read until blank line for HTTP version specified, else parse immediate */
+               /* Read until blank line */
                while (1) {
                        if (!get_line())
                                break; /* EOF or error or empty line */
@@ -1990,9 +2021,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                        if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
                                /* extra read only for POST */
                                if (prequest != request_GET
-#if ENABLE_FEATURE_HTTPD_CGI
+# if ENABLE_FEATURE_HTTPD_CGI
                                 && prequest != request_HEAD
-#endif
+# endif
                                ) {
                                        tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1);
                                        if (!tptr[0])
@@ -2053,6 +2084,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                                        }
                                }
                        }
+#endif
+#if ENABLE_FEATURE_HTTPD_GZIP
+                       if (STRNCASECMP(iobuf, "Accept-Encoding:") == 0) {
+                               /* Note: we do not support "gzip;q=0"
+                                * method of _disabling_ gzip
+                                * delivery. No one uses that, though */
+                               const char *s = strstr(iobuf, "gzip");
+                               if (s) {
+                                       // want more thorough checks?
+                                       //if (s[-1] == ' '
+                                       // || s[-1] == ','
+                                       // || s[-1] == ':'
+                                       //) {
+                                               content_gzip = 1;
+                                       //}
+                               }
+                       }
 #endif
                } /* while extra header reading */
        }
@@ -2103,8 +2151,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                header_ptr += 2;
                write(proxy_fd, header_buf, header_ptr - header_buf);
                free(header_buf); /* on the order of 8k, free it */
-               /* cgi_io_loop_and_exit needs to have two distinct fds */
-               cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length);
+               cgi_io_loop_and_exit(proxy_fd, proxy_fd, length);
        }
 #endif
 
@@ -2183,9 +2230,9 @@ static void mini_httpd(int server_socket)
                /* Wait for connections... */
                fromAddr.len = LSA_SIZEOF_SA;
                n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
-
                if (n < 0)
                        continue;
+
                /* set the KEEPALIVE option to cull dead connections */
                setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
 
@@ -2226,9 +2273,9 @@ static void mini_httpd_nommu(int server_socket, int argc, char **argv)
                /* Wait for connections... */
                fromAddr.len = LSA_SIZEOF_SA;
                n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
-
                if (n < 0)
                        continue;
+
                /* set the KEEPALIVE option to cull dead connections */
                setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));