/*
 *
 * @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@
 *
 */
/*
    File:       RTSPRequest.cpp

    Contains:   Implementation of RTSPRequest class.

    
    
*/


#include "RTSPRequest.h"
#include "RTSPProtocol.h"
#include "QTSServerInterface.h"

#include "RTSPSession.h"
#include "RTSPSessionInterface.h"
#include "StringParser.h"
#include "StringTranslator.h"
#include "OS.h"
#include "OSMemory.h"
#include "QTSS.h"
#include "QTSSModuleUtils.h"
#include "base64.h"
#include "OSArrayObjectDeleter.h"
#include "DateTranslator.h"
#include "SocketUtils.h"

UInt8
RTSPRequest::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, 1, 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
};

static StrPtrLen    sDefaultRealm("Streaming Server", 16);
static StrPtrLen    sAuthBasicStr("Basic", 5);
static StrPtrLen    sAuthDigestStr("Digest", 6);
static StrPtrLen    sUsernameStr("username", 8);
static StrPtrLen    sRealmStr("realm", 5);
static StrPtrLen    sNonceStr("nonce", 5);
static StrPtrLen    sUriStr("uri", 3);
static StrPtrLen    sQopStr("qop", 3);
static StrPtrLen    sQopAuthStr("auth", 3);
static StrPtrLen    sQopAuthIntStr("auth-int", 8);
static StrPtrLen    sNonceCountStr("nc", 2);
static StrPtrLen    sResponseStr("response", 8);
static StrPtrLen    sOpaqueStr("opaque", 6);
static StrPtrLen    sEqualQuote("=\"", 2);
static StrPtrLen    sQuoteCommaSpace("\", ", 3);
static StrPtrLen    sStaleTrue("stale=\"true\", ", 14); 

//Parses the request
QTSS_Error RTSPRequest::Parse()
{
    StringParser parser(this->GetValue(qtssRTSPReqFullRequest));
    Assert(this->GetValue(qtssRTSPReqFullRequest)->Ptr != NULL);

    //parse status line.
    QTSS_Error error = ParseFirstLine(parser);

    //handle any errors that come up    
    if (error != QTSS_NoErr)
        return error;
        
    error = this->ParseHeaders(parser);
    if (error != QTSS_NoErr)
        return error;
    
    //Response headers should set themselves up to reflect what's in the request headers
    fResponseKeepAlive = fRequestKeepAlive;
    
    //Make sure that there was some path that was extracted from this request. If not, there is no way
    //we can process the request, so generate an error
    if (this->GetValue(qtssRTSPReqFilePath)->Len == 0)
        return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoURLInRequest,this->GetValue(qtssRTSPReqFullRequest));
    
    return QTSS_NoErr;
}

//returns: StatusLineTooLong, SyntaxError, BadMethod
QTSS_Error RTSPRequest::ParseFirstLine(StringParser &parser)
{   
    //first get the method
    StrPtrLen theParsedData;
    parser.ConsumeWord(&theParsedData);
    this->SetVal(qtssRTSPReqMethodStr, theParsedData.Ptr, theParsedData.Len);
    
    
    //THIS WORKS UNDER THE ASSUMPTION THAT:
    //valid HTTP/1.1 headers are: GET, HEAD, POST, PUT, OPTIONS, DELETE, TRACE
    fMethod = RTSPProtocol::GetMethod(theParsedData);
    if (fMethod == qtssIllegalMethod)
        return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgBadRTSPMethod, &theParsedData);
    
    //no longer assume this is a space... instead, just consume whitespace
    parser.ConsumeWhitespace();

    //now parse the uri
    QTSS_Error err = ParseURI(parser);
    if (err != QTSS_NoErr)
        return err;

    //no longer assume this is a space... instead, just 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 = RTSPProtocol::GetVersion(versionStr);

    //go past the end of line
    if (!parser.ExpectEOL())
        return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoRTSPVersion,&theParsedData);
    return QTSS_NoErr;
}

//returns: SyntaxError if there was an error in the uri. Or InternalServerError
QTSS_Error RTSPRequest::ParseURI(StringParser &parser)
{
    //read in the complete URL, set it to be the qtssAbsoluteURLParam
    StrPtrLen theAbsURL;

    //  RTSPRequestInterface::sPathURLStopConditions stop on ? as well as sURLStopConditions
    parser.ConsumeUntil(&theAbsURL, sURLStopConditions );

    // set qtssRTSPReqAbsoluteURL to the URL throught the path component; will be : <protocol>://<host-addr>/<path>
    this->SetVal(qtssRTSPReqAbsoluteURL, &theAbsURL);
    
    StringParser urlParser(&theAbsURL);
    
    //we always should have a slash before the uri.
    //If not, that indicates this is a full URI. Also, this could be a '*' OPTIONS request
    if ((*theAbsURL.Ptr != '/') && (*theAbsURL.Ptr != '*'))
    {
        //if it is a full URL, store the host name off in a separate parameter
        StrPtrLen theRTSPString;
        urlParser.ConsumeLength(&theRTSPString, 7); //consume "rtsp://"
        //assign the host field here to the proper QTSS param
        StrPtrLen theHost;
        urlParser.ConsumeUntil(&theHost, '/');
        fHeaderDictionary.SetVal(qtssHostHeader, &theHost);
    }
    
    // don't allow non-aggregate operations indicated by a url/media track=id
    if (qtssSetupMethod != fMethod) // any method not a setup is not allowed to have a "/trackID=" in the url.
    {
        StrPtrLenDel tempCStr(theAbsURL.GetAsCString()); 
        StrPtrLen nonaggregate(tempCStr.FindString("/trackID="));
        if (nonaggregate.Len > 0) // check for non-aggregate method and return error
            return QTSSModuleUtils::SendErrorResponse(this, qtssClientAggregateOptionAllowed, qtssMsgBadRTSPMethod, &theAbsURL);
    }

    // don't allow non-aggregate operations like a setup on a playing session
    if (qtssSetupMethod == fMethod) // if it is a setup but we are playing don't allow it
    {
        RTSPSession*  theSession =  (RTSPSession*)this->GetSession();
        if (theSession != NULL && theSession->IsPlaying())
            return QTSSModuleUtils::SendErrorResponse(this, qtssClientAggregateOptionAllowed, qtssMsgBadRTSPMethod, &theAbsURL);
    }

    //
    // In case there is no URI at all... we have to fake it.
    static char* sSlashURI = "/";
        
    //whatever is in this position in the URL must be the URI. Store that
    //in the qtssURLParam. Confused?
    UInt32 uriLen = urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen();
    if (uriLen > 0)
        this->SetVal(qtssRTSPReqURI, urlParser.GetCurrentPosition(), urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen());
    else
        //
        // This might happen if there is nothing after the host at all, not even
        // a '/'. This is legal (RFC 2326, Sec 3.2). If so, just pretend that there
        // is a '/'
        this->SetVal(qtssRTSPReqURI, sSlashURI, 1);

    // parse the query string from the url if present.
    // init qtssRTSPReqQueryString dictionary to an empty string
    StrPtrLen queryString;
    this->SetVal(qtssRTSPReqQueryString, queryString.Ptr, queryString.Len);
    
    if ( parser.GetDataRemaining() > 0 )
    {   
        if ( parser.PeekFast() == '?' )
        {       
            // we've got some CGI param
            parser.ConsumeLength(&queryString, 1); // toss '?'
            
            // consume the rest of the line..
            parser.ConsumeUntilWhitespace(&queryString);
            
            this->SetVal(qtssRTSPReqQueryString, queryString.Ptr, queryString.Len);
        }
    }
 
 
    //
    // If the is a '*', return right now because '*' is not a path
    // so the below functions don't make any sense.
    if ((*theAbsURL.Ptr == '*') && (theAbsURL.Len == 1))
    {
		this->SetValue(qtssRTSPReqFilePath, 0, theAbsURL.Ptr, theAbsURL.Len, QTSSDictionary::kDontObeyReadOnly);
		
        return QTSS_NoErr;
    }
    
    //path strings are statically allocated. Therefore, if they are longer than
    //this length we won't be able to handle the request.
    StrPtrLen* theURLParam = this->GetValue(qtssRTSPReqURI);
    if (theURLParam->Len > RTSPRequestInterface::kMaxFilePathSizeInBytes)
        return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgURLTooLong, theURLParam);

    //decode the URL, put the result in the separate buffer for the file path,
    //set the file path StrPtrLen to the proper value
    SInt32 theBytesWritten = StringTranslator::DecodeURL(theURLParam->Ptr, theURLParam->Len,
                                                fFilePath, RTSPRequestInterface::kMaxFilePathSizeInBytes);
    //if negative, an error occurred, reported as an QTSS_Error
    //we also need to leave room for a terminator.
    if ((theBytesWritten < 0) || (theBytesWritten == RTSPRequestInterface::kMaxFilePathSizeInBytes))
    {
        return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgURLInBadFormat, theURLParam);
    }

    // Convert from a / delimited path to a local file system path
    StringTranslator::DecodePath(fFilePath, theBytesWritten);
    
    //setup the proper QTSS param
    fFilePath[theBytesWritten] = '\0';
    //this->SetVal(qtssRTSPReqFilePath, fFilePath, theBytesWritten);
	this->SetValue(qtssRTSPReqFilePath, 0, fFilePath, theBytesWritten, QTSSDictionary::kDontObeyReadOnly);



    return QTSS_NoErr;
}


//throws eHTTPNoMoreData and eHTTPOutOfBuffer
QTSS_Error RTSPRequest::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)
            return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoColonAfterHeader, this->GetValue(qtssRTSPReqFullRequest));
                            
         theKeyWord.TrimWhitespace();
        
        //Look up the proper header enumeration based on the header string.
        //Use the enumeration to look up the dictionary ID of this header,
        //and set that dictionary attribute to be whatever is in the body of the header
        
        UInt32 theHeader = RTSPProtocol::GetRequestHeader(theKeyWord);

        StrPtrLen theHeaderVal;
		parser.ConsumeUntil(&theHeaderVal, StringParser::sEOLMask);
	
		StrPtrLen theEOL;
		if ((parser.PeekFast() == '\r') || (parser.PeekFast() == '\n'))
		{
			isStreamOK = true;
			parser.ConsumeEOL(&theEOL);
		}
		else
			isStreamOK = false;
			
		while((parser.PeekFast() == ' ') || (parser.PeekFast() == '\t'))
		{
			theHeaderVal.Len += theEOL.Len;
			StrPtrLen temp;
			parser.ConsumeUntil(&temp, StringParser::sEOLMask);
			theHeaderVal.Len += temp.Len;
			
			if ((parser.PeekFast() == '\r') || (parser.PeekFast() == '\n'))
			{
				isStreamOK = true;
				parser.ConsumeEOL(&theEOL);
			}
			else
				isStreamOK = false;			
		}

        // If this is an unknown header, ignore it. Otherwise, set the proper
        // dictionary attribute
        if (theHeader != qtssIllegalHeader)
        {
            Assert(theHeader < qtssNumHeaders);
            theHeaderVal.TrimWhitespace();
            fHeaderDictionary.SetVal(theHeader, &theHeaderVal);
        }
        if (!isStreamOK)
            return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoEOLAfterHeader);
        
        //some headers require some special processing. If this code begins
        //to get out of control, we made need to come up with a function pointer table
        switch (theHeader)
        {
            case qtssSessionHeader:             ParseSessionHeader(); break;
            case qtssTransportHeader:           ParseTransportHeader(); break;
            case qtssRangeHeader:               ParseRangeHeader();     break;
            case qtssIfModifiedSinceHeader:     ParseIfModSinceHeader();break;
            case qtssXRetransmitHeader:         ParseRetransmitHeader();break;
            case qtssContentLengthHeader:       ParseContentLengthHeader();break;
            case qtssSpeedHeader:               ParseSpeedHeader();     break;
            case qtssXTransportOptionsHeader:   ParseTransportOptionsHeader();break;
            case qtssXPreBufferHeader:          ParsePrebufferHeader();break;
			case qtssXDynamicRateHeader:		ParseDynamicRateHeader(); break;
            default:    break;
        }
    }

    // Tell the session what the request body length is for this request
    // so that it can prevent people from reading past the end of the request.
    StrPtrLen* theContentLengthBody = fHeaderDictionary.GetValue(qtssContentLengthHeader);
    if (theContentLengthBody->Len > 0)
    {
        StringParser theHeaderParser(fHeaderDictionary.GetValue(qtssContentLengthHeader));
        theHeaderParser.ConsumeWhitespace();
        this->GetSession()->SetRequestBodyLength(theHeaderParser.ConsumeInteger(NULL));
    }
    
    isStreamOK = parser.ExpectEOL();
    Assert(isStreamOK);
    return QTSS_NoErr;
}

void RTSPRequest::ParseSessionHeader()
{
    StringParser theSessionParser(fHeaderDictionary.GetValue(qtssSessionHeader));
    StrPtrLen theSessionID;
    (void)theSessionParser.GetThru(&theSessionID, ';');
    fHeaderDictionary.SetVal(qtssSessionHeader, &theSessionID);
}

Bool16 RTSPRequest::ParseNetworkModeSubHeader(StrPtrLen* inSubHeader)
{
    static StrPtrLen sUnicast("unicast");
    static StrPtrLen sMulticast("multiicast");
    Bool16 result = false; // true means header was found
    
    StringParser theSubHeaderParser(inSubHeader);
        
    if (!result && inSubHeader->EqualIgnoreCase(sUnicast))
    {
        fNetworkMode = qtssRTPNetworkModeUnicast;
        result = true;
    }
        
    if (!result && inSubHeader->EqualIgnoreCase(sMulticast))
    {
        fNetworkMode = qtssRTPNetworkModeMulticast;
        result = true;
    }
        
    return result;
}

void RTSPRequest::ParseTransportHeader()
{
	static char* sRTPAVPTransportStr = "RTP/AVP";
	
    StringParser theTransParser(fHeaderDictionary.GetValue(qtssTransportHeader));
    
    //transport header from client: Transport: RTP/AVP;unicast;client_port=5000-5001\r\n
    //                              Transport: RTP/AVP;multicast;ttl=15;destination=229.41.244.93;client_port=5000-5002\r\n
    //                              Transport: RTP/AVP/TCP;unicast\r\n
    
    //
    // A client may send multiple transports to the server, comma separated.
	// In this case, the server should just pick one and use that. 
	
	while (theTransParser.GetDataRemaining() > 0)
	{	
	    (void)theTransParser.ConsumeWhitespace();
        (void)theTransParser.ConsumeUntil(&fFirstTransport, ',');
    
		if (fFirstTransport.NumEqualIgnoreCase(sRTPAVPTransportStr, ::strlen(sRTPAVPTransportStr)))
			break;

		if (theTransParser.PeekFast() == ',')
			theTransParser.Expect(',');
	}
	
    StringParser theFirstTransportParser(&fFirstTransport);
    
    StrPtrLen theTransportSubHeader;
    (void)theFirstTransportParser.GetThru(&theTransportSubHeader, ';');    

    while (theTransportSubHeader.Len > 0)
    {
       
        // Extract the relevent information from the relevent subheader.
        // So far we care about 3 sub-headers

        if (!this->ParseNetworkModeSubHeader(&theTransportSubHeader))
        {
            theTransportSubHeader.TrimWhitespace();

            switch (*theTransportSubHeader.Ptr)
            {
				case 'r':	// rtp/avp/??? Is this tcp or udp?
                case 'R':   // RTP/AVP/??? Is this TCP or UDP?
                {   
                    if ( theTransportSubHeader.EqualIgnoreCase("RTP/AVP/TCP") )
                        fTransportType = qtssRTPTransportTypeTCP;
                    break;
                }
                case 'c':   //client_port sub-header
                case 'C':   //client_port sub-header
                {
                    this->ParseClientPortSubHeader(&theTransportSubHeader);
                    break;
                }
                case 'd':   //destination sub-header
                case 'D':   //destination sub-header
                {
                    static StrPtrLen sDestinationSubHeader("destination");
                    
                    //Parse the header, extract the destination address
                    this->ParseAddrSubHeader(&theTransportSubHeader, &sDestinationSubHeader, &fDestinationAddr);
                    break;
                }
                case 's':   //source sub-header
                case 'S':   //source sub-header
                {
                    //Same as above code
                    static StrPtrLen sSourceSubHeader("source");
                    this->ParseAddrSubHeader(&theTransportSubHeader, &sSourceSubHeader, &fSourceAddr);
                    break;
                }
                case 't':   //time-to-live sub-header
                case 'T':   //time-to-live sub-header
                {
                    this->ParseTimeToLiveSubHeader(&theTransportSubHeader);
                    break;
                }
                case 'm':   //mode sub-header
                case 'M':   //mode sub-header
                {
                    this->ParseModeSubHeader(&theTransportSubHeader);
                    break;
                }
            }
        }
        
        // Move onto the next parameter
        (void)theFirstTransportParser.GetThru(&theTransportSubHeader, ';');
    }
}

void  RTSPRequest::ParseRangeHeader()
{
    StringParser theRangeParser(fHeaderDictionary.GetValue(qtssRangeHeader));

    // Setup the start and stop time dictionary attributes
    this->SetVal(qtssRTSPReqStartTime, &fStartTime, sizeof(fStartTime));
    this->SetVal(qtssRTSPReqStopTime, &fStopTime, sizeof(fStopTime));

    theRangeParser.GetThru(NULL, '=');//consume "npt="
    theRangeParser.ConsumeWhitespace();
    fStartTime = (Float64)theRangeParser.ConsumeFloat();
    //see if there is a stop time as well.
    if (theRangeParser.GetDataRemaining() > 1)
    {
        theRangeParser.GetThru(NULL, '-');
        theRangeParser.ConsumeWhitespace();
        fStopTime = (Float64)theRangeParser.ConsumeFloat();
    }
}

void  RTSPRequest::ParseRetransmitHeader()
{
    StringParser theRetransmitParser(fHeaderDictionary.GetValue(qtssXRetransmitHeader));
    StrPtrLen theProtName;
    Bool16 foundRetransmitProt = false;
            
    do
    {
        theRetransmitParser.ConsumeWhitespace();
        theRetransmitParser.ConsumeWord(&theProtName);
        theProtName.TrimTrailingWhitespace();
        foundRetransmitProt = theProtName.EqualIgnoreCase(RTSPProtocol::GetRetransmitProtocolName());
    }
    while ( (!foundRetransmitProt) &&
            (theRetransmitParser.GetThru(NULL, ',')) );

    if (!foundRetransmitProt)
        return;
    
    //
    // We are using Reliable RTP as the transport for this stream,
    // but if there was a previous transport header that indicated TCP,
    // do not set the transport to be reliable UDP
    if (fTransportType == qtssRTPTransportTypeUDP)
        fTransportType = qtssRTPTransportTypeReliableUDP;

    StrPtrLen theProtArg;
    while (theRetransmitParser.GetThru(&theProtArg, '='))   
    {
        //
        // Parse out params
        static const StrPtrLen kWindow("window");
         
        theProtArg.TrimWhitespace();
        if (theProtArg.EqualIgnoreCase(kWindow))
        {
            theRetransmitParser.ConsumeWhitespace();
            fWindowSize = theRetransmitParser.ConsumeInteger(NULL);
            
            // Save out the window size argument as a string so we
            // can easily put it into the response
            // (we never muck with this header)
            fWindowSizeStr.Ptr = theProtArg.Ptr;
            fWindowSizeStr.Len = theRetransmitParser.GetCurrentPosition() - theProtArg.Ptr;
        }
            
        theRetransmitParser.GetThru(NULL, ';'); //Skip past ';'
    }
}

void  RTSPRequest::ParseContentLengthHeader()
{
    StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssContentLengthHeader));
    theContentLenParser.ConsumeWhitespace();
    fContentLength = theContentLenParser.ConsumeInteger(NULL);
}

void  RTSPRequest::ParsePrebufferHeader()
{
    StringParser thePrebufferParser(fHeaderDictionary.GetValue(qtssXPreBufferHeader));

    StrPtrLen thePrebufferArg;
    while (thePrebufferParser.GetThru(&thePrebufferArg, '='))   
    {
        thePrebufferArg.TrimWhitespace();

        static const StrPtrLen kMaxTimeSubHeader("maxtime");
        if (thePrebufferArg.EqualIgnoreCase(kMaxTimeSubHeader))
        {        
            thePrebufferParser.ConsumeWhitespace();
            fPrebufferAmt = thePrebufferParser.ConsumeFloat();
        } 
            
        thePrebufferParser.GetThru(NULL, ';'); //Skip past ';'
    
    }
}

void  RTSPRequest::ParseDynamicRateHeader()
{
	StringParser theParser(fHeaderDictionary.GetValue(qtssXDynamicRateHeader));
    theParser.ConsumeWhitespace();
	SInt32 value = theParser.ConsumeInteger(NULL);

	// fEnableDynamicRate: < 0 undefined, 0 disable, > 0 enable
	if (value > 0)
	    fEnableDynamicRateState = 1;
	else
	    fEnableDynamicRateState = 0;
}

void  RTSPRequest::ParseIfModSinceHeader()
{
    fIfModSinceDate = DateTranslator::ParseDate(fHeaderDictionary.GetValue(qtssIfModifiedSinceHeader));

    // Only set the param if this is a legal date
    if (fIfModSinceDate != 0)
        this->SetVal(qtssRTSPReqIfModSinceDate, &fIfModSinceDate, sizeof(fIfModSinceDate));
}

void RTSPRequest::ParseSpeedHeader()
{
    StringParser theSpeedParser(fHeaderDictionary.GetValue(qtssSpeedHeader));
    theSpeedParser.ConsumeWhitespace();
    fSpeed = theSpeedParser.ConsumeFloat();
}

void RTSPRequest::ParseTransportOptionsHeader()
{
    StringParser theRTPOptionsParser(fHeaderDictionary.GetValue(qtssXTransportOptionsHeader));
    StrPtrLen theRTPOptionsSubHeader;

    do
    {
        static StrPtrLen sLateTolerance("late-tolerance");
        
        if (theRTPOptionsSubHeader.NumEqualIgnoreCase(sLateTolerance.Ptr, sLateTolerance.Len))
        {
            StringParser theLateTolParser(&theRTPOptionsSubHeader);
            theLateTolParser.GetThru(NULL,'=');
            theLateTolParser.ConsumeWhitespace();
            fLateTolerance = theLateTolParser.ConsumeFloat();
            fLateToleranceStr = theRTPOptionsSubHeader;
        }
        
        (void)theRTPOptionsParser.GetThru(&theRTPOptionsSubHeader, ';');
        
    } while(theRTPOptionsSubHeader.Len > 0);
}


void RTSPRequest::ParseAddrSubHeader(StrPtrLen* inSubHeader, StrPtrLen* inHeaderName, UInt32* outAddr)
{
    StringParser theSubHeaderParser(inSubHeader);

    // First make sure this is the proper subheader
    if (!inSubHeader->EqualIgnoreCase(*inHeaderName))
        return;
        
    //Find the IP address
    theSubHeaderParser.ConsumeUntilDigit();

    //Set the addr string param.
    StrPtrLen theAddr(theSubHeaderParser.GetCurrentPosition(), theSubHeaderParser.GetDataRemaining());
    
    //Convert the string to a UInt32 IP address
    char theTerminator = theAddr.Ptr[theAddr.Len];
    theAddr.Ptr[theAddr.Len] = '\0';
    
    *outAddr = SocketUtils::ConvertStringToAddr(theAddr.Ptr);
    
    theAddr.Ptr[theAddr.Len] = theTerminator;
}

void RTSPRequest::ParseModeSubHeader(StrPtrLen* inModeSubHeader)
{
    static StrPtrLen sModeSubHeader("mode");
    static StrPtrLen sReceiveMode("receive");
    static StrPtrLen sRecordMode("record");
    StringParser theSubHeaderParser(inModeSubHeader);

    // Skip over to the first port
    StrPtrLen theFirstBit;
    theSubHeaderParser.GetThru(&theFirstBit, '=');
    theFirstBit.TrimWhitespace();
    
    // Make sure this is the client port subheader
    if (theFirstBit.EqualIgnoreCase(sModeSubHeader)) do
    {
        theSubHeaderParser.ConsumeWhitespace();

        StrPtrLen theMode;
		theSubHeaderParser.ConsumeWord(&theMode);
		
		if ( theMode.EqualIgnoreCase(sReceiveMode) || theMode.EqualIgnoreCase(sRecordMode) )
		{	fTransportMode = qtssRTPTransportModeRecord;
			break;
		}
        
    } while (false);
    
}

void RTSPRequest::ParseClientPortSubHeader(StrPtrLen* inClientPortSubHeader)
{
    static StrPtrLen sClientPortSubHeader("client_port");
    static StrPtrLen sErrorMessage("Received invalid client_port field: ");
    StringParser theSubHeaderParser(inClientPortSubHeader);

    // Skip over to the first port
    StrPtrLen theFirstBit;
    theSubHeaderParser.GetThru(&theFirstBit, '=');
    theFirstBit.TrimWhitespace();
    
    // Make sure this is the client port subheader
    if (!theFirstBit.EqualIgnoreCase(sClientPortSubHeader))
        return;

    // Store the two client ports as integers
    theSubHeaderParser.ConsumeWhitespace();
    fClientPortA = (UInt16)theSubHeaderParser.ConsumeInteger(NULL);
    theSubHeaderParser.GetThru(NULL,'-');
    theSubHeaderParser.ConsumeWhitespace();
    fClientPortB = (UInt16)theSubHeaderParser.ConsumeInteger(NULL);
    if (fClientPortB != fClientPortA + 1) // an error in the port values
    {
        // The following to setup and log the error as a message level 2.
        StrPtrLen *userAgentPtr = fHeaderDictionary.GetValue(qtssUserAgentHeader);
        ResizeableStringFormatter errorPortMessage;
        errorPortMessage.Put(sErrorMessage);
        if (userAgentPtr != NULL)
            errorPortMessage.Put(*userAgentPtr);
        errorPortMessage.PutSpace();
        errorPortMessage.Put(*inClientPortSubHeader);
        errorPortMessage.PutTerminator(); 
        QTSSModuleUtils::LogError(qtssMessageVerbosity,qtssMsgNoMessage, 0, errorPortMessage.GetBufPtr(), NULL);
        
        
        //fix the rtcp port and hope it works.
        fClientPortB = fClientPortA + 1;
    }
}

void RTSPRequest::ParseTimeToLiveSubHeader(StrPtrLen* inTimeToLiveSubHeader)
{
    static StrPtrLen sTimeToLiveSubHeader("ttl");

    StringParser theSubHeaderParser(inTimeToLiveSubHeader);

    // Skip over to the first part
    StrPtrLen theFirstBit;
    theSubHeaderParser.GetThru(&theFirstBit, '=');
    theFirstBit.TrimWhitespace();
    // Make sure this is the ttl subheader
    if (!theFirstBit.EqualIgnoreCase(sTimeToLiveSubHeader))
        return;
    
    // Parse out the time to live...
    theSubHeaderParser.ConsumeWhitespace();
    fTtl = (UInt16)theSubHeaderParser.ConsumeInteger(NULL);
}

QTSS_Error RTSPRequest::ParseBasicHeader(StringParser *inParsedAuthLinePtr)
{
    QTSS_Error  theErr = QTSS_NoErr;
    fAuthScheme = qtssAuthBasic;

    StrPtrLen authWord;
    
    inParsedAuthLinePtr->ConsumeWhitespace();
    inParsedAuthLinePtr->ConsumeUntilWhitespace(&authWord);
    if (0 == authWord.Len ) 
        return theErr;
        
    char* encodedStr = authWord.GetAsCString();
    OSCharArrayDeleter encodedStrDeleter(encodedStr);   
    
    char *decodedAuthWord = NEW char[Base64decode_len(encodedStr) + 1];
    OSCharArrayDeleter decodedAuthWordDeleter(decodedAuthWord);

    (void) Base64decode(decodedAuthWord, encodedStr);
    
    StrPtrLen   nameAndPassword;    
    nameAndPassword.Set(decodedAuthWord, ::strlen(decodedAuthWord));
    
    StrPtrLen   name("");
    StrPtrLen   password("");
    StringParser parsedNameAndPassword(&nameAndPassword);

    parsedNameAndPassword.ConsumeUntil(&name,':');          
    parsedNameAndPassword.ConsumeLength(NULL, 1);
    parsedNameAndPassword.GetThruEOL(&password);
        

    // Set the qtssRTSPReqUserName and qtssRTSPReqUserPassword attributes in the Request object
    (void) this->SetValue(qtssRTSPReqUserName, 0,  name.Ptr , name.Len, QTSSDictionary::kDontObeyReadOnly);
    (void) this->SetValue(qtssRTSPReqUserPassword, 0,  password.Ptr , password.Len, QTSSDictionary::kDontObeyReadOnly);

    // Also set the qtssUserName attribute in the qtssRTSPReqUserProfile object attribute of the Request Object
    (void) fUserProfile.SetValue(qtssUserName, 0, name.Ptr, name.Len, QTSSDictionary::kDontObeyReadOnly);
    
    return theErr;
}
    
QTSS_Error RTSPRequest::ParseDigestHeader(StringParser *inParsedAuthLinePtr)
{
    QTSS_Error  theErr = QTSS_NoErr;
    fAuthScheme = qtssAuthDigest;
    
    inParsedAuthLinePtr->ConsumeWhitespace();
    
    while(inParsedAuthLinePtr->GetDataRemaining() != 0) 
    {
        StrPtrLen fieldNameAndValue("");
        inParsedAuthLinePtr->GetThru(&fieldNameAndValue, ','); 
        StringParser parsedNameAndValue(&fieldNameAndValue);
        StrPtrLen fieldName("");
        StrPtrLen fieldValue("");
        
        //Parse name="value" pair fields in the auth line
        parsedNameAndValue.ConsumeUntil(&fieldName, '=');
        parsedNameAndValue.ConsumeLength(NULL, 1);
        parsedNameAndValue.GetThruEOL(&fieldValue);
        StringParser::UnQuote(&fieldValue);
        
        // fieldValue.Ptr below is a pointer to a part of the qtssAuthorizationHeader 
        // as GetValue returns a pointer
        // Since the header attribute remains for the entire time the request is alive
        // we don't need to make copies of the values of each field into the request
        // object, and can just keep pointers to the values
        // Thus, no need to delete memory for the following fields when the request is deleted:
        // fAuthRealm, fAuthNonce, fAuthUri, fAuthNonceCount, fAuthResponse, fAuthOpaque
        if(fieldName.Equal(sUsernameStr)) {
            // Set the qtssRTSPReqUserName attribute in the Request object
            (void) this->SetValue(qtssRTSPReqUserName, 0,  fieldValue.Ptr , fieldValue.Len, QTSSDictionary::kDontObeyReadOnly);
            // Also set the qtssUserName attribute in the qtssRTSPReqUserProfile object attribute of the Request Object
            (void) fUserProfile.SetValue(qtssUserName, 0, fieldValue.Ptr, fieldValue.Len, QTSSDictionary::kDontObeyReadOnly);
        }
        else if(fieldName.Equal(sRealmStr)) {
            fAuthRealm.Set(fieldValue.Ptr, fieldValue.Len);
        }
        else if(fieldName.Equal(sNonceStr)) {
            fAuthNonce.Set(fieldValue.Ptr, fieldValue.Len);
        }
        else if(fieldName.Equal(sUriStr)) {
            fAuthUri.Set(fieldValue.Ptr, fieldValue.Len);
        }
        else if(fieldName.Equal(sQopStr)) {
            if(fieldValue.Equal(sQopAuthStr))
                fAuthQop = RTSPSessionInterface::kAuthQop;
            else if(fieldValue.Equal(sQopAuthIntStr))
                fAuthQop = RTSPSessionInterface::kAuthIntQop;
        }
        else if(fieldName.Equal(sNonceCountStr)) {
            fAuthNonceCount.Set(fieldValue.Ptr, fieldValue.Len);
        }
        else if(fieldName.Equal(sResponseStr)) {
            fAuthResponse.Set(fieldValue.Ptr, fieldValue.Len);
        }
        else if(fieldName.Equal(sOpaqueStr)) {
            fAuthOpaque.Set(fieldValue.Ptr, fieldValue.Len);
        }
                
        inParsedAuthLinePtr->ConsumeWhitespace();
    }   
    
    return theErr;
}

QTSS_Error RTSPRequest::ParseAuthHeader(void)
{
    QTSS_Error  theErr = QTSS_NoErr;
    QTSSDictionary *theRTSPHeaders = this->GetHeaderDictionary();
    StrPtrLen   *authLine = theRTSPHeaders->GetValue(qtssAuthorizationHeader);
    if ( (authLine == NULL) || (0 == authLine->Len))
        return theErr;
        
    StrPtrLen   authWord("");
    StringParser parsedAuthLine(authLine);
    parsedAuthLine.ConsumeUntilWhitespace(&authWord);
    
    if (authWord.EqualIgnoreCase(sAuthBasicStr.Ptr, sAuthBasicStr.Len)) 
        return ParseBasicHeader(&parsedAuthLine);
        
    if (authWord.EqualIgnoreCase(sAuthDigestStr.Ptr, sAuthDigestStr.Len)) 
        return ParseDigestHeader(&parsedAuthLine);

    return theErr;
}

void RTSPRequest::SetupAuthLocalPath(void)
{
     QTSS_AttributeID theID = qtssRTSPReqFilePath;

    //
    // Get the truncated path on a setup, because setups have the trackID appended
    if (qtssSetupMethod == fMethod)
        theID = qtssRTSPReqFilePathTrunc;   

    UInt32 theLen = 0;
    char* theFullPath = QTSSModuleUtils::GetFullPath(this, theID, &theLen, NULL);
    this->SetValue(qtssRTSPReqLocalPath, 0, theFullPath, theLen, QTSSDictionary::kDontObeyReadOnly);
	delete [] theFullPath;
}

QTSS_Error RTSPRequest::SendDigestChallenge(UInt32 qop, StrPtrLen *nonce, StrPtrLen* opaque)
{
    QTSS_Error theErr = QTSS_NoErr;

    char challengeBuf[kAuthChallengeHeaderBufSize];
    ResizeableStringFormatter challengeFormatter(challengeBuf, kAuthChallengeHeaderBufSize);
    
    StrPtrLen realm;
    char *prefRealmPtr = NULL;
    StrPtrLen *realmPtr = this->GetValue(qtssRTSPReqURLRealm);              // Get auth realm set by the module
    if(realmPtr->Len > 0) {
        realm = *realmPtr;
    }
    else {                                                                  // If module hasn't set the realm
        QTSServerInterface* theServer = QTSServerInterface::GetServer();    // get the realm from prefs
        prefRealmPtr = theServer->GetPrefs()->GetAuthorizationRealm();      // allocates memory
        Assert(prefRealmPtr != NULL);
        if (prefRealmPtr != NULL){  
            realm.Set(prefRealmPtr, strlen(prefRealmPtr));
        }
        else {
            realm = sDefaultRealm;  
        }
    }
    
    // Creating the Challenge header
    challengeFormatter.Put(sAuthDigestStr);             // [Digest]
    challengeFormatter.PutSpace();                      // [Digest ] 
    challengeFormatter.Put(sRealmStr);                  // [Digest realm]
    challengeFormatter.Put(sEqualQuote);                // [Digest realm="]
    challengeFormatter.Put(realm);                      // [Digest realm="somerealm]
    challengeFormatter.Put(sQuoteCommaSpace);           // [Digest realm="somerealm", ]
    if(this->GetStale()) {
        challengeFormatter.Put(sStaleTrue);             // [Digest realm="somerealm", stale="true", ]
    }
    challengeFormatter.Put(sNonceStr);                  // [Digest realm="somerealm", nonce]
    challengeFormatter.Put(sEqualQuote);                // [Digest realm="somerealm", nonce="]
    challengeFormatter.Put(*nonce);                     // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0]
    challengeFormatter.PutChar('"');                    // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0"]
    challengeFormatter.PutTerminator();                 // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0"\0]

    StrPtrLen challengePtr(challengeFormatter.GetBufPtr(), challengeFormatter.GetBytesWritten() - 1);
    fStatus = qtssClientUnAuthorized;
    this->SetResponseKeepAlive(true);
    this->AppendHeader(qtssWWWAuthenticateHeader, &challengePtr);
    this->SendHeader();
    
    // deleting the memory that was allocated in GetPrefs call above
    if (prefRealmPtr != NULL)
    {   
        delete[] prefRealmPtr;  
    }
        
    return theErr;
}

QTSS_Error RTSPRequest::SendBasicChallenge(void)
{   
    QTSS_Error theErr = QTSS_NoErr;
    char *prefRealmPtr = NULL;
    
    do 
    {   
        char realmBuff[kRealmBuffSize] = "Basic realm=\"";
        StrPtrLen challenge(realmBuff);
        StrPtrLen whichRealm;
        
        // Get the module's realm
        StrPtrLen moduleRealm;
        theErr = this->GetValuePtr(qtssRTSPReqURLRealm, 0,  (void **) &moduleRealm.Ptr, &moduleRealm.Len);
        if ( (QTSS_NoErr == theErr) && (moduleRealm.Len > 0) )
        {
            whichRealm = moduleRealm;   
        }
        else
        {
            theErr = QTSS_NoErr;
            // Get the default realm from the config file or use the static default if config realm is not found
            QTSServerInterface* theServer = QTSServerInterface::GetServer();
            prefRealmPtr = theServer->GetPrefs()->GetAuthorizationRealm(); // allocates memory
            Assert(prefRealmPtr != NULL);
            if (prefRealmPtr != NULL)
            {   whichRealm.Set(prefRealmPtr, strlen(prefRealmPtr));
            }   
            else
            {
                whichRealm = sDefaultRealm;
            }
        }
        
        int realmLen = whichRealm.Len + challenge.Len + 2; // add 2 based on double quote char + end of string 0x00
        if (realmLen > kRealmBuffSize) // The realm is too big so use the default realm
        {   Assert(0);
            whichRealm = sDefaultRealm;
        }
        memcpy(&challenge.Ptr[challenge.Len],whichRealm.Ptr,whichRealm.Len);
        int newLen = challenge.Len + whichRealm.Len;
        
        challenge.Ptr[newLen] = '"'; // add the terminating "" this was accounted for with the size check above
        challenge.Ptr[newLen + 1] = 0;// add the 0 terminator this was accounted for with the size check above
        challenge.Len = newLen +1; // set the real size of the string excluding the 0.
        
        #if (0)
        {  // test code
            char test[256];
            
            memcpy(test,sDefaultRealm.Ptr,sDefaultRealm.Len);
            test[sDefaultRealm.Len] = 0;
            qtss_printf("the static realm =%s \n",test);
            
            OSCharArrayDeleter prefDeleter(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm());
            memcpy(test,prefDeleter.GetObject(),strlen(prefDeleter.GetObject()));
            test[strlen(prefDeleter.GetObject())] = 0;
            qtss_printf("the Pref realm =%s \n",test);

            memcpy(test,moduleRealm.Ptr,moduleRealm.Len);
            test[moduleRealm.Len] = 0;
            qtss_printf("the moduleRealm  =%s \n",test);
        
            memcpy(test,whichRealm.Ptr,whichRealm.Len);
            test[whichRealm.Len] = 0;
            qtss_printf("the challenge realm  =%s \n",test);
            
            memcpy(test,challenge.Ptr,challenge.Len);
            test[challenge.Len] = 0;
            qtss_printf("the challenge string  =%s len = %ld\n",test, challenge.Len);
        }
        #endif

        fStatus = qtssClientUnAuthorized;
        this->SetResponseKeepAlive(true);
        this->AppendHeader(qtssWWWAuthenticateHeader, &challenge);
        this->SendHeader();

    
    } while (false);
    
    if (prefRealmPtr != NULL)
    {   
        delete[] prefRealmPtr;  
    }
    
    return theErr;
}

QTSS_Error RTSPRequest::SendForbiddenResponse(void)
{
    fStatus = qtssClientForbidden;
    this->SetResponseKeepAlive(false);
    this->SendHeader();

    return QTSS_NoErr;
}
