/* =================================================================
 *
 *      http.c
 *
 *      Handles the client requests.
 *
 * ================================================================= 
 * ####ECOSGPLCOPYRIGHTBEGIN####
 * -------------------------------------------
 * This file is part of eCos, the Embedded Configurable Operating
 * System.
 * Copyright (C) 2005 eCosCentric Ltd.
 * 
 * eCos is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 or (at your option)
 * any later version.
 * 
 * eCos is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with eCos; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 * 
 * As a special exception, if other files instantiate templates or
 * use macros or inline functions from this file, or you compile this
 * file and link it with other works to produce a work based on this
 * file, this file does not by itself cause the resulting work to be
 * covered by the GNU General Public License. However the source code
 * for this file must still be made available in accordance with
 * section (3) of the GNU General Public License.
 * 
 * This exception does not invalidate any other reasons why a work
 * based on this file might be covered by the GNU General Public
 * License.
 *
 * -------------------------------------------
 * ####ECOSGPLCOPYRIGHTEND####
 * =================================================================
 * #####DESCRIPTIONBEGIN####
 * 
 *  Author(s):    Anthony Tonizzo (atonizzo@gmail.com)
 *  Contributors: Sergei Gavrikov (w3sg@SoftHome.net)
 *  Date:         2006-06-12
 *  Purpose:      
 *  Description:  
 *               
 * ####DESCRIPTIONEND####
 * 
 * =================================================================
 */

#include <pkgconf/hal.h>
#include <pkgconf/kernel.h>
#include <cyg/kernel/kapi.h>           // Kernel API.
#include <cyg/infra/diag.h>            // For diagnostic printing.
#include <network.h>
#include <time.h>

#include <cyg/hal/hal_tables.h>
#include <cyg/fileio/fileio.h>
#include <stdio.h>                     // sprintf().
#include <stdlib.h>

#ifdef CYGOPT_NET_ATHTTPD_USE_CGIBIN_OBJLOADER
#include <cyg/objloader/elf.h>
#include <cyg/objloader/objelf.h>
#endif

#include <cyg/athttpd/http.h>
#include <cyg/athttpd/socket.h>
#include <cyg/athttpd/handler.h>
#include <cyg/athttpd/forms.h>

cyg_int32 debug_print = 0;

const char *day_of_week[7] = { "Sun", "Mon", "Tue", "Wed", 
                                 "Thu", "Fri", "Sat" };
const char *month_of_year[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
const char *home_pages[] = { "index.html",   "index.htm",
                             "default.html", "home.html" };
CYG_HAL_TABLE_BEGIN(cyg_httpd_mime_table, httpd_mime_table);
CYG_HAL_TABLE_END(cyg_httpd_mime_table_end, httpd_mime_table);

__externC cyg_httpd_mime_table_entry cyg_httpd_mime_table[];
__externC cyg_httpd_mime_table_entry cyg_httpd_mime_table_end[];

// Standard handlers added by default. Correspond to the most used extensions.
// The user can add his/her own later.
CYG_HTTPD_MIME_TABLE_ENTRY(hal_htm_entry, "htm",
                                       "text/html; charset=iso-8859-1");
CYG_HTTPD_MIME_TABLE_ENTRY(hal_html_entry, "html", 
                                       "text/html; charset=iso-8859-1");
CYG_HTTPD_MIME_TABLE_ENTRY(hal_gif_entry, "gif", "image/gif");
CYG_HTTPD_MIME_TABLE_ENTRY(hal_jpg_entry, "jpg", "image/jpg");
CYG_HTTPD_MIME_TABLE_ENTRY(hal_css_entry, "css", "text/css");
CYG_HTTPD_MIME_TABLE_ENTRY(hal_js_entry, "js", "application/x-javascript");

void
cyg_httpd_format_time_string(char *ptr, time_t *t)
{
    struct tm *gmt_time = gmtime(t);
    if (gmt_time != NULL)
        sprintf(ptr, 
                "%s, %02d %s %d %02d:%02d:%02d GMT",
                day_of_week[gmt_time->tm_wday],
                gmt_time->tm_mday,
                month_of_year[gmt_time->tm_mon],
                1900 + gmt_time->tm_year,
                gmt_time->tm_hour,
                gmt_time->tm_min,
                gmt_time->tm_sec);
    else     
        // If anything fails, then we'll just create a bogus date.
        sprintf(ptr, "Thu, 01 Jan 1970 00:00:01 GMT");
}

void 
cyg_httpd_send_error(cyg_int32 err_type)
{
    httpstate.status_code = err_type;

#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    diag_printf("Sending error: %d\n", err_type);
#endif    

#ifdef CYGOPT_NET_ATHTTPD_USE_FS
    // Check if the user has defines his own error pages.
    struct stat sp;
    char file_name[CYG_HTTPD_MAXPATH];
    strcpy(file_name, CYGDAT_NET_ATHTTPD_SERVEROPT_ROOTDIR);
    if (file_name[strlen(file_name)-1] != '/')
        strcat(file_name, "/");
    strcat(file_name, CYGDAT_NET_ATHTTPD_SERVEROPT_ERRORDIR);
    if (file_name[strlen(file_name)-1] != '/')
        strcat(file_name, "/");
    sprintf(file_name + strlen(file_name), "error_%d.html", err_type);
    cyg_httpd_cleanup_filename(file_name);
    cyg_int32 rc = stat(file_name, &sp);
    if (rc == 0)
    {
        char *extension = rindex(file_name, '.');
        if (extension == NULL)
            // No extension in the file name.
            httpstate.mime_type = 0;
        else
            httpstate.mime_type = cyg_httpd_find_mime_string(++extension);

        httpstate.payload_len  = sp.st_size;
        cyg_int32 header_length = cyg_httpd_format_header();
        cyg_httpd_write(httpstate.outbuffer, header_length);
    
        // File found.
        FILE *fp = fopen(file_name, "r");
        if(fp == NULL)
            return;

        ssize_t payload_size = fread(httpstate.outbuffer, 
                                     1, 
                                     CYG_HTTPD_MAXOUTBUFFER, 
                                     fp);
        while (payload_size > 0)
        {
            ssize_t bytes_written = cyg_httpd_write_chunked(httpstate.outbuffer, 
                                                            payload_size);
            if (bytes_written != payload_size)
                break;

            payload_size = fread(httpstate.outbuffer, 
                                 1, 
                                 CYG_HTTPD_MAXOUTBUFFER, 
                                 fp);
        }

        fclose(fp);
        return;
    }
#endif    
    
    // If no file has been defined, send a simple notification. We use inbuffer
    //  for temporary storage of the error messages, so that we know the length
    //  of the frame prior to building the header, thus avoiding the use of 
    //  chunked frames.
    strcpy(httpstate.inbuffer, 
           "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n");
    switch (err_type)
    {
    case CYG_HTTPD_STATUS_MOVED_PERMANENTLY:
        strcat(httpstate.inbuffer, 
               "<html><head><title>301 Moved Permanently</title></head>\r\n"
               "<body><h1>Moved Permanently</h1>\r\n"
               "<p>The documenthas moved <a href=\"" );
        strcat(httpstate.inbuffer, httpstate.url);
        strcat(httpstate.inbuffer, "\">here</a>.\r\n");
        break;
    case CYG_HTTPD_STATUS_NOT_AUTHORIZED:
        strcat(httpstate.inbuffer, 
               "<html><head><title>401 Not Authorized</title></head>\r\n");
        strcat(httpstate.inbuffer, 
               "<p>Authorization required to access this URL.</p>");
        break;    
    case CYG_HTTPD_STATUS_NOT_MODIFIED:
        httpstate.mode |= CYG_HTTPD_SEND_HEADER_ONLY;
        break;    
    case CYG_HTTPD_STATUS_NOT_FOUND:
        strcat(httpstate.inbuffer, 
               "<html><head><title>404 Not Found</title></head>\r\n");
        sprintf(httpstate.inbuffer + strlen(httpstate.inbuffer),
                 "<p>The requested URL: %s was not found on"
                 " this server</p>",
                 httpstate.url);
        break;
    case CYG_HTTPD_STATUS_SYSTEM_ERROR:
        strcat(httpstate.inbuffer, 
               "<html><head><title>500 Server Error</title></head>\r\n");
        strcat(httpstate.inbuffer, 
               "<p>The server encountered an unexpected condition that "
               "prevented it from fulfilling the request by the client</p>");
        break;
    case CYG_HTTPD_STATUS_NOT_IMPLEMENTED:
        strcat(httpstate.inbuffer, 
               "<html><head><title>501 Not Implemented</title></head>\r\n");
        strcat(httpstate.inbuffer, 
               "<p>The method requested is not implemented</p>");
        break;
    default:
        strcat(httpstate.inbuffer, 
               "<html><head><title>400 Bad Request</title></head>\r\n");
        strcat(httpstate.inbuffer, 
               "<p>Bad request</p>");
        break;
    }
    
    sprintf(httpstate.inbuffer + strlen(httpstate.inbuffer),
            "</p><hr><address>%s at %d.%d.%d.%d Port %d</address>\r\n"
            "</body></html>\r\n",
            CYGDAT_NET_ATHTTPD_SERVEROPT_SERVERID,
            httpstate.host[0],
            httpstate.host[1],
            httpstate.host[2],
            httpstate.host[3],
            CYGNUM_NET_ATHTTPD_SERVEROPT_PORT);
    
    httpstate.last_modified = -1;
    httpstate.mime_type = cyg_httpd_find_mime_string("html");
    httpstate.mode |= CYG_HTTPD_MODE_CLOSE_CONN;
    httpstate.payload_len  = strlen(httpstate.inbuffer);
    cyg_int32 header_length = cyg_httpd_format_header();
    cyg_httpd_write(httpstate.outbuffer, header_length);
    if (httpstate.mode & CYG_HTTPD_SEND_HEADER_ONLY)
        return;
    cyg_httpd_write(httpstate.inbuffer, strlen(httpstate.inbuffer));
}

time_t
cyg_httpd_parse_date(char *time)
{
    int    i;
    char   month[4];
    struct tm tm_mod;

    // We are going to get rid of the day of the week. This is always the first
    //  part of the string, separated by a blank.
    time = strchr( time, ' ' );
    if ( time == NULL )
        return 0;
    time++;

    /// RFC1123. The date is in the format: Sun, 06 Nov 1994 08:49:37 GMT.
    cyg_int32 rc = sscanf(time,
                          "%2d %3s %4d %2d:%2d:%2d GMT",
                          &tm_mod.tm_mday,
                          month,
                          &tm_mod.tm_year,
                          &tm_mod.tm_hour,
                          &tm_mod.tm_min,
                          &tm_mod.tm_sec);
    if (rc != 6)
    {
        // RFC1036. The date is in the format: Sunday, 06-Nov-94 08:49:37 GMT.
        rc = sscanf(time,
                    "%2d-%3s-%2d %2d:%2d:%2d GMT",
                    &tm_mod.tm_mday,
                    month,
                    &tm_mod.tm_year,
                    &tm_mod.tm_hour,
                    &tm_mod.tm_min,
                    &tm_mod.tm_sec);
        if (rc != 6)
        {
            // asctime() in the stdlibc library.
            // The date is in the format: Sun Nov 6 08:49:37 1994.
            rc = sscanf(time,"%3s %2d %2d:%2d:%2d %4d",
                        month,
                        &tm_mod.tm_mday,
                        &tm_mod.tm_hour,
                        &tm_mod.tm_min,
                        &tm_mod.tm_sec,
                        &tm_mod.tm_year);
            if (rc != 6)
                return 0;
        }
    }

    for (i = 0; i < sizeof(month_of_year); i++)
        if (strcmp(month, month_of_year[i]) == 0)
        {
            tm_mod.tm_mon = i;
            if (tm_mod.tm_year > 1900)
                tm_mod.tm_year -= 1900;
            return mktime(&tm_mod);
        }

    return 0;
}

// Finds the mime string into the mime_table associated with a specific 
//  extension. Returns the MIME type to send in the header, or NULL if the
//  extension is not in the table.
char*
cyg_httpd_find_mime_string(char *ext)
{
    cyg_httpd_mime_table_entry *entry = cyg_httpd_mime_table;

    while (entry != cyg_httpd_mime_table_end)
    {
        if (!strcmp((const char*)ext, entry->extension ))
            return entry->mime_string;
        entry++;
    }
            
    return (char*)0;
}

void
cyg_httpd_cleanup_filename(char *filename)
{
    char *src  = filename;

    src = strstr(filename, "//");
    while (src != 0)
    {
        strcpy(src + 1, src + 2);
        src = strstr(filename, "//");
    }

    src = strstr(filename, "/./");
    while (src != 0)
    {
        strcpy(src + 1, src + 3);
        src = strstr(filename, "/./");
    }

    src = strstr(filename, "/../");
    while (src != 0)
    {
        char *comp1 = filename, *comp2 = filename;

        // Search the path component before this redirection.
        while ((comp1 = strchr(comp1, '/')) != src)
            comp2 = ++comp1;

        strcpy(comp2, src + 4);
        src = strstr(filename, "/../");
    }
 }

cyg_int32
cyg_httpd_initialize(void)
{
    httpstate.needs_auth = (cyg_httpd_auth_table_entry *)0;
    return 0;
}

void
cyg_httpd_append_homepage(char *root)
{
#ifdef CYGOPT_NET_ATHTTPD_USE_FS
    struct stat sp;
    cyg_int32 i;

    cyg_int32 root_len = strlen(root);
    for (i = 0; i < sizeof(home_pages)/sizeof(char*); i++)
    {
        root[root_len] = '\0';
        sprintf(root + root_len, "%s", home_pages[i]);
        cyg_int32 rc = stat(root, &sp);
        if (rc == 0)
            return;
    }
    root[root_len] = 0;
#endif    
    
#ifdef CYGDAT_NET_ATHTTPD_ALTERNATE_HOME    
    if (strcmp(root, "/") == 0)
        // The client is trying to open the main index file.
        strcat(root, CYGDAT_NET_ATHTTPD_ALTERNATE_HOME);
#endif    
}

#ifdef CYGOPT_NET_ATHTTPD_USE_FS
void
cyg_httpd_send_file(char *name)
{
    cyg_int32  err;
    FILE      *fp;
    struct stat sp;
    char       file_name[CYG_HTTPD_MAXPATH];

#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    diag_printf("Sending file: %s\n", name);
#endif    
#ifdef CYGOPT_NET_ATHTTPD_USE_AUTH
    // Let's check that the requested URL is not inside some directory that 
    //  needs authentication.
    cyg_httpd_auth_table_entry* auth = cyg_httpd_is_authenticated(name);
    if (auth != 0)
    {
        cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_AUTHORIZED);
        return;
    }
#endif

    strcpy(file_name, CYGDAT_NET_ATHTTPD_SERVEROPT_ROOTDIR);
    if (file_name[strlen(file_name)-1] != '/')
        strcat(file_name, "/");
    strcat(file_name, name);
    cyg_httpd_cleanup_filename(file_name);
        
    // Check if the file is in the file system. This will also give us the
    //  size of the file, to be used in the HTTP header.
    cyg_int32 rc = stat(file_name, &sp);
    if (rc < 0)
    {
        // Before giving up, we make a last ditch attempt at finding a file
        //  within the internal resources of the server. The user can add
        //  his/her own files to the table.
        cyg_httpd_ires_table_entry *p = cyg_httpd_find_ires(name);
        if (p != NULL)
        {
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
            diag_printf("Sending Internal Resource: %s\n", name);
#endif    
            cyg_httpd_send_ires(p);
        }    
        else    
            cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_FOUND);
        return;
    }
    
    if (S_ISDIR(sp.st_mode))
    {
        char tmp_url[CYG_HTTPD_MAXURL];
        strcpy(tmp_url, httpstate.url);
        // Directories need a trialing slash, and if missing, we'll redirect
        //  the client to the right URL. This is called (appropriately
        //  enough) "Trailing-Slash Redirection". 
        if (name[strlen(name)-1] != '/')
        {
            if (CYGNUM_NET_ATHTTPD_SERVEROPT_PORT == 80)
                sprintf(httpstate.url,
                        "http://%d.%d.%d.%d%s/",
                        httpstate.host[0],
                        httpstate.host[1],
                        httpstate.host[2],
                        httpstate.host[3],
                        tmp_url);
            else            
                sprintf(httpstate.url,
                        "http://%d.%d.%d.%d:%d%s/",
                        httpstate.host[0],
                        httpstate.host[1],
                        httpstate.host[2],
                        httpstate.host[3],
                        CYGNUM_NET_ATHTTPD_SERVEROPT_PORT,
                        tmp_url);
            cyg_httpd_send_error(CYG_HTTPD_STATUS_MOVED_PERMANENTLY);
            return;
        }

        // We are going to try to locate an index page in the directory we got
        //  in the URL. 
        cyg_httpd_append_homepage(file_name);
        if (file_name[strlen(file_name)-1] == '/')
        {
#ifdef CYGOPT_NET_ATHTTPD_USE_DIRLIST
            // No home page found, we are sending a directory listing.
            cyg_httpd_send_directory_listing(name);
#else
            cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_FOUND);
#endif
            return;
        }
        stat(file_name, &sp);
    }
    
    httpstate.last_modified = sp.st_mtime;
    
    // Let's see if we luck out and can send a 304.
    if ((httpstate.modified_since != -1) && 
                   (httpstate.modified_since >= sp.st_mtime))
    {                   
        cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_MODIFIED);
        return;
    }    
    else    
        httpstate.status_code = CYG_HTTPD_STATUS_OK;

    // Here we'll look for extension to the file. Consider the case where
    //  there might be more than one dot in the file name. We'll look for
    //  the last dot, then we'll check the extension.
    char *extension = rindex(file_name, '.');
    if (extension == NULL)
        httpstate.mime_type = 0;
    else    
        httpstate.mime_type = cyg_httpd_find_mime_string(++extension);

    httpstate.payload_len  = sp.st_size;
    cyg_int32 payload_size = cyg_httpd_format_header();
    if ((httpstate.mode & CYG_HTTPD_SEND_HEADER_ONLY) != 0)
    {                 
        send(httpstate.sockets[httpstate.client_index].descriptor, 
             httpstate.outbuffer, 
             payload_size,
             0);
        return;
    }

    fp = fopen(file_name, "r");
    if(fp == NULL)
    {
        // We should really read errno and send messages accordingly...
        cyg_httpd_send_error(CYG_HTTPD_STATUS_SYSTEM_ERROR);
        return;
    }
    
    // Fill up the rest of the buffer and send it out.
    cyg_int32 bread = fread(httpstate.outbuffer + strlen(httpstate.outbuffer),
                            1, 
                            CYG_HTTPD_MAXOUTBUFFER - payload_size,
                            fp);
    cyg_httpd_write(httpstate.outbuffer, payload_size + bread);

    cyg_iovec bufs[2] = { { httpstate.outbuffer, 0 }, 
                          { httpstate.inbuffer,  0 } };
    ssize_t bytes_written = 0;
    sp.st_size -= bread;
    while (bytes_written < sp.st_size)
        if (sp.st_size - bytes_written <= CYG_HTTPD_MAXOUTBUFFER)
        {
            bufs[0].iov_len = fread(httpstate.outbuffer,
                                    1, 
                                    CYG_HTTPD_MAXOUTBUFFER, 
                                    fp);
            bytes_written += cyg_httpd_write(httpstate.outbuffer, 
                                             bufs[0].iov_len);
        }        
        else    
        {
            bufs[0].iov_len = fread(httpstate.outbuffer, 
                                    1, 
                                    CYG_HTTPD_MAXOUTBUFFER, 
                                    fp);
            bufs[1].iov_len = fread(httpstate.inbuffer, 
                                    1, 
                                    CYG_HTTPD_MAXINBUFFER, 
                                    fp);
            bytes_written += cyg_httpd_writev(bufs, 2);
        }    
    
    err = fclose(fp);
    if(err < 0)
        cyg_httpd_send_error(CYG_HTTPD_STATUS_SYSTEM_ERROR);
}
#endif

cyg_int32
cyg_httpd_format_header(void)
{
    char date[CYG_HTTPD_TIME_STRING_LEN];
    sprintf(httpstate.outbuffer, "HTTP/1.1 %d", httpstate.status_code);
    time_t time_val = time(NULL);
    
    // Error messages (i.e. with status other than OK, automatically add
    //  the no-cache header.
    switch (httpstate.status_code)
    {
    case CYG_HTTPD_STATUS_MOVED_PERMANENTLY:
        strcat(httpstate.outbuffer, " Moved Permanently\r\n");
        strcat(httpstate.outbuffer, "Location: ");
        strcat(httpstate.outbuffer, httpstate.url);
        strcat(httpstate.outbuffer, "\r\n");
        strcat(httpstate.outbuffer, "Cache-Control: no-cache\r\n");
        sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                "Content-Length: %d\r\n",
                httpstate.payload_len);
        break;
#ifdef CYGOPT_NET_ATHTTPD_USE_AUTH
    case CYG_HTTPD_STATUS_NOT_AUTHORIZED:
        // A 401 error closes the connection right away.
        httpstate.mode |= CYG_HTTPD_MODE_CLOSE_CONN;
        strcat(httpstate.outbuffer, " Not Authorized\r\n");
        
        // Here we should set the proper header based on the authentication
        //  required (httpstate.needs_authMode) but for now, with only
        //  Basic Authentication supported, there is no need to do so.
        if (httpstate.needs_auth->auth_mode == CYG_HTTPD_AUTH_BASIC)
        {
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                    "WWW-Authenticate: Basic realm=\"%s\"\r\n",
                    httpstate.needs_auth->auth_domainname);
        }
        else             
        {
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                     "WWW-Authenticate: Digest realm=\"%s\" ",
                     httpstate.needs_auth->auth_domainname);
            cyg_httpd_format_time_string(cyg_httpd_md5_nonce, &time_val);
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                    "nonce=\"%s\" ", cyg_httpd_md5_nonce);
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                    "opaque=\"%s\", ", 
                    CYG_HTTPD_MD5_AUTH_OPAQUE);
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                    "stale=false, algorithm=%s, qop=\"%s\"\r\n",
                    CYG_HTTPD_MD5_AUTH_NAME,
                    CYG_HTTPD_MD5_AUTH_QOP );
        }
        strcat(httpstate.outbuffer, "Cache-Control: no-cache\r\n");
        break;
#endif
    case CYG_HTTPD_STATUS_NOT_MODIFIED:
        strcat(httpstate.outbuffer, " Not Modified\r\n");
        strcat(httpstate.outbuffer, "Cache-Control: no-cache\r\n");
        break;
    case CYG_HTTPD_STATUS_NOT_FOUND:
        strcat(httpstate.outbuffer, " Not Found\r\n");
        strcat(httpstate.outbuffer, "Cache-Control: no-cache\r\n");
        sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                "Content-Length: %d\r\n", 
                httpstate.payload_len);
        break;
    case CYG_HTTPD_STATUS_METHOD_NOT_ALLOWED:
        strcat(httpstate.outbuffer, " Method Not Allowed\r\n");
        strcat(httpstate.outbuffer, "Cache-Control: no-cache\r\n");
        break;
    default:
        strcat(httpstate.outbuffer, " OK\r\n");
        if ((httpstate.mode & CYG_HTTPD_MODE_TRANSFER_CHUNKED) == 0)
            sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                    "Content-Length: %d\r\n", 
                    httpstate.payload_len);
        break;
    }

    cyg_httpd_format_time_string(date, &time_val);
    sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer), 
             "Date: %s\r\n", 
             date);
    sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer), 
             "Server: %s\r\n", 
             CYGDAT_NET_ATHTTPD_SERVEROPT_SERVERID);
    
    if (httpstate.mode & CYG_HTTPD_MODE_CLOSE_CONN)
        strcat(httpstate.outbuffer, "Connection: close\r\n");
    else
        strcat(httpstate.outbuffer, "Connection: keep-alive\r\n");

    if (httpstate.mime_type != 0)
    {
        sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                 "Content-Type: %s\r\n", 
                 httpstate.mime_type);
    }             
    else
        // When we cannot find the appropriate MIME type, we'll send a
        //  default type.
        sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                "Content-Type: %s\r\n", 
                CYGDAT_NET_ATHTTPD_DEFAULT_MIME_TYPE);

    if (httpstate.mode & CYG_HTTPD_MODE_TRANSFER_CHUNKED)
        strcat(httpstate.outbuffer, "Transfer-Encoding: chunked\r\n");

    if (httpstate.last_modified != -1)
    {
        time_val = httpstate.last_modified;
        cyg_httpd_format_time_string(httpstate.inbuffer, &time_val);
        sprintf(httpstate.outbuffer + strlen(httpstate.outbuffer),
                 "Last-Modified: %s\r\n", 
                 httpstate.inbuffer);
    }        
                 
    // There must be 2 carriage returns between the header and the body, 
    //  so if you modify this function make sure that there is another 
    //  CRLF already terminating the buffer thus far.
    strcat(httpstate.outbuffer, "\r\n");
    return strlen(httpstate.outbuffer);
}

void
cyg_httpd_handle_method_GET(void)
{
    // Handlers are executed first.
    handler h = cyg_httpd_find_handler();
    if (h != 0)
    {
        // A handler was found. We'll call the function associated to it. If 
        //  the return value is 0 we'll also try to see if the file by the same
        //  name is available in the file system or internal resources.
#ifdef CYGOPT_NET_ATHTTPD_USE_FS
        cyg_int32 rc = h(&httpstate);
        if (rc != 0)
            return;
#else            
        h(&httpstate);
        return;
#endif        
    }
    
#ifdef CYGOPT_NET_ATHTTPD_USE_CGIBIN_OBJLOADER
    // If the URL is a CGI script, there is a different directory...
    if (httpstate.url[0] == '/' &&
                    !strncmp(httpstate.url + 1, 
                              CYGDAT_NET_ATHTTPD_SERVEROPT_CGIDIR, 
                              strlen(CYGDAT_NET_ATHTTPD_SERVEROPT_CGIDIR)))
    {                              
        cyg_httpd_exec_cgi();
        return;
    }
    // If the OBJLOADER package is not loaded, then the request for a library
    //  will likely generate a 404.
#endif    

#ifdef CYGOPT_NET_ATHTTPD_USE_FS
    // No handler, we'll redirect to the file system.
    cyg_httpd_send_file(httpstate.url);
#else
    // If we do not have a file system, we look for the file within the 
    //  internal resources of the server. The user can add his/her own files
    //  to the table.
    if (strcmp(httpstate.url, "/") == 0)
    {
        int i;
        cyg_httpd_ires_table_entry *p;
        for (i = 0; i < sizeof(home_pages)/sizeof(char*); i++)
        {
            httpstate.url[1] = '\0';
            strcat(httpstate.url, home_pages[i]);
            p = cyg_httpd_find_ires(httpstate.url);
            if (p != NULL)
            {
                cyg_httpd_send_ires(p);
                return;
            }    
        }        
    }
    else
    {
        cyg_httpd_ires_table_entry *p = cyg_httpd_find_ires(httpstate.url);
        if (p != NULL)
        {
            cyg_httpd_send_ires(p);
            return;
        }    
    }        
    cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_FOUND);
#endif    
}

char*
cyg_httpd_get_URL(char* p)
{
    char* dest = httpstate.url;

    // First get rid of multiple leading slashes.
    while ((p[0] == '/') && (p[1] == '/'))
       p++;

    // Store the url, and check if there is a form result in it.
    while ((*p != ' ') && (*p != '?') &&
            ((dest - httpstate.url) < CYG_HTTPD_MAXURL - 1))
    {
        // Look for encoded characters in the URL.
        if (*p == '%') 
        {
            p++;
            if (*p) 
                *dest = cyg_httpd_from_hex(*p++) * 16;
            if (*p) 
                *dest += cyg_httpd_from_hex(*p++);
        }
        else 
            *dest++ = *p++;
    }

    // Terminate the file name...
    *dest = '\0';

    // The URL must start with a leadng slash.
    if (httpstate.url[0] != '/') 
    {
        cyg_httpd_send_error(CYG_HTTPD_STATUS_BAD_REQUEST);
        return (char*)0;
    }

    // Run past white spaces.
    while ((*p == ' ') && (*p != '\0'))
        p++;
    return p;
}

char*
cyg_httpd_parse_POST(char* p)
{
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    diag_printf("POST Request URL: %s\n", httpstate.url);
#endif    
    httpstate.method = CYG_HTTPD_METHOD_POST;
    httpstate.mode &= ~CYG_HTTPD_SEND_HEADER_ONLY;
    p = cyg_httpd_get_URL(p);
    if (p == 0)
        return 0;

    while (*p++ != '\n');
    return p;
}

char*
cyg_httpd_parse_GET(char* p)
{
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    diag_printf("GET Request URL: %s\n", httpstate.url);
#endif    
    httpstate.method = CYG_HTTPD_METHOD_GET;
    httpstate.mode &= ~CYG_HTTPD_SEND_HEADER_ONLY;
    p = cyg_httpd_get_URL(p);
    if (p == 0)
        return 0;
    
    if (*p == '?')
        // If we have a GET header with form variables we'll get the
        //  variables out of it and store them in the variable table.
        p = cyg_httpd_store_form_data(++p);

    // Run past white spaces.
    while (*p == ' ')
        p++;
    return p;
}

char*
cyg_httpd_parse_HEAD(char* p)
{
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    diag_printf("HEAD Request URL: %s\n", httpstate.url);
#endif    
    httpstate.method = CYG_HTTPD_METHOD_HEAD;
    httpstate.mode |= CYG_HTTPD_SEND_HEADER_ONLY;
    p = cyg_httpd_get_URL(p);
    if (p == 0)
        return 0;

    // Can we have variables in a HEAD request? I'll assume the answer is a yes
    //  for now.
    if (*p == '?')
        p = cyg_httpd_store_form_data(++p);

    // Run past white spaces.
    while (*p == ' ')
        p++;
    return p;
}

cyg_int32
cyg_httpd_process_header(void)
{
    char *cp;
    char *p = httpstate.inbuffer;
    
    // The deafult for HTTP 1.1 is keep-alive connections, unless specifically
    //  closed by the far end.
    httpstate.mode &= ~CYG_HTTPD_MODE_CLOSE_CONN;
    httpstate.modified_since = -1;
    while (*p)
    {
        if (!strncasecmp("GET ", p, 4))
        {
            p = cyg_httpd_parse_GET(httpstate.inbuffer + 4);
            if (p ==0)
                return 0;
        }
        else if (!strncasecmp("POST ", p, 5))
        {
            p = cyg_httpd_parse_POST(httpstate.inbuffer + 5);
            if (p ==0)
                return 0;
        }
        else if (!strncasecmp("HEAD ", p, 5))
        {
            p = cyg_httpd_parse_HEAD(httpstate.inbuffer + 5);
            if (p ==0)
                return 0;
        }
        else if (strncasecmp(p, "Content-Length: ", 16) == 0)
        {
            cp = strchr(p, ':') + 2;
            if(cp)
                httpstate.inbuffer_len = atoi(cp);
            while(*p != '\n')
                p++;
        }
        else if (strncasecmp("Host:", p, 5) == 0)
        {
            p += 5;
            if ( *p == ' ' )
                p++;
            sscanf(p,
                   "%d.%d.%d.%d",
                   &httpstate.host[0],
                   &httpstate.host[1],
                   &httpstate.host[2],
                   &httpstate.host[3]);
            while ( *p != '\n')
               p++;
            p++;
        }
        else if (strncasecmp("If-Modified-Since:", p, 18) == 0)
        {
            p += 18;
            if ( *p == ' ' )
                p++;
            httpstate.modified_since = cyg_httpd_parse_date(p);
            while ( *p != '\n')
               p++;
            p++;
        }
#ifdef CYGOPT_NET_ATHTTPD_USE_AUTH
        else if (strncasecmp("Authorization:", p, 14) == 0)
        {
            p += 14;
            while (*p == ' ')
                p++;
            if (!strncasecmp("Basic", p, 5))
            {
                char *cr = cyg_httpd_md5_digest;
                p += 5;
                while (*p == ' ')
                    p++;
                while ((*p != '\r') && (*p != '\n') && (*p != ' '))
                    *cr++ = *p++;
                *cr = '\0';
            }
            else if (strncasecmp(p, "Digest", 6) == 0)
            {
                diag_printf(httpstate.inbuffer);
                p += 6;
                while (*p == ' ')
                   p++;
                while ((*p != '\r') && (*p != '\n'))
                {
                    if (strncasecmp(p, "realm=", 6) == 0)
                        p = cyg_httpd_digest_skip(p + 6);
                    else if (strncasecmp(p, "username=", 9) == 0)
                        p = cyg_httpd_digest_skip(p + 9);
                    else if (strncasecmp(p, "nonce=", 6) == 0)
                        p = cyg_httpd_digest_skip(p + 6);
                    else if (strncasecmp(p, "response=", 9) == 0)
                        p = cyg_httpd_digest_data(cyg_httpd_md5_response, 
                                                  p + 9);
                    else if (strncasecmp(p, "cnonce=", 7) == 0)
                        p = cyg_httpd_digest_data(cyg_httpd_md5_cnonce, p + 7);
                    else if (strncasecmp(p, "qop=", 4) == 0)
                        p = cyg_httpd_digest_skip(p + 4);
                    else if (strncasecmp(p, "nc=", 3) == 0)
                        p = cyg_httpd_digest_data(cyg_httpd_md5_noncecount, 
                                                  p + 3);
                    else if (strncasecmp(p, "algorithm=", 10) == 0)
                        p = cyg_httpd_digest_skip(p + 10);
                    else if (strncasecmp(p, "opaque=", 7) == 0)
                        p = cyg_httpd_digest_skip(p + 7);
                    else if (strncasecmp(p, "uri=", 4) == 0)
                        p = cyg_httpd_digest_skip(p + 4);
                }
            }    
            else
            {
                while (*p != '\n')
                   p++;
                p++;
            }    
        }   
#endif // CYGOPT_NET_ATHTTPD_USE_AUTH
        else if (strncasecmp(p, "Connection:", 11) == 0)
        {
            p += 11;
            while (*p == ' ')
               p++;
            if (strncasecmp(p, "close", 5) == 0)
                httpstate.mode |= CYG_HTTPD_MODE_CLOSE_CONN;
            while(*p != '\n')
                p++;
            p++;    
        }
        else
        {
            // We'll just dump the rest of the line and move on to the next.
            while(*p != '\n')
                p++;
            p++;
        }
    }
    return 1;
}

cyg_int32
cyg_httpd_process_method(void)
{
    cyg_int32 rc = cyg_httpd_process_header();
    if (rc == 0)
        return 0;
        
    switch (httpstate.method)
    {
    case CYG_HTTPD_METHOD_GET:
    case CYG_HTTPD_METHOD_HEAD:
        cyg_httpd_handle_method_GET();
        break;
    case CYG_HTTPD_METHOD_POST:
        cyg_httpd_handle_method_POST();
        break;
    default:
        cyg_httpd_send_error(CYG_HTTPD_STATUS_NOT_IMPLEMENTED);
        break;
    }
    return 0;
}

