420 lines
14 KiB
C++
420 lines
14 KiB
C++
|
/*
|
||
|
*
|
||
|
* @APPLE_LICENSE_HEADER_START@
|
||
|
*
|
||
|
* Copyright (c) 1999-2008 Apple 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, "%"_U32BITARG_"", 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;
|
||
|
}
|