/*********************************************************************
 *
 *  HyperText Transfer Protocol (HTTP) Server
 *  Module for Microchip TCP/IP Stack
 *   -Serves dynamic pages to web browsers such as Microsoft Internet 
 *    Explorer, Mozilla Firefox, etc.
 *	 -Reference: RFC 2068
 *
 **********************************************************************
 * FileName:        HTTP.c
 * Dependencies:    TCP, MPFS, HTTPGetVar() callback, HTTPExecCmd() callback
 * Processor:       PIC18, PIC24F, PIC24H, dsPIC30F, dsPIC33F, PIC32
 * Compiler:        Microchip C32 v1.05 or higher
 *					Microchip C30 v3.12 or higher
 *					Microchip C18 v3.30 or higher
 *					HI-TECH PICC-18 PRO 9.63PL2 or higher
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * Copyright (C) 2002-2009 Microchip Technology Inc.  All rights
 * reserved.
 *
 * Microchip licenses to you the right to use, modify, copy, and
 * distribute:
 * (i)  the Software when embedded on a Microchip microcontroller or
 *      digital signal controller product ("Device") which is
 *      integrated into Licensee's product; or
 * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
 *		ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
 *		used in conjunction with a Microchip ethernet controller for
 *		the sole purpose of interfacing with the ethernet controller.
 *
 * You should refer to the license agreement accompanying this
 * Software for additional information regarding your rights and
 * obligations.
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 *
 * Author               Date        Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Nilesh Rajbharti     8/14/01     Original
 * Nilesh Rajbharti     9/12/01     Released (Rev. 1.0)
 * Nilesh Rajbharti     2/9/02      Cleanup
 * Nilesh Rajbharti     5/22/02     Rev 2.0 (See version.log for detail)
 * Nilesh Rajbharti     7/9/02      Rev 2.1 (Fixed HTTPParse bug)
 * Howard Schlunder		2/9/05		Fixed variable substitution 
 *									parsing (uses hex now)
 ********************************************************************/
#define __HTTP_C

#include "TCPIPConfig.h"

#if defined(STACK_USE_HTTP_SERVER)

#include "TCPIP Stack/TCPIP.h"
#include "ctype.h"


// Each dynamic variable within a CGI file should be preceeded with this character.
#define HTTP_VAR_ESC_CHAR       '%'
#define HTTP_DYNAMIC_FILE_TYPE  (HTTP_CGI)

// HTTP File Types
#define HTTP_TXT        (0u)
#define HTTP_HTML       (1u)
#define HTTP_CGI        (2u)
#define HTTP_XML        (3u)
#define HTTP_GIF        (4u)
#define HTTP_PNG        (5u)
#define HTTP_JPG        (6u)
#define HTTP_JAVA       (7u)
#define HTTP_WAV        (8u)
#define HTTP_UNKNOWN    (9u)


#define FILE_EXT_LEN    (3u)
typedef struct _FILE_TYPES
{
    BYTE fileExt[FILE_EXT_LEN+1];
} FILE_TYPES;

// Each entry in this structure must be in UPPER case.
// Order of these entries must match with those defined by "HTTP File Types" defines.
static ROM FILE_TYPES httpFiles[] =
{
    { "TXT" },          // HTTP_TXT
    { "HTM" },          // HTTP_HTML
    { "CGI" },          // HTTP_CGI
    { "XML" },          // HTTP_XML
    { "GIF" },          // HTTP_GIF
    { "PNG" },          // HTTP_PNG
    { "JPG" },          // HTTP_JPG
    { "CLA" },          // HTTP_JAVA
    { "WAV" },          // HTTP_WAV
	{ ""    }			// HTTP_UNKNOWN
};
#define TOTAL_FILE_TYPES        ( sizeof(httpFiles)/sizeof(httpFiles[0]) )


typedef struct
{
    ROM BYTE typeString[20];
} HTTP_CONTENT;

// Content entry order must match with those "HTTP File Types" define's.
static ROM HTTP_CONTENT httpContents[] =
{
    { "text/plain" },            // HTTP_TXT
    { "text/html" },             // HTTP_HTML
    { "text/html" },             // HTTP_CGI
    { "text/xml" },              // HTTP_XML
    { "image/gif" },             // HTTP_GIF
    { "image/png" },             // HTTP_PNG
    { "image/jpeg" },            // HTTP_JPG
    { "application/java-vm" },   // HTTP_JAVA
    { "audio/x-wave" },          // HTTP_WAV
	{ "" }						 // HTTP_UNKNOWN
};
#define TOTAL_HTTP_CONTENTS     ( sizeof(httpContents)/sizeof(httpConetents[0]) )

// HTTP FSM states for each connection.
typedef enum
{
    SM_HTTP_IDLE = 0u,
    SM_HTTP_GET,
    SM_HTTP_NOT_FOUND,
    SM_HTTP_GET_READ,
    SM_HTTP_GET_PASS,
    SM_HTTP_GET_DLE,
    SM_HTTP_GET_HANDLE,
    SM_HTTP_GET_HANDLE_NEXT,
    SM_HTTP_GET_VAR,
    SM_HTTP_HEADER,
    SM_HTTP_DISCARD
} SM_HTTP;

// Supported HTTP Commands
typedef enum
{
    HTTP_GET = 0u,
    HTTP_POST,
    HTTP_NOT_SUPPORTED,
    HTTP_INVALID_COMMAND
} HTTP_COMMAND;

// HTTP Connection Info - one for each connection.
typedef struct
{
    TCP_SOCKET socket;
    MPFS file;
    SM_HTTP smHTTP;
    BYTE smHTTPGet;
    WORD VarRef;
    BYTE bProcess;
    BYTE Variable;
    BYTE fileType;
} HTTP_INFO;
typedef BYTE HTTP_HANDLE;


typedef enum
{
    HTTP_NOT_FOUND = 0u,
    HTTP_NOT_AVAILABLE
} HTTP_MESSAGES;

// Following message order must match with that of HTTP_MESSAGES enum.
static ROM BYTE * ROM HTTPMessages[] =
{
	    (ROM BYTE*)"HTTP/1.1 404 Not found\r\n\r\nNot found.\r\n",
	    (ROM BYTE*)"HTTP/1.1 503 \r\n\r\nService Unavailable\r\n"
};

// Standard HTTP messages.
static ROM BYTE HTTP_OK_STRING[] = "HTTP/1.1 200 OK\r\nContent-type: ";
static ROM BYTE HTTP_OK_NO_CACHE_STRING[] = "HTTP/1.1 200 OK\r\nDate: Wed, 05 Apr 2006 02:53:05 GMT\r\nExpires: Wed, 05 Apr 2006 02:52:05 GMT\r\nCache-control: private\r\nContent-type: ";
#define HTTP_OK_STRING_LEN (sizeof(HTTP_OK_STRING)-1)
#define HTTP_OK_NO_CACHE_STRING_LEN (sizeof(HTTP_OK_NO_CACHE_STRING)-1)

static ROM BYTE HTTP_HEADER_END_STRING[] = "\r\n\r\n";
#define HTTP_HEADER_END_STRING_LEN (sizeof(HTTP_HEADER_END_STRING)-1)

// HTTP Command Strings
static ROM BYTE HTTP_GET_STRING[] = "GET";
#define HTTP_GET_STRING_LEN (sizeof(HTTP_GET_STRING)-1)

// Default HTML file.
static ROM BYTE HTTP_DEFAULT_FILE_STRING[] = "INDEX.HTM";
#define HTTP_DEFAULT_FILE_STRING_LEN (sizeof(HTTP_DEFAULT_FILE_STRING)-1)


// Maximum nuber of arguments supported by this HTTP Server.
#define MAX_HTTP_ARGS       (11u)

// Maximum HTML Command String length.
#define MAX_HTML_CMD_LEN    (100u)


static HTTP_INFO HCB[MAX_HTTP_CONNECTIONS];


static void HTTPProcess(HTTP_HANDLE h);
static HTTP_COMMAND HTTPParse(BYTE *string,
                              BYTE** arg,
                              BYTE* argc,
                              BYTE* type);
static BOOL SendFile(HTTP_INFO* ph);




/*********************************************************************
 * Function:        void HTTPInit(void)
 *
 * PreCondition:    TCP must already be initialized.
 *
 * Input:           None
 *
 * Output:          HTTP FSM and connections are initialized
 *
 * Side Effects:    None
 *
 * Overview:        Set all HTTP connections to Listening state.
 *                  Initialize FSM for each connection.
 *
 * Note:            This function is called only one during lifetime
 *                  of the application.
 ********************************************************************/
void HTTPInit(void)
{
    BYTE i;

    for ( i = 0; i <  MAX_HTTP_CONNECTIONS; i++ )
    {
        HCB[i].socket = TCPOpen(0, TCP_OPEN_SERVER, HTTP_PORT, TCP_PURPOSE_HTTP_SERVER);

        HCB[i].smHTTP = SM_HTTP_IDLE;
    }
}



/*********************************************************************
 * Function:        void HTTPServer(void)
 *
 * PreCondition:    HTTPInit() must already be called.
 *
 * Input:           None
 *
 * Output:          Opened HTTP connections are served.
 *
 * Side Effects:    None
 *
 * Overview:        Browse through each connections and let it
 *                  handle its connection.
 *                  If a connection is not finished, do not process
 *                  next connections.  This must be done, all
 *                  connections use some static variables that are
 *                  common.
 *
 * Note:            This function acts as a task (similar to one in
 *                  RTOS).  This function performs its task in
 *                  co-operative manner.  Main application must call
 *                  this function repeatdly to ensure all open
 *                  or new connections are served on time.
 ********************************************************************/
void HTTPServer(void)
{
    BYTE conn;

    for ( conn = 0;  conn < MAX_HTTP_CONNECTIONS; conn++ )
        HTTPProcess(conn);
}


/*********************************************************************
 * Function:        static BOOL HTTPProcess(HTTP_HANDLE h)
 *
 * PreCondition:    HTTPInit() called.
 *
 * Input:           h   -   Index to the handle which needs to be
 *                          processed.
 *
 * Output:          Connection referred by 'h' is served.
 *
 * Side Effects:    None
 *
 * Overview:
 *
 * Note:            None.
 ********************************************************************/
static void HTTPProcess(HTTP_HANDLE h)
{
    BYTE httpData[MAX_HTML_CMD_LEN+1];
    HTTP_COMMAND httpCommand;
    BOOL lbContinue;
    BYTE *arg[MAX_HTTP_ARGS];
    BYTE argc;
    HTTP_INFO* ph;
	WORD w;

    ph = &HCB[h];


    do
    {
        lbContinue = FALSE;

        // If during handling of HTTP socket, it gets disconnected,
        // forget about previous processing and return to idle state.
        if(!TCPIsConnected(ph->socket))
        {
            ph->smHTTP = SM_HTTP_IDLE;
            break;
        }


        switch(ph->smHTTP)
        {
        case SM_HTTP_IDLE:
			// Search for the CRLF deliminating the end of the first GET/HEAD/POST request
			w = TCPFindROMArray(ph->socket, (ROM BYTE*)"\r\n", 2, 0, FALSE);
            if(w == 0xFFFFu)
			{
				if(TCPGetRxFIFOFree(ph->socket) == 0u)
				{
					// Request is too big, we can't support it.
					TCPDisconnect(ph->socket);
				}
				break;
			}

            lbContinue = TRUE;

			if(w > sizeof(httpData)-1)
				w = sizeof(httpData)-1;

			TCPGetArray(ph->socket, httpData, w);
            httpData[w] = '\0';
            TCPDiscard(ph->socket);

            ph->smHTTP = SM_HTTP_NOT_FOUND;
            argc = MAX_HTTP_ARGS;
            httpCommand = HTTPParse(httpData, arg, &argc, &ph->fileType);
            if ( httpCommand == HTTP_GET )
            {
                // If there are any arguments, this must be a remote command.
                // Execute it and then send the file.
                // The file name may be modified by command handler.
                if ( argc > 1u )
                {
                    // Let main application handle this remote command.
                    HTTPExecCmd(&arg[0], argc);

                    // Command handler must have modified arg[0] which now
                    // points to actual file that will be sent as a result of
                    // this remote command.

                    // Assume that Web author will only use CGI or HTML
                    // file for remote command.
                    ph->fileType = HTTP_CGI;
                }

                ph->file = MPFSOpen(arg[0]);
                if ( ph->file == MPFS_INVALID )
                {
                    ph->Variable = HTTP_NOT_FOUND;
                    ph->smHTTP = SM_HTTP_NOT_FOUND;
                }
                else if ( ph->file == MPFS_NOT_AVAILABLE )
                {
                    ph->Variable = HTTP_NOT_AVAILABLE;
                    ph->smHTTP = SM_HTTP_NOT_FOUND;
                }
                else
                {
                    ph->smHTTP = SM_HTTP_HEADER;
                }
            }
            break;

        case SM_HTTP_NOT_FOUND:
            if(TCPIsPutReady(ph->socket) >= strlenpgm((ROM char*)HTTPMessages[ph->Variable]))
            {
				TCPPutROMString(ph->socket, HTTPMessages[ph->Variable]);
				TCPFlush(ph->socket);
				TCPDisconnect(ph->socket);
				ph->smHTTP = SM_HTTP_IDLE;
            }
            break;

        case SM_HTTP_HEADER:
            if ( TCPIsPutReady(ph->socket) )
            {
                lbContinue = TRUE;

                if ( ph->fileType == HTTP_DYNAMIC_FILE_TYPE )
				{
                    ph->bProcess = TRUE;
					TCPPutROMArray(ph->socket, (ROM BYTE*)HTTP_OK_NO_CACHE_STRING, HTTP_OK_NO_CACHE_STRING_LEN);
				}
                else
				{
                    ph->bProcess = FALSE;
					TCPPutROMArray(ph->socket, (ROM BYTE*)HTTP_OK_STRING, HTTP_OK_STRING_LEN);
				}

                TCPPutROMString(ph->socket, httpContents[ph->fileType].typeString);
				TCPPutROMArray(ph->socket, (ROM BYTE*)HTTP_HEADER_END_STRING, HTTP_HEADER_END_STRING_LEN);

                ph->smHTTPGet = SM_HTTP_GET_READ;
                ph->smHTTP = SM_HTTP_GET;
            }
            break;

        case SM_HTTP_GET:
			// Throw away any more data receieved - we aren't going to use it.
			TCPDiscard(ph->socket);

            if(SendFile(ph))
            {
                MPFSClose();
				TCPDisconnect(ph->socket);
                ph->smHTTP = SM_HTTP_IDLE;
            }
            break;

		default:
			break;
        }
    } while( lbContinue );
}


/*********************************************************************
 * Function:        static BOOL SendFile(HTTP_INFO* ph)
 *
 * PreCondition:    None
 *
 * Input:           ph      -   A HTTP connection info.
 *
 * Output:          File reference by this connection is served.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None.
 ********************************************************************/
static BOOL SendFile(HTTP_INFO* ph)
{
    BOOL lbTransmit;
    BYTE c;
	BYTE buffer[8];
	WORD w;
    WORD_VAL HexNumber;

    MPFSGetBegin(ph->file);

	// Check if file is dynamic (.cgi) -- need to look for and 
	// process escape sequences
	if(ph->bProcess)					
	{
		w = TCPIsPutReady(ph->socket);
		while(w)
		{
	        lbTransmit = FALSE;
	
	        if(ph->smHTTPGet != (BYTE)SM_HTTP_GET_VAR)
	        {
	            c = MPFSGet();
	            if(MPFSIsEOF())
	            {
	                MPFSGetEnd();
	                TCPFlush(ph->socket);
	                return TRUE;
	            }
	        }
	
			switch(ph->smHTTPGet)
			{
			case SM_HTTP_GET_READ:
			    if ( c == HTTP_VAR_ESC_CHAR )
			        ph->smHTTPGet = SM_HTTP_GET_DLE;
			    else
			        lbTransmit = TRUE;
			    break;
			
			case SM_HTTP_GET_DLE:
			    if ( c == HTTP_VAR_ESC_CHAR )
			    {
			        lbTransmit = TRUE;
			        ph->smHTTPGet = SM_HTTP_GET_READ;
			    }
			    else
			    {
			        HexNumber.v[1] = c;
			        ph->smHTTPGet = SM_HTTP_GET_HANDLE;
			    }
			    break;
			
			case SM_HTTP_GET_HANDLE:
			    HexNumber.v[0] = c;
			    ph->Variable = hexatob(HexNumber);
			
			    ph->smHTTPGet = SM_HTTP_GET_VAR;
			    ph->VarRef = HTTP_START_OF_VAR;
			    break;
			
			case SM_HTTP_GET_VAR:
			    ph->VarRef = HTTPGetVar(ph->Variable, ph->VarRef, &c);
			    lbTransmit = TRUE;
			    if ( ph->VarRef == HTTP_END_OF_VAR )
			        ph->smHTTPGet = SM_HTTP_GET_READ;
			    break;
			}
			
			if(lbTransmit)
			{
			    TCPPut(ph->socket, c);
				w--;
			}
		}
    }	
	else	// Static page content -- no processing required
	{
		w = TCPIsPutReady(ph->socket);
		while(w >= sizeof(buffer))
		{
			for(c = 0; c < sizeof(buffer); c++)
			{
				buffer[c] = MPFSGet();
				if(MPFSIsEOF())
				{
	                MPFSGetEnd();
					TCPPutArray(ph->socket, buffer, c);
	                TCPFlush(ph->socket);
					return TRUE;
				}
			}
	
			TCPPutArray(ph->socket, buffer, sizeof(buffer));
			w -= sizeof(buffer);
			lbTransmit = TRUE;
		}
		if(lbTransmit)
			TCPFlush(ph->socket);
	}
    ph->file = MPFSGetEnd();

    // We are not done sending a file yet...
    return FALSE;
}


/*********************************************************************
 * Function:        static HTTP_COMMAND HTTPParse(BYTE *string,
 *                                              BYTE** arg,
 *                                              BYTE* argc,
 *                                              BYTE* type)
 *
 * PreCondition:    None
 *
 * Input:           string      - HTTP Command String
 *                  arg         - List of string pointer to hold
 *                                HTTP arguments.
 *                  argc        - Pointer to hold total number of
 *                                arguments in this command string/
 *                  type        - Pointer to hold type of file
 *                                received.
 *                              Valid values are:
 *                                  HTTP_TXT
 *                                  HTTP_HTML
 *                                  HTTP_GIF
 *                                  HTTP_CGI
 *                                  HTTP_UNKNOWN
 *
 * Output:          HTTP FSM and connections are initialized
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            This function parses URL that may or may not
 *                  contain arguments.
 *                  e.g. "GET HTTP/1.0 thank.htm?name=MCHP&age=12"
 *                      would be returned as below:
 *                          arg[0] => GET
 *                          arg[1] => thank.htm
 *                          arg[2] => name
 *                          arg[3] => MCHP
 *                          arg[4] => 12
 *                          argc = 5
 *
 *                  This parses does not "de-escape" URL string.
 ********************************************************************/
static HTTP_COMMAND HTTPParse(BYTE *string,
                            BYTE** arg,
                            BYTE* argc,
                            BYTE* type)
{
    BYTE i;
    BYTE smParse;
    HTTP_COMMAND cmd;
    BYTE *ext;
    BYTE c;
    ROM BYTE *fileType;

    enum
    {
        SM_PARSE_IDLE,
        SM_PARSE_ARG,
        SM_PARSE_ARG_FORMAT
    };

    smParse = SM_PARSE_IDLE;
    ext = NULL;
    i = 0;

    // Only "GET" is supported for time being.
    if ( !memcmppgm2ram(string, (ROM void*) HTTP_GET_STRING, HTTP_GET_STRING_LEN) )
    {
        string += (HTTP_GET_STRING_LEN + 1);
        cmd = HTTP_GET;
    }
    else
    {
        return HTTP_NOT_SUPPORTED;
    }

    // Skip white spaces.
    while( *string == ' ' )
        string++;

    c = *string;

    while ( c != ' ' &&  c != '\0' && c != '\r' && c != '\n' )

    {
        // Do not accept any more arguments than we haved designed to.
        if ( i >= *argc )
            break;

        switch(smParse)
        {
        case SM_PARSE_IDLE:
            arg[i] = string;
            c = *string;
            if ( c == '/' || c == '\\' )
                smParse = SM_PARSE_ARG;
            break;

        case SM_PARSE_ARG:
            arg[i++] = string;
            smParse = SM_PARSE_ARG_FORMAT;
            /*
             * Do not break.
             * Parameter may be empty.
             */

        case SM_PARSE_ARG_FORMAT:
            c = *string;
            if ( c == '?' || c == '&' )
            {
                *string = '\0';
                smParse = SM_PARSE_ARG;
            }
            else
            {
                // Recover space characters.
                if ( c == '+' )
                    *string = ' ';

                // Remember where file extension starts.
                else if ( c == '.' && i == 1u )
                {
                    ext = ++string;
                }

                else if ( c == '=' )
                {
                    *string = '\0';
                    smParse = SM_PARSE_ARG;
                }

                // Only interested in file name - not a path.
                else if ( c == '/' || c == '\\' )
                    arg[i-1] = string+1;

            }
            break;
        }
        string++;
        c = *string;
    }
    *string = '\0';

    *type = HTTP_UNKNOWN;
    if ( ext != NULL )
    {
        ext = (BYTE*)strupr((char*)ext);

        fileType = httpFiles[0].fileExt;
        for ( c = 0; c < TOTAL_FILE_TYPES; c++ )
        {
            if ( !memcmppgm2ram((void*)ext, (ROM void*)fileType, FILE_EXT_LEN) )
            {
                *type = c;
                break;
            }
            fileType += sizeof(FILE_TYPES);

        }
    }

    if ( i == 0u )
    {
        memcpypgm2ram(arg[0], (ROM void*)HTTP_DEFAULT_FILE_STRING,
                                     HTTP_DEFAULT_FILE_STRING_LEN);
        arg[0][HTTP_DEFAULT_FILE_STRING_LEN] = '\0';
        *type = HTTP_HTML;
        i++;
    }
    *argc = i;

    return cmd;
}


#endif //#if defined(STACK_USE_HTTP_SERVER)
