/*
 * Copyright (c) Cameron Rich
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * * 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.
 * * Neither the name of the axTLS project nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include "axhttp.h"

#define HTTP_VERSION        "HTTP/1.1"

static const char * index_file = "index.html";
static const char * rfc1123_format = "%a, %d %b %Y %H:%M:%S GMT";

static int special_read(struct connstruct *cn, void *buf, size_t count);
static int special_write(struct connstruct *cn, 
                                        const char *buf, size_t count);
static void send_error(struct connstruct *cn, int err);
static int hexit(char c);
static void urldecode(char *buf);
static void buildactualfile(struct connstruct *cn);
static int sanitizefile(const char *buf);
static int sanitizehost(char *buf);
static int htaccess_check(struct connstruct *cn);
static const char *getmimetype(const char *name);

#if defined(CONFIG_HTTP_DIRECTORIES)
static void urlencode(const uint8_t *s, char *t);
static void procdirlisting(struct connstruct *cn);
#endif
#if defined(CONFIG_HTTP_HAS_CGI)
static void proccgi(struct connstruct *cn);
static void decode_path_info(struct connstruct *cn, char *path_info);
static int init_read_post_data(char *buf, char *data, struct connstruct *cn, int old_rv);
#endif
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
static int auth_check(struct connstruct *cn);
#endif

#if AXDEBUG
#define AXDEBUGSTART \
	{ \
		FILE *axdout; \
		axdout = fopen("/var/log/axdebug", "a"); \
	
#define AXDEBUGEND \
		fclose(axdout); \
	}
#else /* AXDEBUG */
#define AXDEBUGSTART
#define AXDEBUGEND
#endif /* AXDEBUG */

/* Returns 1 if elems should continue being read, 0 otherwise */
static int procheadelem(struct connstruct *cn, char *buf) 
{
    char *delim, *value;

    if ((delim = strchr(buf, ' ')) == NULL)
        return 0;

    *delim = 0;
    value = delim+1;

    if (strcmp(buf, "GET") == 0 || strcmp(buf, "HEAD") == 0 ||
                                            strcmp(buf, "POST") == 0) 
    {
        if (buf[0] == 'H') 
            cn->reqtype = TYPE_HEAD;
        else if (buf[0] == 'P') 
            cn->reqtype = TYPE_POST;

        if ((delim = strchr(value, ' ')) == NULL)       /* expect HTTP type */
            return 0;

        *delim++ = 0;
        urldecode(value);

        if (sanitizefile(value) == 0) 
        {
            send_error(cn, 403);
            return 0;
        }

#if defined(CONFIG_HTTP_HAS_CGI)
        decode_path_info(cn, value);
#else
        my_strncpy(cn->filereq, value, MAXREQUESTLENGTH);
#endif
        cn->if_modified_since = -1;
        if (strcmp(delim, "HTTP/1.0") == 0) /* v1.0 HTTP? */
            cn->is_v1_0 = 1;
    } 
    else if (strcasecmp(buf, "Host:") == 0) 
    {
        if (sanitizehost(value) == 0) 
        {
            removeconnection(cn);
            return 0;
        }

        my_strncpy(cn->server_name, value, MAXREQUESTLENGTH);
    } 
    else if (strcasecmp(buf, "Connection:") == 0 && strcmp(value, "close") == 0) 
    {
        cn->close_when_done = 1;
    } 
    else if (strcasecmp(buf, "If-Modified-Since:") == 0) 
    {
        cn->if_modified_since = tdate_parse(value);
    }
    else if (strcasecmp(buf, "Expect:") == 0)
    {
		/* supposed to be safe to ignore 100-continue */
		if (strcasecmp(value, "100-continue") != 0) {
			send_error(cn, 417); /* expectation failed */
			return 0;
		}
    }
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
    else if (strcasecmp(buf, "Authorization:") == 0 &&
                                    strncmp(value, "Basic ", 6) == 0)
    {
        int size = sizeof(cn->authorization);
        if (base64_decode(&value[6], strlen(&value[6]), 
                                        (uint8_t *)cn->authorization, &size))
            cn->authorization[0] = 0;   /* error */
        else
            cn->authorization[size] = 0;
    }
#endif
#if defined(CONFIG_HTTP_HAS_CGI)
    else if (strcasecmp(buf, "Content-Length:") == 0)
    {
        sscanf(value, "%d", &cn->content_length);
    }
    else if (strcasecmp(buf, "Content-Type:") == 0)
    {
        my_strncpy(cn->cgicontenttype, value, MAXREQUESTLENGTH);
    }
    else if (strcasecmp(buf, "Cookie:") == 0)
    {
        my_strncpy(cn->cookie, value, MAXREQUESTLENGTH);
    }
#endif

    return 1;
}

#if defined(CONFIG_HTTP_DIRECTORIES)
static void procdirlisting(struct connstruct *cn)
{
    char buf[MAXREQUESTLENGTH];
    char actualfile[1024];

    if (cn->reqtype == TYPE_HEAD) 
    {
        snprintf(buf, sizeof(buf), HTTP_VERSION
                " 200 OK\nContent-Type: text/html\n\n");
        if (write(cn->networkdesc, buf, strlen(buf)) < 0)
        {
            printf("procdirlisting: could not write");
            TTY_FLUSH();
        }

        removeconnection(cn);
        return;
    }

    strcpy(actualfile, cn->actualfile);

#ifdef WIN32
    strcat(actualfile, "*");
    cn->dirp = FindFirstFile(actualfile, &cn->file_data);

    if (cn->dirp == INVALID_HANDLE_VALUE) 
    {
        send_error(cn, 404);
        return;
    }
#else
    if ((cn->dirp = opendir(actualfile)) == NULL) 
    {
        send_error(cn, 404);
        return;
    }
#endif

    snprintf(buf, sizeof(buf), HTTP_VERSION
            " 200 OK\nContent-Type: text/html\n\n"
            "<html><body>\n<title>Directory Listing</title>\n"
            "<h3>Directory listing of %s://%s%s</h3><br />\n", 
            cn->is_ssl ? "https" : "http", cn->server_name, cn->filereq);
    special_write(cn, buf, strlen(buf));
    cn->state = STATE_DOING_DIR;
}

void procdodir(struct connstruct *cn) 
{
#ifndef WIN32
    struct dirent *dp;
#endif
    char buf[MAXREQUESTLENGTH];
    char encbuf[1024];
    char *file;

    do 
    {
       buf[0] = 0;

#ifdef WIN32
        if (!FindNextFile(cn->dirp, &cn->file_data)) 
#else
        if ((dp = readdir(cn->dirp)) == NULL)  
#endif
        {
            snprintf(buf, sizeof(buf), "</body></html>\n");
            special_write(cn, buf, strlen(buf));
            removeconnection(cn);
#ifndef WIN32
            closedir(cn->dirp);
#endif
            return;
        }

#ifdef WIN32
        file = cn->file_data.cFileName;
#else
        file = dp->d_name;
#endif

        /* if no index file, don't display the ".." directory */
        if (cn->filereq[0] == '/' && cn->filereq[1] == '\0' &&
                strcmp(file, "..") == 0) 
            continue;

        /* don't display files beginning with "." */
        if (file[0] == '.' && file[1] != '.')
            continue;

        /* make sure a '/' is at the end of a directory */
        if (cn->filereq[strlen(cn->filereq)-1] != '/')
            strcat(cn->filereq, "/");

        /* see if the dir + file is another directory */
        snprintf(buf, sizeof(buf), "%s%s", cn->actualfile, file);
        if (isdir(buf))
            strcat(file, "/");

        urlencode((uint8_t *)file, encbuf);
        snprintf(buf, sizeof(buf), "<a href=\"%s%s\">%s</a><br />\n",
                cn->filereq, encbuf, file);
    } while (special_write(cn, buf, strlen(buf)));
}

/* Encode funny chars -> %xx in newly allocated storage */
/* (preserves '/' !) */
static void urlencode(const uint8_t *s, char *t) 
{
    const uint8_t *p = s;
    char *tp = t;

    for (; *p; p++) 
    {
        if ((*p > 0x00 && *p < ',') ||
                (*p > '9' && *p < 'A') ||
                (*p > 'Z' && *p < '_') ||
                (*p > '_' && *p < 'a') ||
                (*p > 'z' && *p < 0xA1)) 
        {
            sprintf((char *)tp, "%%%02X", *p);
            tp += 3; 
        } 
        else 
        {
            *tp = *p;
            tp++;
        }
    }

    *tp='\0';
}

#endif

void procreadhead(struct connstruct *cn) 
{
    char buf[MAXREADLENGTH], *tp, *next;
    int rv;

    memset(buf, 0, sizeof(buf));
    rv = special_read(cn, buf, sizeof(buf)-1);
    if (rv <= 0) 
    {
        if (rv < 0 || !cn->is_ssl) /* really dead? */
            removeconnection(cn);
        return;
    }

    buf[rv] = '\0';
    next = tp = buf;

#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
    cn->authorization[0] = 0;
#endif

    /* Split up lines and send to procheadelem() */
    while (*next != '\0') 
    {
        /* If we have a blank line, advance to next stage */
        if (*next == '\r' || *next == '\n') 
        {
#if defined(CONFIG_HTTP_HAS_CGI)
            if (cn->reqtype == TYPE_POST && cn->content_length > 0)
            {
                if (init_read_post_data(buf, next, cn, rv) == 0)
                    return;
            }
#endif

            buildactualfile(cn);
            cn->state = STATE_WANT_TO_SEND_HEAD;
            return;
        }

        while (*next != '\r' && *next != '\n' && *next != '\0') 
            next++;

        if (*next == '\r') 
        {
            *next = '\0';
            next += 2;
        }
        else if (*next == '\n') 
            *next++ = '\0';

        if (procheadelem(cn, tp) == 0) 
            return;

        tp = next;
    }
}

/* In this function we assume that the file has been checked for
 * maliciousness (".."s, etc) and has been decoded
 */
void procsendhead(struct connstruct *cn) 
{
    char buf[MAXREQUESTLENGTH];
    struct stat stbuf;
    time_t t_time;
    struct tm *ptm;
    char date[32];
    char last_modified[32];
    char expires[32];
    int file_exists;

    /* are we trying to access a file over the HTTP connection instead of a
     * HTTPS connection? Or is this directory disabled? */
    if (htaccess_check(cn))      
    {
        send_error(cn, 403);
        return;
    }

#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
    if (auth_check(cn))     /* see if there is a '.htpasswd' file */
    {
#ifdef CONFIG_HTTP_VERBOSE
        printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
#endif
        removeconnection(cn);
        return;
    }
#endif

    file_exists = stat(cn->actualfile, &stbuf);

#if defined(CONFIG_HTTP_HAS_CGI)
    if (file_exists != -1 && cn->is_cgi)
    {
        proccgi(cn);
        return;
    }
#endif

    /* look for "index.html"? */
    if (isdir(cn->actualfile))
    {
        char tbuf[MAXREQUESTLENGTH];
        snprintf(tbuf, MAXREQUESTLENGTH, "%s%s", cn->actualfile, index_file);

        if ((file_exists = stat(tbuf, &stbuf)) != -1) 
            my_strncpy(cn->actualfile, tbuf, MAXREQUESTLENGTH);
        else
        {
#if defined(CONFIG_HTTP_DIRECTORIES)
            /* If not, we do a directory listing of it */
            procdirlisting(cn);
#else
            send_error(cn, 404);
#endif
            return;
        }
    }

    if (file_exists == -1)
    {
        send_error(cn, 404);
        return;
    }


    time(&t_time);
    ptm = gmtime(&t_time);
    strftime(date, sizeof(date), rfc1123_format, ptm);

    /* has the file been read before? */
    if (cn->if_modified_since != -1)  
                                       
    {
        ptm = gmtime(&stbuf.st_mtime);
        t_time = mktime(ptm);

        if (cn->if_modified_since >= t_time)
        {
            snprintf(buf, sizeof(buf), HTTP_VERSION" 304 Not Modified\nServer: "
                "%s\nDate: %s\n\n", server_version, date);
            special_write(cn, buf, strlen(buf));
            cn->state = STATE_WANT_TO_READ_HEAD;
            return;
        }
    }

    if (cn->reqtype == TYPE_HEAD) 
    {
        removeconnection(cn);
        return;
    } 
    else 
    {
        int flags = O_RDONLY;
#if defined(WIN32) || defined(CONFIG_PLATFORM_CYGWIN)
        flags |= O_BINARY;
#endif
        cn->filedesc = open(cn->actualfile, flags);

        if (cn->filedesc < 0) 
        {
            send_error(cn, 404);
            return;
        }

        ptm = gmtime(&stbuf.st_mtime);
        strftime(last_modified, sizeof(last_modified), rfc1123_format, ptm);
        t_time += CONFIG_HTTP_TIMEOUT;
        ptm = gmtime(&t_time);
        strftime(expires, sizeof(expires), rfc1123_format, ptm);

        snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\nServer: %s\n"
            "Content-Type: %s\nContent-Length: %ld\n"
            "Date: %s\nLast-Modified: %s\nExpires: %s\n\n", server_version,
            getmimetype(cn->actualfile), (long) stbuf.st_size,
            date, last_modified, expires); 

        special_write(cn, buf, strlen(buf));

#ifdef CONFIG_HTTP_VERBOSE
        printf("axhttpd: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
        TTY_FLUSH();
#endif

#ifdef WIN32
        for (;;)
        {
            procreadfile(cn);
            if (cn->filedesc == -1)
                break;

            do 
            {
                procsendfile(cn);
            } while (cn->state != STATE_WANT_TO_READ_FILE);
        }
#else
        cn->state = STATE_WANT_TO_READ_FILE;
#endif
    }
}

void procreadfile(struct connstruct *cn) 
{
    int rv = read(cn->filedesc, cn->databuf, BLOCKSIZE);

    if (rv <= 0) 
    {
        close(cn->filedesc);
        cn->filedesc = -1;

        if (cn->close_when_done)        /* close immediately */
            removeconnection(cn);
        else 
        {
            if (cn->is_v1_0)    /* die now */
                removeconnection(cn);
            else                /* keep socket open - HTTP 1.1 */
            {
                cn->state = STATE_WANT_TO_READ_HEAD;
                cn->numbytes = 0;
            }
        }

        return;
    }

    cn->numbytes = rv;
    cn->state = STATE_WANT_TO_SEND_FILE;
}

void procsendfile(struct connstruct *cn) 
{
    int rv = special_write(cn, cn->databuf, cn->numbytes);

    if (rv < 0)
        removeconnection(cn);
    else if (rv == cn->numbytes)
    {
        cn->state = STATE_WANT_TO_READ_FILE;
    }
    else if (rv == 0)
    { 
        /* Do nothing */ 
    }
    else 
    {
        memmove(cn->databuf, cn->databuf + rv, cn->numbytes - rv);
        cn->numbytes -= rv;
    }
}

#if defined(CONFIG_HTTP_HAS_CGI)
/* Should this be a bit more dynamic? It would mean more calls to malloc etc */
#define CGI_ARG_SIZE        17

static void proccgi(struct connstruct *cn) 
{
    int tpipe[2], spipe[2];
    char *myargs[3];
    char cgienv[CGI_ARG_SIZE][MAXREQUESTLENGTH];
    char * cgiptr[CGI_ARG_SIZE+4];
    const char *type = "HEAD";
    int cgi_index = 0, i;
    pid_t pid;
#ifdef WIN32
    int tmp_stdout;
#endif

    snprintf(cgienv[0], MAXREQUESTLENGTH, 
            HTTP_VERSION" 200 OK\nServer: %s\n%s",
            server_version, (cn->reqtype == TYPE_HEAD) ? "\n" : "");
    special_write(cn, cgienv[0], strlen(cgienv[0]));

    if (cn->reqtype == TYPE_HEAD) 
    {
        removeconnection(cn);
        return;
    }

#ifdef CONFIG_HTTP_VERBOSE
    printf("[CGI]: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
    TTY_FLUSH();
#endif

    /* win32 cgi is a bit too painful */
#ifndef WIN32
	/* set up pipe that is used for sending POST query data to CGI script*/
    if (cn->reqtype == TYPE_POST) 
    {
        if (pipe(spipe) == -1)
        {
            printf("[CGI]: could not create pipe");
            TTY_FLUSH();
            return;
        }
    }

	if (pipe(tpipe) == -1)
    {
        printf("[CGI]: could not create pipe");
        TTY_FLUSH();
        return;
    }

    /*
     * use vfork() instead of fork() for performance 
     */
    if ((pid = vfork()) > 0)  /* parent */
    {
        /* Send POST query data to CGI script */
        if ((cn->reqtype == TYPE_POST) && (cn->content_length > 0)) 
        {
            if (write(spipe[1], cn->post_data, cn->content_length) == -1)
            {
                printf("[CGI]: could write to pipe");
                TTY_FLUSH();
                return;
            }

            close(spipe[0]);	 
            close(spipe[1]);

            /* free the memory that is allocated in read_post_data() */
            free(cn->post_data); 
            cn->post_data = NULL;
        }

        /* Close the write descriptor */
        close(tpipe[1]);
        cn->filedesc = tpipe[0];
        cn->state = STATE_WANT_TO_READ_FILE;
        cn->close_when_done = 1;
        return;
    }

    if (pid < 0) /* vfork failed */
        exit(1);

    /* The problem child... */

    /* Our stdout/stderr goes to the socket */
    dup2(tpipe[1], 1);
    dup2(tpipe[1], 2);
    close(tpipe[0]);
    close(tpipe[1]);

    /* If it was a POST request, send the socket data to our stdin */
    if (cn->reqtype == TYPE_POST)  {
        dup2(spipe[0], 0);  
        close(spipe[0]);
        close(spipe[1]);
    } else    /* Otherwise we can shutdown the read side of the sock */
        shutdown(cn->networkdesc, 0);

    myargs[0] = CONFIG_HTTP_CGI_LAUNCHER;
    myargs[1] = cn->actualfile;
    myargs[2] = NULL;

    /* 
     * set the cgi args. A url is defined by:
     * http://$SERVER_NAME:$SERVER_PORT$SCRIPT_NAME$PATH_INFO?$QUERY_STRING
     * TODO: other CGI parameters?
     */
    sprintf(cgienv[cgi_index++], "SERVER_SOFTWARE=%s", server_version);
    strcpy(cgienv[cgi_index++], "DOCUMENT_ROOT=" CONFIG_HTTP_WEBROOT);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "SERVER_NAME=%s", cn->server_name);
    sprintf(cgienv[cgi_index++], "SERVER_PORT=%d", 
            cn->is_ssl ? CONFIG_HTTP_HTTPS_PORT : CONFIG_HTTP_PORT);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "REQUEST_URI=%s", cn->uri_request);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "SCRIPT_NAME=%s", cn->filereq);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "PATH_INFO=%s", cn->uri_path_info);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "QUERY_STRING=%s", cn->uri_query);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "REMOTE_ADDR=%s", cn->remote_addr);
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "HTTP_COOKIE=%s", cn->cookie);  /* note: small size */
#if defined(CONFIG_HTTP_HAS_AUTHORIZATION)
    snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
            "REMOTE_USER=%s", cn->authorization);
#endif

    switch (cn->reqtype)
    {
        case TYPE_GET: 
            type = "GET";
            break;

#if defined(CONFIG_HTTP_HAS_CGI)
        case TYPE_POST:
            type = "POST";
            sprintf(cgienv[cgi_index++], 
                        "CONTENT_LENGTH=%d", cn->content_length);
            snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
                        "CONTENT_TYPE=%s", cn->cgicontenttype);
            break;
#endif
    }

    sprintf(cgienv[cgi_index++], "REQUEST_METHOD=%s", type);

    if (cn->is_ssl)
        strcpy(cgienv[cgi_index++], "HTTPS=on");

    if (cgi_index >= CGI_ARG_SIZE)
    {
        printf("Content-type: text/plain\n\nToo many CGI args (%d, %d)\n",
                cgi_index, CGI_ARG_SIZE);
        _exit(1);
    }

    /* copy across the pointer indexes */
    for (i = 0; i < cgi_index; i++)
        cgiptr[i] = cgienv[i];

    cgiptr[i++] = "AUTH_TYPE=Basic";
    cgiptr[i++] = "GATEWAY_INTERFACE=CGI/1.1";
    cgiptr[i++] = "SERVER_PROTOCOL="HTTP_VERSION;
    cgiptr[i] = NULL;

    execve(myargs[0], myargs, cgiptr);
    printf("Content-type: text/plain\n\nshouldn't get here\n");
    _exit(1);
#endif
}

static char * cgi_filetype_match(struct connstruct *cn, const char *fn)
{
    struct cgiextstruct *tp = cgiexts;

    while (tp != NULL) 
    {
        char *t;

        if ((t = strstr(fn, tp->ext)) != NULL)
        {
            t += strlen(tp->ext);

            if (*t == '/' || *t == '\0')
                return t;
            else
                return NULL;

        }

        tp = tp->next;
    }

    return NULL;
}

static void decode_path_info(struct connstruct *cn, char *path_info)
{
    char *cgi_delim;

#if defined(CONFIG_HTTP_HAS_CGI)
    cn->is_cgi = 0;
#endif
    *cn->uri_request = '\0';
    *cn->uri_path_info = '\0';
    *cn->uri_query = '\0';

    my_strncpy(cn->uri_request, path_info, MAXREQUESTLENGTH);

    /* query info? */
    if ((cgi_delim = strchr(path_info, '?')))
    {
        *cgi_delim = '\0';
        my_strncpy(cn->uri_query, cgi_delim+1, MAXREQUESTLENGTH);
    }

#if defined(CONFIG_HTTP_HAS_CGI)
    if ((cgi_delim = cgi_filetype_match(cn, path_info)) != NULL)
    {
        cn->is_cgi = 1;     /* definitely a CGI script */

        /* path info? */
        if (*cgi_delim != '\0')
        {
            my_strncpy(cn->uri_path_info, cgi_delim, MAXREQUESTLENGTH);
            *cgi_delim = '\0';
        }
    }
#endif

    /* the bit at the start must be the script name */
    my_strncpy(cn->filereq, path_info, MAXREQUESTLENGTH);
}

static int init_read_post_data(char *buf, char *data, 
                                struct connstruct *cn, int old_rv)
{
   char *next = data;
   int rv = old_rv;
   char *post_data;

    /* Too much Post data to send. MAXPOSTDATASIZE should be 
       configured (now it can be changed in the header file) */
   if (cn->content_length > MAXPOSTDATASIZE) 
   {
       send_error(cn, 418);
       return 0;
   }
   
   /* remove CRLF */
   while ((*next == '\r' || *next == '\n') && (next < &buf[rv])) 
       next++;
   
   if (cn->post_data == NULL)
   {
       /* Allocate buffer for the POST data that will be used by proccgi 
          to send POST data to the CGI script */
       cn->post_data = (char *)calloc(1, (cn->content_length + 1)); 
   }

   cn->post_state = 0;
   cn->post_read = 0;
   post_data = cn->post_data;

   while (next < &buf[rv])
   { 
       /* copy POST data to buffer */
       *post_data++ = *next++;
       cn->post_read++;
       if (cn->post_read == cn->content_length)
       { 
           /* No more POST data to be copied */
           *post_data = '\0';
           return 1;
       }
   }

   /* More POST data has to be read. read_post_data will continue with that */
   cn->post_state = 1;
   return 0;
}

void read_post_data(struct connstruct *cn)
{
    char buf[MAXREADLENGTH], *next;
    char *post_data;
    int rv;

    memset(buf, 0, sizeof(buf));
    rv = special_read(cn, buf, sizeof(buf)-1);
    if (rv <= 0) 
    {
        if (rv < 0 || !cn->is_ssl) /* really dead? */
            removeconnection(cn);
        return;
    }

    buf[rv] = '\0';
    next = buf;
    post_data = &cn->post_data[cn->post_read];

    while (next < &buf[rv])
    {
        *post_data++ = *next++;
        cn->post_read++;

        if (cn->post_read == cn->content_length)
        {  
            /* No more POST data to be copied */
            *post_data='\0';
            cn->post_state = 0;
            buildactualfile(cn);
            cn->state = STATE_WANT_TO_SEND_HEAD;
            return;
        }
    }

    /* More POST data to read */
}

#endif  /* CONFIG_HTTP_HAS_CGI */

/* Decode string %xx -> char (in place) */
static void urldecode(char *buf) 
{
    int v;
    char *p, *s, *w;

    w = p = buf;

    while (*p) 
    {
        v = 0;

        if (*p == '%') 
        {
            s = p;
            s++;

            if (isxdigit((int) s[0]) && isxdigit((int) s[1]))
            {
                v = hexit(s[0])*16 + hexit(s[1]);

                if (v) 
                { 
                    /* do not decode %00 to null char */
                    *w = (char)v;
                    p = &s[1];
                }
            }

        }

        if (!v) *w=*p;
        p++; 
        w++;
    }

    *w='\0';
}

static int hexit(char c) 
{
    if (c >= '0' && c <= '9')
        return c - '0';
    else if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else
        return 0;
}

static void buildactualfile(struct connstruct *cn)
{
    char *cp;
    snprintf(cn->actualfile, MAXREQUESTLENGTH, ".%s", cn->filereq);

#ifndef WIN32
    /* Add directory slash if not there */
    if (isdir(cn->actualfile) && 
            cn->actualfile[strlen(cn->actualfile)-1] != '/')
        strcat(cn->actualfile, "/");

    /* work out the directory name */
    strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
    if ((cp = strrchr(cn->dirname, '/')) == NULL)
        cn->dirname[0] = 0;
    else
        *cp = 0;
#else
    {
        char curr_dir[MAXREQUESTLENGTH];
        char path[MAXREQUESTLENGTH];
        char *t = cn->actualfile;

        GetCurrentDirectory(MAXREQUESTLENGTH, curr_dir);

        /* convert all the forward slashes to back slashes */
        while ((t = strchr(t, '/')))
            *t++ = '\\';

        snprintf(path, MAXREQUESTLENGTH, "%s%s", curr_dir, cn->actualfile);
        memcpy(cn->actualfile, path, MAXREQUESTLENGTH);

        /* Add directory slash if not there */
        if (isdir(cn->actualfile) && 
                    cn->actualfile[strlen(cn->actualfile)-1] != '\\')
            strcat(cn->actualfile, "\\");

        /* work out the directory name */
        strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
        if ((cp = strrchr(cn->dirname, '\\')) == NULL)
            cn->dirname[0] = 0;
        else
            *cp = 0;
    }
#endif
}

static int sanitizefile(const char *buf) 
{
    int len, i;

    /* Don't accept anything not starting with a / */
    if (*buf != '/') 
        return 0;

    len = strlen(buf);
    for (i = 0; i < len; i++) 
    {
        /* Check for "/." i.e. don't send files starting with a . */
        if (buf[i] == '/' && buf[i+1] == '.') 
            return 0;
    }

    return 1;
}

static int sanitizehost(char *buf)
{
    while (*buf != '\0') 
    {
        /* Handle the port */
        if (*buf == ':') 
        {
            *buf = '\0';
            return 1;
        }

        /* Enforce some basic URL rules... */
        if ((isalnum((int)(*buf)) == 0 && *buf != '-' && *buf != '.') ||
                (*buf == '.' && *(buf+1) == '.') ||
                (*buf == '.' && *(buf+1) == '-') ||
                (*buf == '-' && *(buf+1) == '.'))
            return 0;

        buf++;
    }

    return 1;
}

static FILE * exist_check(struct connstruct *cn, const char *check_file)
{
    char pathname[MAXREQUESTLENGTH];
    snprintf(pathname, MAXREQUESTLENGTH, "%s/%s", cn->dirname, check_file);
    return fopen(pathname, "r");
}

#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
static void send_authenticate(struct connstruct *cn, const char *realm)
{
    char buf[1024];

    snprintf(buf, sizeof(buf), HTTP_VERSION" 401 Unauthorized\n"
         "WWW-Authenticate: Basic\n"
                 "realm=\"%s\"\n", realm);
    special_write(cn, buf, strlen(buf));
}

static int check_digest(char *salt, const char *msg_passwd)
{
    uint8_t b256_salt[MAXREQUESTLENGTH];
    uint8_t real_passwd[MD5_SIZE];
    int salt_size = sizeof(b256_salt);
    int password_size = sizeof(real_passwd);
    char *b64_passwd;
    uint8_t md5_result[MD5_SIZE];
    MD5_CTX ctx;

    /* retrieve the salt */
    if ((b64_passwd = strchr(salt, '$')) == NULL)
        return -1;

    *b64_passwd++ = 0;
    if (base64_decode(salt, strlen(salt), b256_salt, &salt_size))
        return -1;

    if (base64_decode(b64_passwd, strlen(b64_passwd), real_passwd,
                &password_size))
        return -1;

    /* very simple MD5 crypt algorithm, but then the salt we use is large */
    MD5_Init(&ctx);
    MD5_Update(&ctx, b256_salt, salt_size);           /* process the salt */
    MD5_Update(&ctx, (uint8_t *)msg_passwd, strlen(msg_passwd)); 
    MD5_Final(md5_result, &ctx);
    return memcmp(md5_result, real_passwd, MD5_SIZE);/* 0 = ok */
}

static int auth_check(struct connstruct *cn)
{
    char line[MAXREQUESTLENGTH];
    FILE *fp;
    char *cp;

    if ((fp = exist_check(cn, ".htpasswd")) == NULL)
        return 0;               /* no .htpasswd file, so let though */

    if (cn->authorization[0] == 0)
        goto error;

    /* cn->authorization is in form "username:password" */
    if ((cp = strchr(cn->authorization, ':')) == NULL)
        goto error;
    else
        *cp++ = 0;  /* cp becomes the password */

    while (fgets(line, sizeof(line), fp) != NULL)
    {
        char *b64_file_passwd;
        int l = strlen(line);

        /* nuke newline */
        if (line[l-1] == '\n')
            line[l-1] = 0;

        /* line is form "username:salt(b64)$password(b64)" */
        if ((b64_file_passwd = strchr(line, ':')) == NULL)
            continue;

        *b64_file_passwd++ = 0;

        if (strcmp(line, cn->authorization)) /* our user? */
            continue;

        if (check_digest(b64_file_passwd, cp) == 0)
        {
            fclose(fp);
            return 0;
        }
    }

error:
    fclose(fp);
    send_authenticate(cn, cn->server_name);
    return -1;
}
#endif

static int htaccess_check(struct connstruct *cn)
{
    char line[MAXREQUESTLENGTH];
    FILE *fp;
    int ret = 0;

    if ((fp = exist_check(cn, ".htaccess")) == NULL)
        return 0;               /* no .htaccess file, so let though */

    while (fgets(line, sizeof(line), fp) != NULL)
    {
        if (strstr(line, "Deny all") || /* access to this dir denied */
                    /* Access will be denied unless SSL is active */
                    (!cn->is_ssl && strstr(line, "SSLRequireSSL")) ||
                    /* Access will be denied if SSL is active */
                    (cn->is_ssl && strstr(line, "SSLDenySSL")))
        {
            ret = -1;
            break;
        }
    }

    fclose(fp);
    return ret;
}

static void send_error(struct connstruct *cn, int err)
{
    char buf[MAXREQUESTLENGTH];
    char *title;
    char *text;

    switch (err)
    {
        case 403:
            title = "Forbidden";
            text = "File is protected";
#ifdef CONFIG_HTTP_VERBOSE
            printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
#endif
            break;

        case 404:
            title = "Not Found";
            text = title;
            break;

        case 418:
            title = "POST data size is too large";
            text = title;
            break;

        default:
            title = "Unknown";
            text = "Unknown";
            break;
    }

    snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\n"
            "Content-Type: text/html\n\n"
            "<html><body>\n<title>%s</title>\n"
            "<h1>Error %d - %s</h1>\n</body></html>\n", 
            title, err, text);
    special_write(cn, buf, strlen(buf));

#ifdef CONFIG_HTTP_VERBOSE
    printf("axhttpd: http error: %s [%d]\n", title, err); TTY_FLUSH();
#endif
    removeconnection(cn);
}

static const char *getmimetype(const char *name)
{
    /* only bother with a few mime types - let the browser figure the rest out */
    if (strstr(name, ".htm"))
        return "text/html";
    else if (strstr(name, ".css"))
        return "text/css"; 
    else if (strstr(name, ".php"))
        return "application/x-http-php"; 
    else
        return "application/octet-stream";
}

static int special_write(struct connstruct *cn, 
                                        const char *buf, size_t count)
{
    if (cn->is_ssl)
    {
        SSL *ssl = cn->ssl;
        return ssl ? ssl_write(ssl, (uint8_t *)buf, count) : -1;
    }
    else
        return SOCKET_WRITE(cn->networkdesc, buf, count);
}

static int special_read(struct connstruct *cn, void *buf, size_t count)
{
    int res;

    if (cn->is_ssl)
    {
        uint8_t *read_buf;
        if ((res = ssl_read(cn->ssl, &read_buf)) > SSL_OK)
        {
            memcpy(buf, read_buf, res > (int)count ? count : res);
        }
    }
    else
        res = SOCKET_READ(cn->networkdesc, buf, count);

    return res;
}

