Darwin-Streaming-Server/Server.tproj/RTSPRequest.cpp

1116 lines
41 KiB
C++
Raw Normal View History

/*
*
* @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@
*
*/
/*
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", 4);
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
// might need this for rate adapt if (qtssSetupMethod != fMethod && qtssOptionsMethod != fMethod && qtssSetParameterMethod != fMethod) // any method not a setup, options, or setparameter is not allowed to have a "/trackID=" in the url.
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;
case qtssXRandomDataSizeHeader: ParseRandomDataSizeHeader(); break;
case qtss3GPPAdaptationHeader: fRequest3GPP.ParseAdpationHeader(&fHeaderDictionary); break;
case qtss3GPPLinkCharHeader: fRequest3GPP.ParseLinkCharHeader(&fHeaderDictionary); break;
case qtssBandwidthHeader: ParseBandwidthHeader(); 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
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.ConsumeNPT();
//see if there is a stop time as well.
if (theRangeParser.GetDataRemaining() > 1)
{
theRangeParser.GetThru(NULL, '-');
theRangeParser.ConsumeWhitespace();
fStopTime = (Float64)theRangeParser.ConsumeNPT();
}
}
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)
{
if (!inSubHeader || !inHeaderName || !outAddr)
return;
StringParser theSubHeaderParser(inSubHeader);
// Skip over to the value
StrPtrLen theFirstBit;
theSubHeaderParser.GetThru(&theFirstBit, '=');
theFirstBit.TrimWhitespace();
// First make sure this is the proper subheader
if (!theFirstBit.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);
}
// DJM PROTOTYPE
void RTSPRequest::ParseRandomDataSizeHeader()
{
StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssXRandomDataSizeHeader));
theContentLenParser.ConsumeWhitespace();
fRandomDataSize = theContentLenParser.ConsumeInteger(NULL);
if (fRandomDataSize > RTSPSessionInterface::kMaxRandomDataSize) {
fRandomDataSize = RTSPSessionInterface::kMaxRandomDataSize;
}
}
void RTSPRequest::ParseBandwidthHeader()
{
StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssBandwidthHeader));
theContentLenParser.ConsumeWhitespace();
fBandwidthBits = theContentLenParser.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();
StrPtrLen *authLine = inParsedAuthLinePtr->GetStream();
if (NULL != authLine)
{
StringParser digestAuthLine(authLine);
digestAuthLine.GetThru(NULL, '=');
digestAuthLine.ConsumeWhitespace();
fAuthDigestResponse.Set(authLine->Ptr, authLine->Len);
}
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);
this->SetValue(qtssRTSPReqDigestChallenge, 0, challengePtr.Ptr, challengePtr.Len, QTSSDictionary::kDontObeyReadOnly);
RTSPSessionInterface* thisRTSPSession = this->GetSession();
if (thisRTSPSession)
{ (void)thisRTSPSession->SetValue(qtssRTSPSesLastDigestChallenge, 0, challengePtr.Ptr, challengePtr.Len,QTSSDictionary::kDontObeyReadOnly);
}
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 = %"_S32BITARG_"\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;
}