/*
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
#include "HTTPRequest.h"
#include "HTTPProtocol.h"
#include "OSMemory.h"
#include "StringParser.h"
#include "StringTranslator.h"
#include "ResizeableStringFormatter.h"
#include "DateTranslator.h"


StrPtrLen HTTPRequest::sColonSpace(": ", 2);
static Bool16 sFalse = false;
static Bool16 sTrue = true;
static StrPtrLen sCloseString("close", 5);
static StrPtrLen sKeepAliveString("keep-alive", 10);
static StrPtrLen sDefaultRealm("Streaming Server", 19);
UInt8 HTTPRequest::sURLStopConditions[] =
{
  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9      //'\t' is a stop condition
  1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19    //'\r' & '\n' are stop conditions
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29
  0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39    //' '
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249
  0, 0, 0, 0, 0, 0                         //250-255
};


// Constructor
HTTPRequest::HTTPRequest(StrPtrLen* serverHeader, StrPtrLen* requestPtr)
{
    // Store the pointer to the server header field
    fSvrHeader = *serverHeader;
   
    // Set initial state
    fRequestHeader = *requestPtr;
    fResponseHeader = NULL;
    fResponseFormatter = NULL;
    fMethod = httpIllegalMethod;
    fVersion = httpIllegalVersion;
    fAbsoluteURI = NULL;
    fRelativeURI = NULL;
    fAbsoluteURIScheme = NULL;
    fHostHeader = NULL;
    fRequestPath = NULL;    
    fStatusCode = httpOK;
    fRequestKeepAlive = false; // Default value when there is no version string
}

// Constructor for creating a response only
HTTPRequest::HTTPRequest(StrPtrLen* serverHeader)
{
    // Store the pointer to the server header field
    fSvrHeader = *serverHeader;
  
    // We do not require any of these:
    fRequestHeader = NULL;
 
    fMethod = httpIllegalMethod;
    fVersion = httpIllegalVersion;
    fRequestLine = NULL;
    fAbsoluteURI = NULL;
    fRelativeURI = NULL;
    fAbsoluteURIScheme = NULL;
    fHostHeader = NULL;
    fRequestPath = NULL;    
    fStatusCode = 0;
    fRequestKeepAlive = false; 
    
    // We require the response  but we allocate memory only when we call
    // CreateResponseHeader
    fResponseHeader = NULL;
    fResponseFormatter = NULL;
}

// Destructor
 HTTPRequest::~HTTPRequest()
 {
    if (fResponseHeader != NULL)
    {
        if (fResponseHeader->Ptr != NULL)
            delete fResponseHeader->Ptr;
        delete fResponseHeader;
     }
    if (fResponseFormatter != NULL) 
        delete fResponseFormatter;
    if (fRequestPath != NULL)
        delete [] fRequestPath;  
 }
//Parses the request
QTSS_Error HTTPRequest::Parse()
{
    Assert(fRequestHeader.Ptr != NULL);
    StringParser parser(&fRequestHeader);
    
    // Store the request line (used for logging) 
    // (ex: GET /index.html HTTP/1.0)
    StringParser requestLineParser(&fRequestHeader);
    requestLineParser.ConsumeUntil(&fRequestLine, StringParser::sEOLMask);
  
    // Parse request line returns an error if there is an error in the
    // request URI or the formatting of the request line. 
    // If the method or version are not found, they are set
    // to httpIllegalMethod or httpIllegalVersion respectively, 
    // and QTSS_NoErr is returned.
    QTSS_Error err = ParseRequestLine(&parser);
    if (err != QTSS_NoErr)
            return err;
  
    // Parse headers and set values of headers into fFieldValues array
    err = ParseHeaders(&parser);
    if (err != QTSS_NoErr)
            return err;
  
    return QTSS_NoErr;
}

QTSS_Error HTTPRequest::ParseRequestLine(StringParser* parser)
{
    // Get the method - If the method is not one of the defined methods
    // then it doesn't return an error but sets fMethod to httpIllegalMethod
    StrPtrLen theParsedData;
    parser->ConsumeWord(&theParsedData);
    fMethod = HTTPProtocol::GetMethod(&theParsedData);
    
    // Consume whitespace
    parser->ConsumeWhitespace();
  
    // Parse the URI - If it fails returns an error after setting 
    // the fStatusCode to the appropriate error code
    QTSS_Error err = ParseURI(parser);
    if (err != QTSS_NoErr)
            return err;
  
    // Consume whitespace
    parser->ConsumeWhitespace();
  
    // If there is a version, consume the version string
    StrPtrLen versionStr;
    parser->ConsumeUntil(&versionStr, StringParser::sEOLMask);
    // Check the version
    if (versionStr.Len > 0)
            fVersion = HTTPProtocol::GetVersion(&versionStr);
  
    // Go past the end of line
    if (!parser->ExpectEOL())
        {
            fStatusCode = httpBadRequest;
            return QTSS_BadArgument;     // Request line is not properly formatted!
        }

    return QTSS_NoErr;
}

QTSS_Error HTTPRequest::ParseURI(StringParser* parser)
{

    // read in the complete URL into fRequestAbsURI
    parser->ConsumeUntil(&fAbsoluteURI, sURLStopConditions);
  
    StringParser urlParser(&fAbsoluteURI);
  
    // we always should have a slash before the URI
    // If not, that indicates this is a full URI
    if (fAbsoluteURI.Ptr[0] != '/')
    {
            //if it is a full URL, store the scheme and host name
            urlParser.ConsumeLength(&fAbsoluteURIScheme, 7); //consume "http://"
            urlParser.ConsumeUntil(&fHostHeader, '/');
    }
  
    // whatever is in this position is the relative URI
    StrPtrLen relativeURI(urlParser.GetCurrentPosition(), urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen());
    // read this URI into fRequestRelURI
    fRelativeURI = relativeURI;
    
    // Allocate memory for fRequestPath
    UInt32 len = fRelativeURI.Len;
    len++;
    char* relativeURIDecoded = NEW char[len];
    
    SInt32 theBytesWritten = StringTranslator::DecodeURL(fRelativeURI.Ptr, fRelativeURI.Len,
                                                       relativeURIDecoded, len);
     
    //if negative, an error occurred, reported as an QTSS_Error
    //we also need to leave room for a terminator.
    if ((theBytesWritten < 0) || ((UInt32)theBytesWritten == len))
        {
            fStatusCode = httpBadRequest;
            return QTSS_BadArgument;
        }
    fRequestPath = NEW char[theBytesWritten + 1];
    ::memcpy(fRequestPath, relativeURIDecoded + 1, theBytesWritten); 
    delete relativeURIDecoded;
    fRequestPath[theBytesWritten] = '\0';
    return QTSS_NoErr;
}

// Parses the Connection header and makes sure that request is properly terminated
QTSS_Error HTTPRequest::ParseHeaders(StringParser* parser)
{
    StrPtrLen theKeyWord;
    Bool16 isStreamOK;
  
    //Repeat until we get a \r\n\r\n, which signals the end of the headers
    while ((parser->PeekFast() != '\r') && (parser->PeekFast() != '\n'))
    {
        //First get the header identifier
    
        isStreamOK = parser->GetThru(&theKeyWord, ':');
        if (!isStreamOK)
        {       // No colon after header!
            fStatusCode = httpBadRequest;
            return QTSS_BadArgument;
        }
    
        if (parser->PeekFast() == ' ') 
        {        // handle space, if any
            isStreamOK = parser->Expect(' ');
            Assert(isStreamOK);
        }
     
        //Look up the proper header enumeration based on the header string.
        HTTPHeader theHeader = HTTPProtocol::GetHeader(&theKeyWord);
      
        StrPtrLen theHeaderVal;
        isStreamOK = parser->GetThruEOL(&theHeaderVal);
      
        if (!isStreamOK)
        {       // No EOL after header!
            fStatusCode = httpBadRequest;
            return QTSS_BadArgument;
        }
      
        // If this is the connection header
        if ( theHeader == httpConnectionHeader )
        { // Set the keep alive boolean based on the connection header value
            SetKeepAlive(&theHeaderVal);
        }
      
        // Have the header field and the value; Add value to the array
        // If the field is invalid (or unrecognized) just skip over gracefully
        if ( theHeader != httpIllegalHeader )
            fFieldValues[theHeader] = theHeaderVal;
            
    }
  
    isStreamOK = parser->ExpectEOL();
    Assert(isStreamOK);
  
    return QTSS_NoErr;
}

void HTTPRequest::SetKeepAlive(StrPtrLen *keepAliveValue)
{
    if ( sCloseString.EqualIgnoreCase(keepAliveValue->Ptr, keepAliveValue->Len) )
            fRequestKeepAlive = sFalse;
    else
        {
            Assert( sKeepAliveString.EqualIgnoreCase(keepAliveValue->Ptr, keepAliveValue->Len) );
            fRequestKeepAlive = sTrue;
        }
}

void HTTPRequest::PutStatusLine(StringFormatter* putStream, HTTPStatusCode status,
                                HTTPVersion version)
{
    putStream->Put(*(HTTPProtocol::GetVersionString(version)));
    putStream->PutSpace();
    putStream->Put(*(HTTPProtocol::GetStatusCodeAsString(status)));
    putStream->PutSpace();
    putStream->Put(*(HTTPProtocol::GetStatusCodeString(status)));
    putStream->PutEOL();
}

StrPtrLen* HTTPRequest::GetHeaderValue(HTTPHeader inHeader)
{
    if ( inHeader !=  httpIllegalHeader )
        return &fFieldValues[inHeader];
    return NULL;
}

void HTTPRequest:: CreateResponseHeader(HTTPVersion version, HTTPStatusCode statusCode)
{
    // If we are creating a second response for the same request, make sure and
    // deallocate memory for old response and allocate fresh memory
    if (fResponseFormatter != NULL) 
    {
        if(fResponseHeader->Ptr != NULL)
            delete fResponseHeader->Ptr;
        delete fResponseHeader;
        delete fResponseFormatter;
    }
    
    // Allocate memory for the response when you first create it
    char* responseString = NEW char[kMinHeaderSizeInBytes];
    fResponseHeader = NEW StrPtrLen(responseString, kMinHeaderSizeInBytes);
    fResponseFormatter = NEW ResizeableStringFormatter(fResponseHeader->Ptr, fResponseHeader->Len);
    
    //make a partial header for the given version and status code
    PutStatusLine(fResponseFormatter, statusCode, version);
    Assert(fSvrHeader.Ptr != NULL);
    fResponseFormatter->Put(fSvrHeader);
    fResponseFormatter->PutEOL();
    fResponseHeader->Len = fResponseFormatter->GetCurrentOffset();
}

StrPtrLen* HTTPRequest::GetCompleteResponseHeader()
{
    fResponseFormatter->PutEOL();
    fResponseHeader->Len = fResponseFormatter->GetCurrentOffset();
    return fResponseHeader;
}

void HTTPRequest::AppendResponseHeader(HTTPHeader inHeader, StrPtrLen* inValue)
{
    fResponseFormatter->Put(*(HTTPProtocol::GetHeaderString(inHeader)));
    fResponseFormatter->Put(sColonSpace);
    fResponseFormatter->Put(*inValue);
    fResponseFormatter->PutEOL();
    fResponseHeader->Len = fResponseFormatter->GetCurrentOffset();
}

void HTTPRequest::AppendContentLengthHeader(UInt64 length_64bit)
{
    char* contentLength = NEW char[256];
    qtss_sprintf(contentLength, "%"_64BITARG_"d", length_64bit);
    StrPtrLen contentLengthPtr(contentLength);
    AppendResponseHeader(httpContentLengthHeader, &contentLengthPtr);
}

void HTTPRequest::AppendContentLengthHeader(UInt32 length_32bit)
{
    char* contentLength = NEW char[256];
    qtss_sprintf(contentLength, "%lu", length_32bit);
    StrPtrLen contentLengthPtr(contentLength);
    AppendResponseHeader(httpContentLengthHeader, &contentLengthPtr);
}

void HTTPRequest::AppendConnectionCloseHeader()
{
    AppendResponseHeader(httpConnectionHeader, &sCloseString);
}

void HTTPRequest::AppendConnectionKeepAliveHeader()
{
    AppendResponseHeader(httpConnectionHeader, &sKeepAliveString);
}

void HTTPRequest::AppendDateAndExpiresFields()
{
    Assert(OSThread::GetCurrent() != NULL);
    DateBuffer* theDateBuffer = OSThread::GetCurrent()->GetDateBuffer();
    theDateBuffer->InexactUpdate(); // Update the date buffer to the current date & time
    StrPtrLen theDate(theDateBuffer->GetDateBuffer(), DateBuffer::kDateBufferLen);
  
    // Append dates, and have this response expire immediately
    this->AppendResponseHeader(httpDateHeader, &theDate);
    this->AppendResponseHeader(httpExpiresHeader, &theDate);
}

void HTTPRequest::AppendDateField()
{
    Assert(OSThread::GetCurrent() != NULL);
    DateBuffer* theDateBuffer = OSThread::GetCurrent()->GetDateBuffer();
    theDateBuffer->InexactUpdate(); // Update the date buffer to the current date & time
    StrPtrLen theDate(theDateBuffer->GetDateBuffer(), DateBuffer::kDateBufferLen);
  
    // Append date
    this->AppendResponseHeader(httpDateHeader, &theDate);
}

time_t HTTPRequest::ParseIfModSinceHeader()
{
    time_t theIfModSinceDate = (time_t) DateTranslator::ParseDate(&fFieldValues[httpIfModifiedSinceHeader]);
    return theIfModSinceDate;
}
