Darwin-Streaming-Server/HTTPUtilitiesLib/HTTPRequest.cpp

419 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;
}