/*
 *******************************************************************************
 * Copyright 1998-2010 NetBurner, Inc.  ALL RIGHTS RESERVED
 *    Permission is hereby granted to purchasers of NetBurner Hardware to use or
 *    modify this computer program for any use as long as the resultant program
 *    is only executed on NetBurner provided hardware.
 *
 *    No other rights to use this program or its derivatives in part or in whole
 *    are granted.
 *
 *    It may be possible to license this or other NetBurner software for use on
 *    non-NetBurner Hardware.
 *    Please contact sales@Netburner.com for more information.
 *
 *    NetBurner makes no representation or warranties with respect to the
 *    performance of this computer program, and specifically disclaims any
 *    responsibility for any damages, special or consequential, connected with
 *    the use of this program.
 *---------------------------------------------------------------------
 * NetBurner, Inc.
 * 5405 Morehouse Dr.
 * San Diego, Ca. 92121
 *
 * information available at:  http://www.netburner.com
 * E-Mail info@netburner.com
 *
 * Support is available: E-Mail support@netburner.com
 *
 *******************************************************************************
 */

/* NB Library Definitions */
#include "predef.h"

/* C Standard Library */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Portability & uCos Definitions */
#include <basictypes.h>
#include <ucos.h>
#include <utils.h>

/* NB Runtime Libraries */
#include <constants.h>
#include <system.h>

/* NB I/O System */
#include <iosys.h>

/* NB Network Stack */
#include <nettypes.h>
#include <buffers.h>
#include <counters.h>

/* NB TCP/IP */
#include <arp.h>
#include <ip.h>
#include <tcp.h>

/* NB HTTP */
#include <http.h>
#include <htmlfiles.h>

/*
 ******************************************************************************
 *
 * Global Function Pointers
 *
 ******************************************************************************
 */
/* HTTP user replaceable handlers */
http_posthandler *ThePostHandler;
http_gethandler *TheGetHandler;
http_headhandler *TheHeadHandler;
http_multiparthandler *TheMultipartHandler;
http_multiextracthandler *TheMultipartExtractHandler;
http_errorhandler *TheErrorHandler;
extern http_wshandler *TheWSHandler;

/*
 ******************************************************************************
 *
 * Global Data
 *
 ******************************************************************************
 */
/* HTTP receiver buffer shared with http(s).cpp functions */
char http_rxBuffer[ ( HTTP_RX_BUFFERSIZE + 2 ) ];

/* Multipart handler currently in use */
BOOL b_InMultipart;

int httpstricmp( PCSTR s1, PCSTR sisupper2 );

void HTTP_Request::ProcessLine( PSTR startofline )
{
   PSTR cp = startofline;
   while ( isspace( *cp ) )
   {
      cp++;
   }

   switch ( toupper( *cp ) ) {
   case 'A':
      //Looking for AUTHORIZATION
      if ( httpstricmp( cp, "AUTHORIZATION" ) )
      {
         pAuthorization = cp + 13;
      }
      break;

   case 'C':
      //Looking for CONTENT-LENGTH or COOKIE
      if ( httpstricmp( cp, "CONTENT-LENGTH" ) )
      {
         cp += 15; // Point to next char after "CONTENT-LENGTH:"

         while ( isspace( *cp ) )
         {
            cp++; // Skip any whitespaces
         }
         content_length = atoi( cp );
      } else if ( httpstricmp( cp, "COOKIE" ) )
      {
         if ( pFirstCookie == NULL )
         {
            pFirstCookie = cp + 6;
         }
      } else if ( httpstricmp( cp, "CONTENT-TYPE" ) )
      {
         cp += 13;

         while ( (*cp) && ((*cp == ':') || isspace( *cp )) )
         {
            cp++;
         }

         if ( httpstricmp( cp, "MULTIPART/FORM-DATA" ) )
         {
            /* Adjust for string and find the next tag */
            cp += 19;
            while ( (*cp) && ((*cp == '=') || isspace( *cp ) || (*cp == ';') ))
            {
               cp++;
            }

            if ( httpstricmp( cp, "BOUNDARY" ) )
            {
               cp += 8;
               while ( (*cp) && ((*cp == '=') || isspace( *cp )) )
               {
                  cp++;
               }
               pSep = cp;
               sep_len = strlen( cp );
            }
         }
      }
      else if ( httpstricmp( cp, "CONNECTION" ) ) {
         cp += 11;

         while ( (*cp) && ((*cp == ':') || isspace( *cp )) ) {
            cp++;
         }

         while ( (*cp) ) {
            if ( httpstricmp( cp, "UPGRADE" ) ) {
                websocketFlags |= WS_CONNECT;
                break;
            }
            else {
               bool foundSpace = false;
               while ( *cp ) {
                  if (isspace( *cp )) {
                     foundSpace = true;
                  }
                  else if (foundSpace) {
                     break;
                  }
                  cp++;
              }
            }
         }

      }
      break;
   case 'G':
      //Looking for GET
      if ( httpstricmp( cp, "GET" ) )
      {
         pURL = cp + 4;
         while ( isspace( *pURL ) )
         {
            pURL++;
         }
         req = tGet;
      }
      break;
   case 'H':
      //Looking for Head
      if ( httpstricmp( cp, "HEAD" ) )
      {
         pURL = cp + 5;
         while ( isspace( *pURL ) )
         {
            pURL++;
         }
         req = tHead;
      }
      break;
   case 'P':
      //Looking for Post
      if ( httpstricmp( cp, "POST" ) )
      {
         pURL = cp + 5;
         while ( isspace( *pURL ) )
         {
            pURL++;
         }
         req = tPost;
      }
      break;
   case 'S':
      if ( httpstricmp( cp, "SEC-WEBSOCKET-" ) ) {
         cp += 14;
         if ( httpstricmp( cp, "KEY" ) ) {
            cp += 4;
            while ( (*cp) && ((*cp == ':') || isspace( *cp )) ) {
               cp++;
            }
            wsKey = cp;
         }
         else if ( httpstricmp( cp, "PROTOCOL" ) ) {
            cp += 9;
            while ( (*cp) && ((*cp == ':') || isspace( *cp )) ) {
               cp++;
            }
            wsProtocol = cp;
         }
         else if ( httpstricmp( cp, "VERSION" ) ) {
            cp += 8;
            while ( (*cp) && ((*cp == ':') || isspace( *cp )) ) {
               cp++;
            }
            if ( httpstricmp( cp, "13" ) ) {
               websocketFlags |= WS_VER_13;
            }
         }
      }

   case 'U':
      // Looking for Upgrade
      if ( httpstricmp( cp, "UPGRADE" ) ) {
          cp += 8;

         while ( (*cp) && ((*cp == ':') || isspace( *cp )) ) {
            cp++;
         }
         if ( httpstricmp( cp, "WEBSOCKET" ) ) {
            websocketFlags |= WS_UPGRADE;
         }
      }
      break;
   default:
      return;
   }//switch
}

void SendHTMLHeader( int sock )
{
   writestring( sock,
         "HTTP/1.0 200 OK\r\nPragma: no-cache\r\nContent-Type: text/html\r\n\r\n" );
}

void SendHTMLHeaderWCookie( int sock, char *cookie )
{
   writestring( sock, "HTTP/1.0 200 OK\r\nPragma: no-cache\r\nSet-Cookie: " );
   writesafestring( sock, cookie );
   writestring( sock, "\r\nContent-Type: text/html\r\n\r\n" );
}

void SendTextHeader( int sock )
{
   writestring( sock,
         "HTTP/1.0 200 OK\r\nPragma: no-cache\r\nContent-Type: text/plain\r\n\r\n" );
}

void SendGifHeader( int sock )
{
   writestring( sock,
         "HTTP/1.0 200 OK\r\nPragma: no-cache\r\nContent-Type: image/gif\r\n\r\n" );
}

int HTTP_Request::Respond( int sock )
{
   if ( pURL == NULL )
   {
      return 0;
   }
   if ( req == tUnknown )
   {
      return 0;
   }

   PSTR cp = pURL;
   while ( (*cp) && (!isspace( *cp )) )
   {
      cp++;
   }
   *cp = 0;

   switch ( req ) {
   case tGet:
      pURL++;
#ifdef SHOW_HTTP_TRAFIC

      iprintf( "Get %s\r\n", pURL );
#endif
      if ((websocketFlags == (WS_UPGRADE | WS_CONNECT | WS_VER_13))
            && TheWSHandler) {
         return TheWSHandler( this, sock, pURL, http_rxBuffer );
      }
      else if ( TheGetHandler )
      {
         return TheGetHandler( sock, pURL, http_rxBuffer );
      }
#ifdef SHOW_HTTP_TRAFIC
      iprintf( "Done\r\n" );
#endif
      break;
   case tPost:
      if ( (pSep) && (TheMultipartHandler) )
      {
         b_InMultipart = TRUE;
         TheMultipartHandler( sock, pURL, pSep, pData, (last_datarx - pData) );
         b_InMultipart = FALSE;
      } else if ( ThePostHandler )
      {
         ThePostHandler( sock, pURL, pData, http_rxBuffer );
      }
      break;
   case tHead:
      if ( TheHeadHandler )
      {
         TheHeadHandler( sock, pURL, http_rxBuffer );
      }
      break;
   default:
      break;
   }
   return 0;
}

void StopHTTP()
{
}

void append( char *&cpto, char *cpfrm )
{
   while ( *cpfrm )
   {
      *(cpto++) = (*(cpfrm++));
   }
}

void writesafestring( int fd, PCSTR str )
{
   PCSTR cp = str;
   PCSTR begin = str;
   int n = 0;

   while ( *cp )
   {
      if ( (*cp == '"') || (*cp == '&') || (*cp == '>') || (*cp == '<') )
      {

         if ( cp != begin )
         {
            write( fd, (char *) begin, n );
         }
         switch ( *cp ) {
         case '"':
            write( fd, "&quot;", 6 );
            break;
         case '&':
            write( fd, "&amp;", 5 );
            break;
         case '<':
            write( fd, "&lt;", 4 );
            break;
         case '>':
            write( fd, "&gt;", 4 );
            break;
         }
         begin = cp + 1;
         n = -1;
      }
      cp++;
      n++;
   }

   if ( n > 0 )
   {
      write( fd, (char *) begin, n );
   }
}

int writeallsafestring( int fd, PCSTR str )
{
   PCSTR cp = str;
   PCSTR begin = str;
   int n = 0;

   while ( *cp )
   {
      if ( (*cp == '"') || (*cp == '&') || (*cp == '>') || (*cp == '<') )
      {

         if ( cp != begin )
         {
            writeall( fd, (char *) begin, n );
         }
         switch ( *cp ) {
         case '"':
            writeall( fd, "&quot;", 6 );
            break;
         case '&':
            writeall( fd, "&amp;", 5 );
            break;
         case '<':
            writeall( fd, "&lt;", 4 );
            break;
         case '>':
            writeall( fd, "&gt;", 4 );
            break;
         }
         begin = cp + 1;
         n = -1;
      }
      cp++;
      n++;
   }

   if ( n > 0 )
   {
      writeall( fd, (char *) begin, n );
   }
   return strlen( str );

}

void RedirectResponse( int sock, PCSTR new_page )
{
   writestring( sock, "HTTP/1.0 302 Redirection\r\nLocation: " );
   writestring( sock, new_page );
   writestring( sock, "\r\n\r\n" );
}

void NotFoundResponse( int sock, PCSTR url )
{
   writestring( sock,
         "HTTP/1.0 404\r\nPragma: no-cache\r\nContent-Type: text/html\r\n\r\n" );
   writestring(
         sock,
         "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n<BODY><H1>404 Not Found</H1>\nThe requested URL " );
   writestring( sock, url );
   writestring( sock, " was not found on this server.<BR>\n</BODY>\r\n" );
}

void ForbiddenResponse( int sock, PCSTR url )
{
   writestring( sock,
         "HTTP/1.0 403\r\nPragma: no-cache\r\nContent-Type: text/html\r\n\r\n" );
   writestring(
         sock,
         "<HEAD><TITLE>403 Forbidden</TITLE></HEAD>\n<BODY><H1>403 Forbidden</H1>\nYou do not have permission to access the requested URL: " );
   writestring( sock, url );
   writestring( sock, "<BR>\n</BODY>\r\n" );
}

//This is a place holder for user a provided function.
int BaseDoPost( int sock, char *url, char *pData, char *http_rxBuffer )
{
   return 0;
}

void ShowIPOnFd( int fd, IPADDR ia )
{
   char tbuf[20];
   PBYTE ipb = (PBYTE) &ia;
   siprintf( tbuf, "%d.%d.%d.%d", (int) ipb[0], (int) ipb[1], (int) ipb[2],
         (int) ipb[3] );
   writesafestring( fd, tbuf );
}

extern const char *default_page;

int GetInterface( IPADDR ip );

int BaseInterfaceOK( int sock, HTML_FILE_RECORD * rec )
{
    int intf_num = GetInterface(GetSocketLocalAddr( sock ));
    // If somehow the local addr isn't associated with an interface
    // give up and fail.
    if (!intf_num) { return 0; }

    if (!rec) {
        return 0;
    }

    // If the file is declared that no direct requests can be made of it
    // we must deny the request
    if (rec->no_direct) {
        return 0;
    }
    // If either, there is no restriction on the interface
    // or the interface bit is set for the current interface
    // the interface is ok to use
    if ((!rec->intf_ok) || (rec->intf_ok & (1<<(intf_num-1)))) {
        return 1;
    }

    return 0;

}

char *tip_buffer[20];
int BaseDoGet( int sock, PSTR url, PSTR http_rxBuffer )
{
   if ( *url == 0 ) //The Default value
   {
      RedirectResponse( sock, default_page );
      return 1;
   }

   if ( httpstricmp( url, "ECHO" ) )
   {
      while ( *url )
      {
         url++;
      }
      *url = ' ';
      SendTextHeader( sock );
      writesafestring( sock, "Received request from " );
      ShowIPOnFd( sock, GetSocketRemoteAddr( sock ) );
      writesafestring( sock, " on IP address: " );
      ShowIPOnFd( sock, GetSocketLocalAddr( sock ) );
      writestring( sock, "\r\n" );
      writestring( sock, http_rxBuffer );
      return 1;
   }

   if ( !SendFullResponse( url, sock ) )
   {
      //We Failed
      NotFoundResponse( sock, url );
      DBPRINT( DB_HTTP, "Did not find:" );
      DBPRINT( DB_HTTP, url );
      DBPRINT( DB_HTTP, "\r\n" );
   }
   return 0;
}

int BaseDoHead( int sock, PSTR url, PSTR rxb )
{
   if ( *url == 0 ) //The Default value
   {
      RedirectResponse( sock, default_page );
      return 1;
   }

   if ( httpstricmp( url, "ECHO" ) )
   {
      while ( *url )
      {
         url++;
      }
      *url = ' ';
      SendTextHeader( sock );
      return 1;
   }

   if ( !SendHeaderResponse( url, sock ) )
   {
      //We Failed
      NotFoundResponse( sock, url );
   }
   return 0;
}

/*
 *
 * This function takes the HTML post data sent to the DoPost function and
 * extracts the data asociated with a specific name and returns it in dest_buffer
 *  It returns -1 if no data of that name was found.
 *  It returns  0 If the data field was presented, but Empty
 *  It returns  the number of chars copied into dest_buffer otherwise.
 */
int ExtractPostData( PCSTR name, PCSTR data, PSTR dest_buffer, int maxlen )
{
   int len;
   if ( (b_InMultipart) && (TheMultipartExtractHandler) )
   {
      len = TheMultipartExtractHandler( name, data, dest_buffer, maxlen );
      if ( len > 0 )
         dest_buffer[len] = 0;
      return len;
   }

   const char *cpStart;
   len = strlen( name );

   cpStart = data;

   do
   {
      cpStart = strstr( cpStart, name );
      if ( cpStart == NULL )
         break;

      if ( cpStart[len] == '=' ) //We found an = at the end
      {
         //If we are the begining or we have a & at our begining then we match!
         if ( (cpStart == data) || (cpStart[-1] == '&') )
            break;
      }
      cpStart++;
   } while ( (cpStart) && (*cpStart) );

   if ( !cpStart )
   {
      return -1;
   } //We did not find the data
   cpStart += len;
   cpStart++;

   len = 0;
   while ( (*cpStart) && (*cpStart != '&') )
   {
      switch ( *cpStart ) {
      case '+':
         dest_buffer[len++] = ' ';
         cpStart++;
         break;
      case '%': {
         //A two digit hex code of the results...
         cpStart++;
         char c1, c2;
         c1 = toupper( *cpStart++ );
         c2 = toupper( *cpStart++ );
         if ( c1 <= '9' )
         {
            c1 = c1 - '0';
         } else
         {
            c1 = c1 + 10 - 'A';
         }
         c1 = c1 << 4;
         if ( c2 <= '9' )
         {
            c1 += c2 - '0';
         } else
         {
            c1 += c2 + 10 - 'A';
         }
         dest_buffer[len++] = c1;
      }
         break;
      default:
         dest_buffer[len++] = *cpStart++;
         break;
      }
      if ( len == maxlen )
      {
         dest_buffer[maxlen - 1] = 0;
         return maxlen;
      }
   }

   dest_buffer[len] = 0;
   return len;
}

//Setup a custom Post Handler
http_posthandler * SetNewPostHandler( http_posthandler *newhandler )
{
   http_posthandler *pf = ThePostHandler;
   ThePostHandler = newhandler;
   return pf;
}

http_wshandler * SetNewWSHandler( http_wshandler *newhandler )
{
   http_wshandler *pf = TheWSHandler;
   TheWSHandler = newhandler;
   return pf;
}

//Setup a custom get Handler
http_gethandler * SetNewGetHandler( http_gethandler *newhandler )
{
   http_gethandler *pf = TheGetHandler;
   TheGetHandler = newhandler;
   return pf;
}

//Setup a custom Post Handler
http_headhandler * SetNewHeadHandler( http_headhandler *newhandler )
{
   http_headhandler *pf = TheHeadHandler;
   TheHeadHandler = newhandler;
   return pf;
}

http_multiparthandler * SetNewMultiPartHandler(
      http_multiparthandler *pNewHandler )
{
   http_multiparthandler *pf = TheMultipartHandler;
   TheMultipartHandler = pNewHandler;
   return pf;
}

http_multiextracthandler * SetNewMultiPartExtractHandler(
      http_multiextracthandler *pNewHandler )
{
   http_multiextracthandler *pf = TheMultipartExtractHandler;
   TheMultipartExtractHandler = pNewHandler;
   return pf;
}

http_errorhandler * SetNewErrorHandler( http_errorhandler *newhandler )
{
   http_errorhandler *pf = TheErrorHandler;
   TheErrorHandler = newhandler;
   return pf;
}

void InitHttp()
{
   ThePostHandler = BaseDoPost;
   TheGetHandler = BaseDoGet;
   TheHeadHandler = BaseDoHead;
   TheMultipartHandler = NULL;
   TheWSHandler = NULL;
}
