2202 lines
94 KiB
C++
2202 lines
94 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@
|
||
|
*
|
||
|
*/
|
||
|
/*
|
||
|
File: RTSPSession.cpp
|
||
|
|
||
|
Contains: Implemenation of RTSPSession objects
|
||
|
|
||
|
|
||
|
*/
|
||
|
#define __RTSP_HTTP_DEBUG__ 0
|
||
|
#define __RTSP_HTTP_VERBOSE__ 0
|
||
|
#define __RTSP_AUTH_DEBUG__ 0
|
||
|
#define debug_printf if (__RTSP_AUTH_DEBUG__) qtss_printf
|
||
|
|
||
|
#include "RTSPSession.h"
|
||
|
#include "RTSPRequest.h"
|
||
|
#include "QTSServerInterface.h"
|
||
|
#include "RTSPRequest3GPP.h"
|
||
|
|
||
|
#include "MyAssert.h"
|
||
|
#include "OSMemory.h"
|
||
|
|
||
|
#include "QTSS.h"
|
||
|
#include "QTSSModuleUtils.h"
|
||
|
#include "UserAgentParser.h"
|
||
|
#include "base64.h"
|
||
|
#include "OSArrayObjectDeleter.h"
|
||
|
#include "md5digest.h"
|
||
|
#include "QTSSDataConverter.h"
|
||
|
|
||
|
#if __FreeBSD__ || __hpux__
|
||
|
#include <unistd.h>
|
||
|
#endif
|
||
|
|
||
|
#include <errno.h>
|
||
|
|
||
|
#if __solaris__ || __linux__ || __sgi__ || __hpux__
|
||
|
#include <crypt.h>
|
||
|
#endif
|
||
|
|
||
|
#if __RTSP_HTTP_DEBUG__
|
||
|
#define HTTP_TRACE(s) qtss_printf(s);
|
||
|
#define HTTP_TRACE_SPL(s) PrintfStrPtrLen(s);
|
||
|
#define HTTP_TRACE_ONE(s, one ) qtss_printf(s, one);
|
||
|
#define HTTP_TRACE_TWO(s, one, two ) qtss_printf(s, one, two);
|
||
|
#else
|
||
|
#define HTTP_TRACE(s);
|
||
|
#define HTTP_TRACE_SPL(s);
|
||
|
#define HTTP_TRACE_ONE(s, one );
|
||
|
#define HTTP_TRACE_TWO(s, one, two );
|
||
|
#endif
|
||
|
|
||
|
#if __RTSP_HTTP_VERBOSE__
|
||
|
#define HTTP_VTRACE(s) qtss_printf(s);
|
||
|
#define HTTP_VTRACE_SPL(s) PrintfStrPtrLen(s);
|
||
|
#define HTTP_VTRACE_ONE(s, one ) qtss_printf(s, one);
|
||
|
#define HTTP_VTRACE_TWO(s, one, two ) qtss_printf(s, one, two);
|
||
|
#else
|
||
|
#define HTTP_VTRACE(s);
|
||
|
#define HTTP_VTRACE_SPL(s);
|
||
|
#define HTTP_VTRACE_ONE(s, one );
|
||
|
#define HTTP_VTRACE_TWO(s, one, two );
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
#if __RTSP_HTTP_DEBUG__ || __RTSP_HTTP_VERBOSE__
|
||
|
|
||
|
static void PrintfStrPtrLen( StrPtrLen *splRequest )
|
||
|
{
|
||
|
|
||
|
char buff[1024];
|
||
|
|
||
|
memcpy( buff, splRequest->Ptr, splRequest->Len );
|
||
|
|
||
|
buff[ splRequest->Len] = 0;
|
||
|
|
||
|
HTTP_TRACE_ONE( "%s\n", buff )
|
||
|
//qtss_printf( "%s\n", buff );
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//hack stuff
|
||
|
static char* sBroadcasterSessionName="QTSSReflectorModuleBroadcasterSession";
|
||
|
static QTSS_AttributeID sClientBroadcastSessionAttr = qtssIllegalAttrID;
|
||
|
|
||
|
|
||
|
static StrPtrLen sVideoStr("video");
|
||
|
static StrPtrLen sAudioStr("audio");
|
||
|
static StrPtrLen sRtpMapStr("rtpmap");
|
||
|
static StrPtrLen sControlStr("control");
|
||
|
static StrPtrLen sBufferDelayStr("x-bufferdelay");
|
||
|
static StrPtrLen sContentType("application/x-random-data");
|
||
|
|
||
|
static StrPtrLen sAuthAlgorithm("md5");
|
||
|
static StrPtrLen sAuthQop("auth");
|
||
|
static StrPtrLen sEmptyStr("");
|
||
|
|
||
|
// static class member initialized in RTSPSession ctor
|
||
|
OSRefTable* RTSPSession::sHTTPProxyTunnelMap = NULL;
|
||
|
|
||
|
char RTSPSession::sHTTPResponseHeaderBuf[kMaxHTTPResponseLen];
|
||
|
StrPtrLen RTSPSession::sHTTPResponseHeaderPtr(sHTTPResponseHeaderBuf, kMaxHTTPResponseLen);
|
||
|
|
||
|
char RTSPSession::sHTTPResponseNoServerHeaderBuf[kMaxHTTPResponseLen];
|
||
|
StrPtrLen RTSPSession::sHTTPResponseNoServerHeaderPtr(sHTTPResponseNoServerHeaderBuf, kMaxHTTPResponseLen);
|
||
|
|
||
|
// stock reponse with place holder for server header and optional "x-server-ip-address" header ( %s%s%s for "x-server-ip-address" + ip address + \r\n )
|
||
|
// the optional version must be generated at runtime to include a valid IP address for the actual interface
|
||
|
char* RTSPSession::sHTTPResponseFormatStr = "HTTP/1.0 200 OK\r\n%s%s%s%s\r\nConnection: close\r\nDate: Thu, 19 Aug 1982 18:30:00 GMT\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Type: application/x-rtsp-tunnelled\r\n\r\n";
|
||
|
char* RTSPSession::sHTTPNoServerResponseFormatStr = "HTTP/1.0 200 OK\r\n%s%s%s%sConnection: close\r\nDate: Thu, 19 Aug 1982 18:30:00 GMT\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Type: application/x-rtsp-tunnelled\r\n\r\n";
|
||
|
|
||
|
void RTSPSession::Initialize()
|
||
|
{
|
||
|
sHTTPProxyTunnelMap = new OSRefTable(OSRefTable::kDefaultTableSize);
|
||
|
|
||
|
// Construct premade HTTP response for HTTP proxy tunnel
|
||
|
qtss_sprintf(sHTTPResponseHeaderBuf, sHTTPResponseFormatStr, "","","", QTSServerInterface::GetServerHeader().Ptr);
|
||
|
sHTTPResponseHeaderPtr.Len = ::strlen(sHTTPResponseHeaderBuf);
|
||
|
Assert(sHTTPResponseHeaderPtr.Len < kMaxHTTPResponseLen);
|
||
|
|
||
|
qtss_sprintf(sHTTPResponseNoServerHeaderBuf, sHTTPNoServerResponseFormatStr, "","","","");
|
||
|
sHTTPResponseNoServerHeaderPtr.Len = ::strlen(sHTTPResponseNoServerHeaderBuf);
|
||
|
Assert(sHTTPResponseNoServerHeaderPtr.Len < kMaxHTTPResponseLen);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
RTSPSession::RTSPSession( Bool16 doReportHTTPConnectionAddress )
|
||
|
: RTSPSessionInterface(),
|
||
|
fRequest(NULL),
|
||
|
fRTPSession(NULL),
|
||
|
fReadMutex(),
|
||
|
fHTTPMethod( kHTTPMethodInit ),
|
||
|
fWasHTTPRequest( false ),
|
||
|
fFoundValidAccept( false),
|
||
|
fDoReportHTTPConnectionAddress(doReportHTTPConnectionAddress),
|
||
|
fCurrentModule(0),
|
||
|
fState(kReadingFirstRequest)
|
||
|
{
|
||
|
this->SetTaskName("RTSPSession");
|
||
|
|
||
|
// must guarantee this map is present
|
||
|
Assert(sHTTPProxyTunnelMap != NULL);
|
||
|
|
||
|
QTSServerInterface::GetServer()->AlterCurrentRTSPSessionCount(1);
|
||
|
|
||
|
// Setup the QTSS param block, as none of these fields will change through the course of this session.
|
||
|
fRoleParams.rtspRequestParams.inRTSPSession = this;
|
||
|
fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
|
||
|
fRoleParams.rtspRequestParams.inClientSession = NULL;
|
||
|
|
||
|
fModuleState.curModule = NULL;
|
||
|
fModuleState.curTask = this;
|
||
|
fModuleState.curRole = 0;
|
||
|
fModuleState.globalLockRequested = false;
|
||
|
|
||
|
fProxySessionID[0] = 0;
|
||
|
fProxySessionIDPtr.Set( fProxySessionID, 0 );
|
||
|
|
||
|
fLastRTPSessionID[0] = 0;
|
||
|
fLastRTPSessionIDPtr.Set( fLastRTPSessionID, 0 );
|
||
|
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
|
||
|
|
||
|
(void)QTSS_IDForAttr(qtssClientSessionObjectType, sBroadcasterSessionName, &sClientBroadcastSessionAttr);
|
||
|
|
||
|
}
|
||
|
|
||
|
RTSPSession::~RTSPSession()
|
||
|
{
|
||
|
// Invoke the session closing modules
|
||
|
QTSS_RoleParams theParams;
|
||
|
theParams.rtspSessionClosingParams.inRTSPSession = this;
|
||
|
|
||
|
// Invoke modules
|
||
|
for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPSessionClosingRole); x++)
|
||
|
(void)QTSServerInterface::GetModule(QTSSModule::kRTSPSessionClosingRole, x)->CallDispatch(QTSS_RTSPSessionClosing_Role, &theParams);
|
||
|
|
||
|
fLiveSession = false; //used in Clean up request to remove the RTP session.
|
||
|
this->CleanupRequest();// Make sure that all our objects are deleted
|
||
|
if (fSessionType == qtssRTSPSession)
|
||
|
QTSServerInterface::GetServer()->AlterCurrentRTSPSessionCount(-1);
|
||
|
else
|
||
|
QTSServerInterface::GetServer()->AlterCurrentRTSPHTTPSessionCount(-1);
|
||
|
|
||
|
if ( *fProxySessionID != '\0')
|
||
|
{
|
||
|
#if DEBUG
|
||
|
char * str = "???";
|
||
|
|
||
|
if ( fSessionType == qtssRTSPHTTPInputSession )
|
||
|
str = "input session";
|
||
|
else if ( fSessionType == qtssRTSPHTTPSession )
|
||
|
str = "input session";
|
||
|
|
||
|
HTTP_VTRACE_TWO( "~RTSPSession, was a fProxySessionID (%s), %s\n", fProxySessionID, str )
|
||
|
#endif
|
||
|
sHTTPProxyTunnelMap->UnRegister( &fProxyRef );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SInt64 RTSPSession::Run()
|
||
|
{
|
||
|
EventFlags events = this->GetEvents();
|
||
|
QTSS_Error err = QTSS_NoErr;
|
||
|
QTSSModule* theModule = NULL;
|
||
|
UInt32 numModules = 0;
|
||
|
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
|
||
|
// Some callbacks look for this struct in the thread object
|
||
|
OSThreadDataSetter theSetter(&fModuleState, NULL);
|
||
|
|
||
|
//check for a timeout or a kill. If so, just consider the session dead
|
||
|
if ((events & Task::kTimeoutEvent) || (events & Task::kKillEvent))
|
||
|
fLiveSession = false;
|
||
|
|
||
|
while (this->IsLiveSession())
|
||
|
{
|
||
|
// RTSP Session state machine. There are several well defined points in an RTSP request
|
||
|
// where this session may have to return from its run function and wait for a new event.
|
||
|
// Because of this, we need to track our current state and return to it.
|
||
|
|
||
|
switch (fState)
|
||
|
{
|
||
|
case kReadingFirstRequest:
|
||
|
{
|
||
|
if ((err = fInputStream.ReadRequest()) == QTSS_NoErr)
|
||
|
{
|
||
|
// If the RequestStream returns QTSS_NoErr, it means
|
||
|
// that we've read all outstanding data off the socket,
|
||
|
// and still don't have a full request. Wait for more data.
|
||
|
|
||
|
//+rt use the socket that reads the data, may be different now.
|
||
|
fInputSocketP->RequestEvent(EV_RE);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((err != QTSS_RequestArrived) && (err != E2BIG))
|
||
|
{
|
||
|
// Any other error implies that the client has gone away. At this point,
|
||
|
// we can't have 2 sockets, so we don't need to do the "half closed" check
|
||
|
// we do below
|
||
|
Assert(err > 0);
|
||
|
Assert(!this->IsLiveSession());
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (err == QTSS_RequestArrived)
|
||
|
fState = kHTTPFilteringRequest;
|
||
|
// If we get an E2BIG, it means our buffer was overfilled.
|
||
|
// In that case, we can just jump into the following state, and
|
||
|
// the code their does a check for this error and returns an error.
|
||
|
if (err == E2BIG)
|
||
|
fState = kHaveNonTunnelMessage;
|
||
|
}
|
||
|
continue;
|
||
|
|
||
|
case kHTTPFilteringRequest:
|
||
|
{
|
||
|
|
||
|
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest\n" )
|
||
|
|
||
|
fState = kHaveNonTunnelMessage; // assume it's not a tunnel setup message
|
||
|
// prefilter will set correct tunnel state if it is.
|
||
|
|
||
|
QTSS_Error preFilterErr = this->PreFilterForHTTPProxyTunnel();
|
||
|
|
||
|
if ( preFilterErr == QTSS_NoErr )
|
||
|
{
|
||
|
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest\n" )
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// pre filter error indicates a tunnelling message that could
|
||
|
// not join to a session.
|
||
|
HTTP_TRACE( "RTSPSession::Run kHTTPFilteringRequest Tunnel protocol ERROR.\n" )
|
||
|
return -1;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case kWaitingToBindHTTPTunnel:
|
||
|
//flush the GET response, if it's there
|
||
|
err = fOutputStream.Flush();
|
||
|
if (err == EAGAIN)
|
||
|
{
|
||
|
// If we get this error, we are currently flow-controlled and should
|
||
|
// wait for the socket to become writeable again
|
||
|
fSocket.RequestEvent(EV_WR);
|
||
|
}
|
||
|
return 0;
|
||
|
//continue;
|
||
|
|
||
|
case kSocketHasBeenBoundIntoHTTPTunnel:
|
||
|
|
||
|
// DMS - Can this execute either? I don't think so... this one
|
||
|
// we may not need...
|
||
|
|
||
|
// I've been joined, it's time to kill this session.
|
||
|
Assert(!this->IsLiveSession()); // at least the socket should not report connected any longer
|
||
|
HTTP_TRACE( "RTSPSession has died of snarfage.\n" )
|
||
|
break;
|
||
|
|
||
|
|
||
|
case kReadingRequest:
|
||
|
{
|
||
|
// We should lock down the session while reading in data,
|
||
|
// because we can't snarf up a POST while reading.
|
||
|
OSMutexLocker readMutexLocker(&fReadMutex);
|
||
|
|
||
|
// we should be only reading an RTSP request here, no HTTP tunnel messages
|
||
|
|
||
|
if ((err = fInputStream.ReadRequest()) == QTSS_NoErr)
|
||
|
{
|
||
|
// If the RequestStream returns QTSS_NoErr, it means
|
||
|
// that we've read all outstanding data off the socket,
|
||
|
// and still don't have a full request. Wait for more data.
|
||
|
|
||
|
//+rt use the socket that reads the data, may be different now.
|
||
|
fInputSocketP->RequestEvent(EV_RE);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((err != QTSS_RequestArrived) && (err != E2BIG) && (err != QTSS_BadArgument))
|
||
|
{
|
||
|
//Any other error implies that the input connection has gone away.
|
||
|
// We should only kill the whole session if we aren't doing HTTP.
|
||
|
// (If we are doing HTTP, the POST connection can go away)
|
||
|
Assert(err > 0);
|
||
|
if (fOutputSocketP->IsConnected())
|
||
|
{
|
||
|
// If we've gotten here, this must be an HTTP session with
|
||
|
// a dead input connection. If that's the case, we should
|
||
|
// clean up immediately so as to not have an open socket
|
||
|
// needlessly lingering around, taking up space.
|
||
|
Assert(fOutputSocketP != fInputSocketP);
|
||
|
Assert(!fInputSocketP->IsConnected());
|
||
|
fInputSocketP->Cleanup();
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert(!this->IsLiveSession());
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
fState = kHaveNonTunnelMessage;
|
||
|
// fall thru to kHaveNonTunnelMessage
|
||
|
}
|
||
|
|
||
|
case kHaveNonTunnelMessage:
|
||
|
{
|
||
|
// should only get here when fInputStream has a full message built.
|
||
|
|
||
|
Assert( fInputStream.GetRequestBuffer() );
|
||
|
|
||
|
Assert(fRequest == NULL);
|
||
|
fRequest = NEW RTSPRequest(this);
|
||
|
fRoleParams.rtspRequestParams.inRTSPRequest = fRequest;
|
||
|
fRoleParams.rtspRequestParams.inRTSPHeaders = fRequest->GetHeaderDictionary();
|
||
|
|
||
|
// We have an RTSP request and are about to begin processing. We need to
|
||
|
// make sure that anyone sending interleaved data on this session won't
|
||
|
// be allowed to do so until we are done sending our response
|
||
|
// We also make sure that a POST session can't snarf in while we're
|
||
|
// processing the request.
|
||
|
fReadMutex.Lock();
|
||
|
fSessionMutex.Lock();
|
||
|
|
||
|
// The fOutputStream's fBytesWritten counter is used to
|
||
|
// count the # of bytes for this RTSP response. So, at
|
||
|
// this point, reset it to 0 (we can then just let it increment
|
||
|
// until the next request comes in)
|
||
|
fOutputStream.ResetBytesWritten();
|
||
|
|
||
|
// Check for an overfilled buffer, and return an error.
|
||
|
if (err == E2BIG)
|
||
|
{
|
||
|
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientBadRequest,
|
||
|
qtssMsgRequestTooLong);
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
// Check for a corrupt base64 error, return an error
|
||
|
if (err == QTSS_BadArgument)
|
||
|
{
|
||
|
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientBadRequest,
|
||
|
qtssMsgBadBase64);
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Assert(err == QTSS_RequestArrived);
|
||
|
fState = kFilteringRequest;
|
||
|
|
||
|
// Note that there is no break here. We'd like to continue onto the next
|
||
|
// state at this point. This goes for every case in this case statement
|
||
|
}
|
||
|
|
||
|
case kFilteringRequest:
|
||
|
{
|
||
|
// We received something so auto refresh
|
||
|
// The need to auto refresh is because the api doesn't allow a module to refresh at this point
|
||
|
//
|
||
|
fTimeoutTask.RefreshTimeout();
|
||
|
|
||
|
//
|
||
|
// Before we even do this, check to see if this is a *data* packet,
|
||
|
// in which case this isn't an RTSP request, so we don't need to go
|
||
|
// through any of the remaining steps
|
||
|
|
||
|
if (fInputStream.IsDataPacket()) // can this interfere with MP3?
|
||
|
{
|
||
|
this->HandleIncomingDataPacket();
|
||
|
fState = kCleaningUp;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// In case a module wants to replace the request
|
||
|
char* theReplacedRequest = NULL;
|
||
|
char* oldReplacedRequest = NULL;
|
||
|
|
||
|
// Setup the filter param block
|
||
|
QTSS_RoleParams theFilterParams;
|
||
|
theFilterParams.rtspFilterParams.inRTSPSession = this;
|
||
|
theFilterParams.rtspFilterParams.inRTSPRequest = fRequest;
|
||
|
theFilterParams.rtspFilterParams.outNewRequest = &theReplacedRequest;
|
||
|
|
||
|
// Invoke filter modules
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPFilterRole);
|
||
|
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
|
||
|
{
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPFilterRole, fCurrentModule);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPFilter_Role, &theFilterParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if this module has replaced the request. If so, check
|
||
|
// to see if there is an old replacement that we should delete
|
||
|
if (theReplacedRequest != NULL)
|
||
|
{
|
||
|
if (oldReplacedRequest != NULL)
|
||
|
delete [] oldReplacedRequest;
|
||
|
|
||
|
fRequest->SetVal(qtssRTSPReqFullRequest, theReplacedRequest, ::strlen(theReplacedRequest));
|
||
|
oldReplacedRequest = theReplacedRequest;
|
||
|
theReplacedRequest = NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
fCurrentModule = 0;
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (fSentOptionsRequest && this->ParseOptionsResponse())
|
||
|
{
|
||
|
fRoundTripTime = (SInt32) (OS::Milliseconds() - fOptionsRequestSendTime);
|
||
|
//qtss_printf("RTSPSession::Run RTT time = %"_S32BITARG_" msec\n", fRoundTripTime);
|
||
|
fState = kSendingResponse;
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
// Otherwise, this is a normal request, so parse it and get the RTPSession.
|
||
|
this->SetupRequest();
|
||
|
|
||
|
|
||
|
// This might happen if there is some syntax or other error,
|
||
|
// or if it is an OPTIONS request
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
fState = kRoutingRequest;
|
||
|
}
|
||
|
case kRoutingRequest:
|
||
|
{
|
||
|
// Invoke router modules
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPRouteRole);
|
||
|
{
|
||
|
// Manipulation of the RTPSession from the point of view of
|
||
|
// a module is guaranteed to be atomic by the API.
|
||
|
Assert(fRTPSession != NULL);
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
|
||
|
{
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPRouteRole, fCurrentModule);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPRoute_Role, &fRoleParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fCurrentModule = 0;
|
||
|
|
||
|
// SetupAuthLocalPath must happen after kRoutingRequest and before kAuthenticatingRequest
|
||
|
// placed here so that if the state is shifted to kPostProcessingRequest from a response being sent
|
||
|
// then the AuthLocalPath will still be set.
|
||
|
fRequest->SetupAuthLocalPath();
|
||
|
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(fRequest->SkipAuthorization())
|
||
|
{
|
||
|
// Skip the authentication and authorization states
|
||
|
|
||
|
// The foll. normally gets executed at the end of the authorization state
|
||
|
// Prepare for kPreprocessingRequest state.
|
||
|
fState = kPreprocessingRequest;
|
||
|
|
||
|
if (fRequest->GetMethod() == qtssSetupMethod)
|
||
|
// Make sure to erase the session ID stored in the request at this point.
|
||
|
// If we fail to do so, this same session would be used if another
|
||
|
// SETUP was issued on this same TCP connection.
|
||
|
fLastRTPSessionIDPtr.Len = 0;
|
||
|
else if (fLastRTPSessionIDPtr.Len == 0)
|
||
|
fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
fState = kAuthenticatingRequest;
|
||
|
}
|
||
|
|
||
|
case kAuthenticatingRequest:
|
||
|
{
|
||
|
Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault();
|
||
|
Bool16 allowed = allowedDefault; //server pref?
|
||
|
Bool16 hasUser = false;
|
||
|
Bool16 handled = false;
|
||
|
Bool16 wasHandled = false;
|
||
|
|
||
|
StrPtrLenDel prefRealm(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm());
|
||
|
if (prefRealm.Ptr != NULL)
|
||
|
{
|
||
|
fRequest->SetValue(qtssRTSPReqURLRealm,0, prefRealm.Ptr, prefRealm.Len, kDontObeyReadOnly);
|
||
|
}
|
||
|
|
||
|
|
||
|
QTSS_RTSPMethod method = fRequest->GetMethod();
|
||
|
if (method != qtssIllegalMethod) do
|
||
|
{ //Set the request action before calling the authentication module
|
||
|
|
||
|
if((method == qtssAnnounceMethod) || ((method == qtssSetupMethod) && fRequest->IsPushRequest()))
|
||
|
{ fRequest->SetAction(qtssActionFlagsWrite);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
void* theSession = NULL;
|
||
|
UInt32 theLen = sizeof(theSession);
|
||
|
if (QTSS_NoErr == fRTPSession->GetValue(sClientBroadcastSessionAttr, 0, &theSession, &theLen) )
|
||
|
{ fRequest->SetAction(qtssActionFlagsWrite); // an incoming broadcast session
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fRequest->SetAction(qtssActionFlagsRead);
|
||
|
} while (false);
|
||
|
else
|
||
|
{ Assert(0);
|
||
|
}
|
||
|
|
||
|
if(fRequest->GetAuthScheme() == qtssAuthNone)
|
||
|
{
|
||
|
QTSS_AuthScheme scheme = QTSServerInterface::GetServer()->GetPrefs()->GetAuthScheme();
|
||
|
if( scheme == qtssAuthBasic)
|
||
|
fRequest->SetAuthScheme(qtssAuthBasic);
|
||
|
else if( scheme == qtssAuthDigest)
|
||
|
fRequest->SetAuthScheme(qtssAuthDigest);
|
||
|
|
||
|
if( scheme == qtssAuthDigest)
|
||
|
debug_printf("RTSPSession.cpp:kAuthenticatingRequest scheme == qtssAuthDigest\n");
|
||
|
}
|
||
|
|
||
|
// Setup the authentication param block
|
||
|
QTSS_RoleParams theAuthenticationParams;
|
||
|
theAuthenticationParams.rtspAthnParams.inRTSPRequest = fRequest;
|
||
|
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
|
||
|
fRequest->SetAllowed(allowed);
|
||
|
fRequest->SetHasUser(hasUser);
|
||
|
fRequest->SetAuthHandled(handled);
|
||
|
|
||
|
StrPtrLen* lastUsedDigestChallengePtr = this->GetValue(qtssRTSPSesLastDigestChallenge);
|
||
|
if (lastUsedDigestChallengePtr != NULL)
|
||
|
(void) fRequest->SetValue(qtssRTSPReqDigestChallenge,(UInt32) 0,(void *) lastUsedDigestChallengePtr->Ptr,lastUsedDigestChallengePtr->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAthnRole);
|
||
|
for (fCurrentModule = 0; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
|
||
|
{
|
||
|
|
||
|
fRequest->SetAllowed(allowedDefault);
|
||
|
fRequest->SetHasUser(false);
|
||
|
fRequest->SetAuthHandled(false);
|
||
|
debug_printf("RTSPSession.cpp:kAuthenticatingRequest fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
|
||
|
|
||
|
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPAthnRole, fCurrentModule);
|
||
|
if (NULL == theModule)
|
||
|
continue;
|
||
|
|
||
|
if (__RTSP_AUTH_DEBUG__)
|
||
|
{ theModule->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", "\n");
|
||
|
}
|
||
|
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPAuthenticate_Role, &theAuthenticationParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
|
||
|
allowed = fRequest->GetAllowed();
|
||
|
hasUser = fRequest->GetHasUser();
|
||
|
handled = fRequest->GetAuthHandled();
|
||
|
debug_printf("RTSPSession::Run Role(kAuthenticatingRequest) allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled);
|
||
|
if (handled)
|
||
|
wasHandled = handled;
|
||
|
|
||
|
if (hasUser || handled )
|
||
|
{
|
||
|
debug_printf("RTSPSession.cpp::Run(kAuthenticatingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (!wasHandled) //don't check and possibly fail the user if it the user has already been checked.
|
||
|
this->CheckAuthentication();
|
||
|
|
||
|
fCurrentModule = 0;
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
fState = kAuthorizingRequest;
|
||
|
}
|
||
|
case kAuthorizingRequest:
|
||
|
{
|
||
|
// Invoke authorization modules
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAuthRole);
|
||
|
Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault();
|
||
|
Bool16 allowed = true;
|
||
|
Bool16 hasUser = false;
|
||
|
Bool16 handled = false;
|
||
|
QTSS_Error theErr = QTSS_NoErr;
|
||
|
|
||
|
// Invoke authorization modules
|
||
|
|
||
|
// Manipulation of the RTPSession from the point of view of
|
||
|
// a module is guaranteed to be atomic by the API.
|
||
|
Assert(fRTPSession != NULL);
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
fRequest->SetAllowed(allowed);
|
||
|
fRequest->SetHasUser(hasUser);
|
||
|
fRequest->SetAuthHandled(handled);
|
||
|
|
||
|
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
|
||
|
{
|
||
|
fRequest->SetHasUser(false);
|
||
|
fRequest->SetAuthHandled(false);
|
||
|
debug_printf("RTSPSession.cpp:kAuthorizingRequest BEFORE DISPATCH fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
|
||
|
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPAuthRole, fCurrentModule);
|
||
|
if (NULL == theModule)
|
||
|
continue;
|
||
|
|
||
|
if (__RTSP_AUTH_DEBUG__)
|
||
|
{ theModule->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", "\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPAuthorize_Role, &fRoleParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
|
||
|
// allowed != default means a module has set the result
|
||
|
// handled means a module wants to be the primary for this request
|
||
|
// -- example qtaccess says only allow valid user and allowed default is false. So module says handled, hasUser is false, allowed is false
|
||
|
//
|
||
|
allowed = fRequest->GetAllowed();
|
||
|
hasUser = fRequest->GetHasUser();
|
||
|
handled = fRequest->GetAuthHandled();
|
||
|
debug_printf("RTSPSession::Run Role(kAuthorizingRequest) allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled);
|
||
|
|
||
|
if (!allowed && !handled) //old module break on !allowed
|
||
|
{
|
||
|
debug_printf("RTSPSession.cpp::Run(kAuthorizingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
|
||
|
break;
|
||
|
}
|
||
|
if (!allowed && hasUser && handled) //new module break on !allowed
|
||
|
{
|
||
|
debug_printf("RTSPSession.cpp::Run(kAuthorizingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", fCurrentModule,numModules);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
this->SaveRequestAuthorizationParams(fRequest);
|
||
|
|
||
|
if (!allowed)
|
||
|
{
|
||
|
if (false == fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
QTSS_AuthScheme challengeScheme = fRequest->GetAuthScheme();
|
||
|
|
||
|
if( challengeScheme == qtssAuthDigest)
|
||
|
{ debug_printf("RTSPSession.cpp:kAuthorizingRequest scheme == qtssAuthDigest)\n");
|
||
|
}
|
||
|
else if( challengeScheme == qtssAuthBasic)
|
||
|
{ debug_printf("RTSPSession.cpp:kAuthorizingRequest scheme == qtssAuthBasic)\n");
|
||
|
}
|
||
|
|
||
|
if(challengeScheme == qtssAuthBasic) {
|
||
|
fRTPSession->SetAuthScheme(qtssAuthBasic);
|
||
|
theErr = fRequest->SendBasicChallenge();
|
||
|
}
|
||
|
else if(challengeScheme == qtssAuthDigest) {
|
||
|
fRTPSession->UpdateDigestAuthChallengeParams(false, false, RTSPSessionInterface::kNoQop);
|
||
|
theErr = fRequest->SendDigestChallenge(fRTPSession->GetAuthQop(), fRTPSession->GetAuthNonce(), fRTPSession->GetAuthOpaque());
|
||
|
}
|
||
|
else {
|
||
|
// No authentication scheme is given and the request was not allowed,
|
||
|
// so send a 403: Forbidden message
|
||
|
theErr = fRequest->SendForbiddenResponse();
|
||
|
}
|
||
|
if (QTSS_NoErr != theErr) // We had an error so bail on the request quit the session and post process the request.
|
||
|
{
|
||
|
fRequest->SetResponseKeepAlive(false);
|
||
|
fCurrentModule = 0;
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fCurrentModule = 0;
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Prepare for kPreprocessingRequest state.
|
||
|
fState = kPreprocessingRequest;
|
||
|
|
||
|
if (fRequest->GetMethod() == qtssSetupMethod)
|
||
|
// Make sure to erase the session ID stored in the request at this point.
|
||
|
// If we fail to do so, this same session would be used if another
|
||
|
// SETUP was issued on this same TCP connection.
|
||
|
fLastRTPSessionIDPtr.Len = 0;
|
||
|
else if (fLastRTPSessionIDPtr.Len == 0)
|
||
|
fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr);
|
||
|
}
|
||
|
|
||
|
case kPreprocessingRequest:
|
||
|
{
|
||
|
// Invoke preprocessor modules
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPPreProcessorRole);
|
||
|
{
|
||
|
// Manipulation of the RTPSession from the point of view of
|
||
|
// a module is guarenteed to be atomic by the API.
|
||
|
Assert(fRTPSession != NULL);
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
for (; (fCurrentModule < numModules) && ((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested); fCurrentModule++)
|
||
|
{
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPPreProcessorRole, fCurrentModule);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPPreProcessor_Role, &fRoleParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
// The way the API is set up currently, the first module that adds a stream
|
||
|
// to the session is responsible for sending RTP packets for the session.
|
||
|
if (fRTPSession->HasAnRTPStream() && (fRTPSession->GetPacketSendingModule() == NULL))
|
||
|
fRTPSession->SetPacketSendingModule(theModule);
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fCurrentModule = 0;
|
||
|
if (fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
fState = kPostProcessingRequest;
|
||
|
break;
|
||
|
}
|
||
|
fState = kProcessingRequest;
|
||
|
}
|
||
|
|
||
|
case kProcessingRequest:
|
||
|
{
|
||
|
// If no preprocessor sends a response, move onto the request processing module. It
|
||
|
// is ALWAYS supposed to send a response, but if it doesn't, we have a canned error
|
||
|
// to send back.
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPRequestRole) > 0)
|
||
|
{
|
||
|
// Manipulation of the RTPSession from the point of view of
|
||
|
// a module is guarenteed to be atomic by the API.
|
||
|
Assert(fRTPSession != NULL);
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPRequestRole, 0);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPRequest_Role, &fRoleParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
// Do the same check as above for the preprocessor
|
||
|
if (fRTPSession->HasAnRTPStream() && fRTPSession->GetPacketSendingModule() == NULL)
|
||
|
fRTPSession->SetPacketSendingModule(theModule);
|
||
|
|
||
|
this->Process3GPPData();
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
if (!fRequest->HasResponseBeenSent())
|
||
|
{
|
||
|
// no modules took this one so send back a parameter error
|
||
|
if (fRequest->GetMethod() == qtssSetParameterMethod) // keep session
|
||
|
{
|
||
|
QTSS_RTSPStatusCode statusCode = qtssSuccessOK; //qtssClientParameterNotUnderstood;
|
||
|
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
|
||
|
fRequest->SendHeader();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
QTSSModuleUtils::SendErrorResponse(fRequest, qtssServerInternal, qtssMsgNoModuleForRequest);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fState = kPostProcessingRequest;
|
||
|
}
|
||
|
|
||
|
case kPostProcessingRequest:
|
||
|
{
|
||
|
// Post process the request *before* sending the response. Therefore, we
|
||
|
// will post process regardless of whether the client actually gets our response
|
||
|
// or not.
|
||
|
|
||
|
//if this is not a keepalive request, we should kill the session NOW
|
||
|
fLiveSession = fRequest->GetResponseKeepAlive();
|
||
|
|
||
|
if (fRTPSession != NULL)
|
||
|
{
|
||
|
// Invoke postprocessor modules only if there is an RTP session. We do NOT want
|
||
|
// postprocessors running when filters or syntax errors have occurred in the request!
|
||
|
numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPPostProcessorRole);
|
||
|
{
|
||
|
// Manipulation of the RTPSession from the point of view of
|
||
|
// a module is guarenteed to be atomic by the API.
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
// Make sure the RTPSession contains a copy of the realStatusCode in this request
|
||
|
UInt32 realStatusCode = RTSPProtocol::GetStatusCode(fRequest->GetStatus());
|
||
|
(void) fRTPSession->SetValue(qtssCliRTSPReqRealStatusCode,(UInt32) 0,(void *) &realStatusCode, sizeof(realStatusCode), QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// Make sure the RTPSession contains a copy of the qtssRTSPReqRespMsg in this request
|
||
|
StrPtrLen* theRespMsg = fRequest->GetValue(qtssRTSPReqRespMsg);
|
||
|
if (theRespMsg->Len > 0)
|
||
|
(void)fRTPSession->SetValue(qtssCliRTSPReqRespMsg, 0, theRespMsg->Ptr, theRespMsg->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// Set the current RTSP session for this RTP session.
|
||
|
// We do this here because we need to make sure the SessionMutex
|
||
|
// is grabbed while we do this. Only do this if the RTSP session
|
||
|
// is still alive, of course.
|
||
|
if (this->IsLiveSession())
|
||
|
fRTPSession->UpdateRTSPSession(this);
|
||
|
|
||
|
for (; (fCurrentModule < numModules) || (fModuleState.eventRequested) ; fCurrentModule++)
|
||
|
{
|
||
|
fModuleState.eventRequested = false;
|
||
|
fModuleState.idleTime = 0;
|
||
|
if (fModuleState.globalLockRequested )
|
||
|
{ fModuleState.globalLockRequested = false;
|
||
|
fModuleState.isGlobalLocked = true;
|
||
|
}
|
||
|
|
||
|
theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPPostProcessorRole, fCurrentModule);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPPostProcessor_Role, &fRoleParams);
|
||
|
fModuleState.isGlobalLocked = false;
|
||
|
|
||
|
if (fModuleState.globalLockRequested) // call this request back locked
|
||
|
return this->CallLocked();
|
||
|
|
||
|
// If this module has requested an event, return and wait for the event to transpire
|
||
|
if (fModuleState.eventRequested)
|
||
|
{
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return fModuleState.idleTime; // If the module has requested idle time...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fCurrentModule = 0;
|
||
|
fState = kSendingResponse;
|
||
|
}
|
||
|
|
||
|
case kSendingResponse:
|
||
|
{
|
||
|
// Sending the RTSP response consists of making sure the
|
||
|
// RTSP request output buffer is completely flushed to the socket.
|
||
|
Assert(fRequest != NULL);
|
||
|
|
||
|
// If x-dynamic-rate header is sent with a value of 1, send OPTIONS request
|
||
|
if ((fRequest->GetMethod() == qtssSetupMethod) && (fRequest->GetStatus() == qtssSuccessOK)
|
||
|
&& (fRequest->GetDynamicRateState() == 1) && fRoundTripTimeCalculation)
|
||
|
{
|
||
|
this->SaveOutputStream();
|
||
|
this->ResetOutputStream();
|
||
|
this->SendOptionsRequest();
|
||
|
}
|
||
|
|
||
|
if (fSentOptionsRequest && (fRequest->GetMethod() == qtssIllegalMethod))
|
||
|
{
|
||
|
this->ResetOutputStream();
|
||
|
this->RevertOutputStream();
|
||
|
fSentOptionsRequest = false;
|
||
|
}
|
||
|
|
||
|
err = fOutputStream.Flush();
|
||
|
|
||
|
if (err == EAGAIN)
|
||
|
{
|
||
|
// If we get this error, we are currently flow-controlled and should
|
||
|
// wait for the socket to become writeable again
|
||
|
fSocket.RequestEvent(EV_WR);
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return 0;
|
||
|
}
|
||
|
else if (err != QTSS_NoErr)
|
||
|
{
|
||
|
// Any other error means that the client has disconnected, right?
|
||
|
Assert(!this->IsLiveSession());
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fState = kCleaningUp;
|
||
|
}
|
||
|
|
||
|
case kCleaningUp:
|
||
|
{
|
||
|
// Cleaning up consists of making sure we've read all the incoming Request Body
|
||
|
// data off of the socket
|
||
|
if (this->GetRemainingReqBodyLen() > 0)
|
||
|
{
|
||
|
err = this->DumpRequestData();
|
||
|
|
||
|
if (err == EAGAIN)
|
||
|
{
|
||
|
fInputSocketP->RequestEvent(EV_RE);
|
||
|
this->ForceSameThread(); // We are holding mutexes, so we need to force
|
||
|
// the same thread to be used for next Run()
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we've gotten here, we've flushed all the data. Cleanup,
|
||
|
// and wait for our next request!
|
||
|
this->CleanupRequest();
|
||
|
fState = kReadingRequest;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Make absolutely sure there are no resources being occupied by the session
|
||
|
// at this point.
|
||
|
this->CleanupRequest();
|
||
|
|
||
|
// Only delete if it is ok to delete!
|
||
|
if (fObjectHolders == 0)
|
||
|
return -1;
|
||
|
|
||
|
// If we are here because of a timeout, but we can't delete because someone
|
||
|
// is holding onto a reference to this session, just reschedule the timeout.
|
||
|
//
|
||
|
// At this point, however, the session is DEAD.
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
Bool16 RTSPSession::ParseProxyTunnelHTTP()
|
||
|
{
|
||
|
/*
|
||
|
if it's an HTTP request
|
||
|
parse the interesing parts from the request
|
||
|
|
||
|
- check for GET or POST, set fHTTPMethod
|
||
|
- checck for HTTP protocol, set fWasHTTPRequest
|
||
|
- check for SessionID header, set fProxySessionID char array
|
||
|
- check for accept "application/x-rtsp-tunnelled.
|
||
|
|
||
|
*/
|
||
|
|
||
|
Bool16 isHTTPRequest = false;
|
||
|
StrPtrLen *splRequest;
|
||
|
|
||
|
HTTP_VTRACE( "ParseProxyTunnelHTTP\n" )
|
||
|
splRequest = fInputStream.GetRequestBuffer();
|
||
|
|
||
|
|
||
|
fFoundValidAccept = true;
|
||
|
|
||
|
Assert( splRequest );
|
||
|
|
||
|
if ( splRequest )
|
||
|
{
|
||
|
|
||
|
fHTTPMethod = kHTTPMethodUnknown;
|
||
|
|
||
|
#if __RTSP_HTTP_DEBUG__
|
||
|
{
|
||
|
char buff[1024];
|
||
|
|
||
|
memcpy( buff, splRequest->Ptr, splRequest->Len );
|
||
|
|
||
|
buff[ splRequest->Len] = 0;
|
||
|
|
||
|
HTTP_VTRACE( buff )
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
StrPtrLen theParsedData;
|
||
|
StringParser parser(splRequest);
|
||
|
|
||
|
parser.ConsumeWord(&theParsedData);
|
||
|
|
||
|
HTTP_VTRACE( "request method: \n" )
|
||
|
HTTP_VTRACE_SPL( &theParsedData )
|
||
|
|
||
|
// does first line begin with POST or GET?
|
||
|
if (theParsedData.EqualIgnoreCase("post", 4 ))
|
||
|
{
|
||
|
fHTTPMethod = kHTTPMethodPost;
|
||
|
}
|
||
|
else if (theParsedData.EqualIgnoreCase("get", 3 ))
|
||
|
{
|
||
|
|
||
|
fHTTPMethod = kHTTPMethodGet;
|
||
|
}
|
||
|
|
||
|
if ( fHTTPMethod != kHTTPMethodUnknown )
|
||
|
{
|
||
|
HTTP_VTRACE( "IsAHTTPProxyTunnelPostRequest found POST or GET\n" )
|
||
|
parser.ConsumeWhitespace(); // skip over ws past method
|
||
|
|
||
|
parser.ConsumeUntilWhitespace( &theParsedData ); // theParsedData now contains the URL and CGI params ( we don't need yet );
|
||
|
|
||
|
parser.ConsumeWhitespace(); // skip over ws past url
|
||
|
|
||
|
parser.ConsumeWord(&theParsedData); // now should contain "HTTP"
|
||
|
|
||
|
HTTP_VTRACE( "should be HTTP/1.* next: \n" )
|
||
|
HTTP_VTRACE_SPL( &theParsedData )
|
||
|
|
||
|
// DMS - why use NumEqualIgnoreCase? Wouldn't EqualIgnoreCase do the trick here?
|
||
|
if (theParsedData.NumEqualIgnoreCase("http", 4 ))
|
||
|
{ HTTP_TRACE( "ParseProxyTunnelHTTP found HTTP\n" )
|
||
|
fWasHTTPRequest = true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( fWasHTTPRequest )
|
||
|
{
|
||
|
// it's HTTP and one of the methods we like....
|
||
|
// now, find the Session ID and Accept headers
|
||
|
const char* kSessionHeaderName = "X-SessionCookie:";
|
||
|
const int kSessionHeaderNameLen = ::strlen(kSessionHeaderName);
|
||
|
const char* kAcceptHeaderName = "Accept:";
|
||
|
const int kAcceptHeaderNameLen = ::strlen(kAcceptHeaderName);
|
||
|
//const char* kAcceptData = "application/x-rtsp-tunnelled";
|
||
|
//const int kAcceptDataLen = ::strlen(kAcceptData);
|
||
|
|
||
|
while ( parser.GetDataRemaining() > 0 )
|
||
|
{
|
||
|
parser.GetThruEOL( &theParsedData ); // we don't need this, but there is not a ComsumeThru...
|
||
|
|
||
|
parser.ConsumeUntilWhitespace( &theParsedData ); // theParsedData now contains the URL and CGI params ( we don't need yet );
|
||
|
|
||
|
if ( theParsedData.EqualIgnoreCase( kSessionHeaderName, kSessionHeaderNameLen ) )
|
||
|
{
|
||
|
// we got a weener!
|
||
|
if ( parser.GetDataRemaining() > 0 )
|
||
|
parser.ConsumeWhitespace();
|
||
|
|
||
|
if ( parser.GetDataRemaining() > 0 )
|
||
|
{
|
||
|
StrPtrLen sessionID;
|
||
|
|
||
|
parser.ConsumeUntil( &sessionID, StringParser::sEOLMask );
|
||
|
|
||
|
// cache the ID so we can use it to remove ourselves from the map
|
||
|
if ( sessionID.Len < QTSS_MAX_SESSION_ID_LENGTH )
|
||
|
{
|
||
|
::memcpy( fProxySessionID, sessionID.Ptr, sessionID.Len );
|
||
|
fProxySessionID[sessionID.Len] = 0;
|
||
|
fProxySessionIDPtr.Set( fProxySessionID, ::strlen(fProxySessionID) );
|
||
|
HTTP_VTRACE_ONE( "found session id: %s\n", fProxySessionID )
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( theParsedData.EqualIgnoreCase( kAcceptHeaderName, kAcceptHeaderNameLen ) )
|
||
|
{
|
||
|
StrPtrLen hTTPAcceptHeader;
|
||
|
|
||
|
// we got another weener!
|
||
|
if ( parser.GetDataRemaining() > 0 )
|
||
|
parser.ConsumeWhitespace();
|
||
|
|
||
|
if ( parser.GetDataRemaining() > 0 )
|
||
|
{
|
||
|
parser.ConsumeUntil( &hTTPAcceptHeader, StringParser::sEOLMask );
|
||
|
|
||
|
#if __RTSP_HTTP_DEBUG__
|
||
|
{
|
||
|
char buff[1024];
|
||
|
|
||
|
memcpy( buff, hTTPAcceptHeader.Ptr, hTTPAcceptHeader.Len );
|
||
|
|
||
|
buff[ hTTPAcceptHeader.Len] = 0;
|
||
|
|
||
|
HTTP_VTRACE_ONE( "client will accept: %s\n", buff )
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// we really don't need to check thisif ( theParsedData.EqualIgnoreCase( kAcceptData, kAcceptDataLen ) )
|
||
|
{ fFoundValidAccept = true;
|
||
|
|
||
|
HTTP_VTRACE( "found valid accept\n" )
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// we found all that we were looking for
|
||
|
if ( fFoundValidAccept && *fProxySessionID && fWasHTTPRequest )
|
||
|
isHTTPRequest = true;
|
||
|
|
||
|
return isHTTPRequest;
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
|
||
|
"pre" filter the request looking for the HHTP Proxy
|
||
|
tunnel HTTP requests, merge the 2 sessions
|
||
|
into one, let the donor Session die.
|
||
|
|
||
|
|
||
|
*/
|
||
|
|
||
|
QTSS_Error RTSPSession::PreFilterForHTTPProxyTunnel()
|
||
|
{
|
||
|
// returns true if it's an HTTP request that can tunnel
|
||
|
if (!this->ParseProxyTunnelHTTP())
|
||
|
return QTSS_NoErr;
|
||
|
|
||
|
// This is an RTSP / HTTP session, so decrement the total RTSP sessions
|
||
|
// and increment the total HTTP sessions
|
||
|
Assert(fSessionType == qtssRTSPSession);
|
||
|
QTSServerInterface::GetServer()->SwapFromRTSPToHTTP();
|
||
|
|
||
|
// Setup our ProxyTunnel OSRefTable Ref
|
||
|
Assert( fProxySessionIDPtr.Len > 0 );
|
||
|
fProxyRef.Set(fProxySessionIDPtr, this);
|
||
|
|
||
|
// We have to set this here, because IF we are able to put ourselves in the map,
|
||
|
// the GET may arrive immediately after, and the GET checks this state.
|
||
|
fState = kWaitingToBindHTTPTunnel;
|
||
|
QTSS_RTSPSessionType theOtherSessionType = qtssRTSPSession;
|
||
|
|
||
|
if ( fHTTPMethod == kHTTPMethodPost )
|
||
|
{
|
||
|
HTTP_TRACE( "RTSPSession is a POST request.\n" )
|
||
|
fSessionType = qtssRTSPHTTPInputSession;
|
||
|
theOtherSessionType = qtssRTSPHTTPSession;
|
||
|
}
|
||
|
else if ( fHTTPMethod == kHTTPMethodGet )
|
||
|
{
|
||
|
HTTP_TRACE( "RTSPSession is a GET request.\n" )
|
||
|
// we're session O (outptut) the POST half is session 1 ( input )
|
||
|
fSessionType = qtssRTSPHTTPSession;
|
||
|
theOtherSessionType = qtssRTSPHTTPInputSession;
|
||
|
|
||
|
Bool16 showServerInfo = QTSServerInterface::GetServer()->GetPrefs()->GetRTSPServerInfoEnabled();
|
||
|
if (fDoReportHTTPConnectionAddress )
|
||
|
{ // contruct a 200 OK header with an "x-server-ip-address" header
|
||
|
|
||
|
char responseHeaderBuf[kMaxHTTPResponseLen];
|
||
|
char localIPAddrBuf[20] = { 0 };
|
||
|
StrPtrLen localIPAddr(localIPAddrBuf, 19);
|
||
|
|
||
|
// get a copy of the local IP address from the dictionary
|
||
|
this->GetValue(qtssRTSPSesLocalAddrStr, 0, localIPAddr.Ptr, &localIPAddr.Len);
|
||
|
Assert( localIPAddr.Len < sizeof( localIPAddrBuf ) );
|
||
|
localIPAddrBuf[localIPAddr.Len] = 0;
|
||
|
|
||
|
char *headerFieldPtr = "";
|
||
|
if(showServerInfo)
|
||
|
{
|
||
|
headerFieldPtr = QTSServerInterface::GetServerHeader().Ptr;
|
||
|
qtss_sprintf( responseHeaderBuf, sHTTPResponseFormatStr, "X-server-ip-address: ", localIPAddrBuf, "\r\n", headerFieldPtr );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qtss_sprintf( responseHeaderBuf, sHTTPNoServerResponseFormatStr, "X-server-ip-address: ", localIPAddrBuf, "\r\n", headerFieldPtr);
|
||
|
|
||
|
}
|
||
|
Assert(::strlen(responseHeaderBuf) < kMaxHTTPResponseLen);
|
||
|
fOutputStream.Put(responseHeaderBuf);
|
||
|
|
||
|
|
||
|
}
|
||
|
else // use the premade stopck version
|
||
|
{ if (showServerInfo)
|
||
|
fOutputStream.Put(sHTTPResponseHeaderPtr); // 200 OK just means we connected...
|
||
|
else
|
||
|
fOutputStream.Put(sHTTPResponseNoServerHeaderPtr); // 200 OK just means we connected...
|
||
|
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
Assert(0);
|
||
|
|
||
|
// This function attempts to register our Ref into the map. If there is another
|
||
|
// session with a matching magic number, it resolves it and returns that Ref.
|
||
|
// If it returns NULL, something bad has happened, and we should just kill the session.
|
||
|
OSRef* rtspSessionRef = this->RegisterRTSPSessionIntoHTTPProxyTunnelMap(theOtherSessionType);
|
||
|
|
||
|
// Something went wrong (usually we get here because there is a session with this magic
|
||
|
// number, and that session is currently doing something
|
||
|
if (rtspSessionRef == NULL)
|
||
|
{
|
||
|
HTTP_TRACE("RegisterRTSPSessionIntoHTTPProxyTunnelMap returned NULL. Abort.\n");
|
||
|
return QTSS_RequestFailed;
|
||
|
}
|
||
|
|
||
|
// We registered ourselves into the map (we are the first half), so wait for our other half
|
||
|
if (rtspSessionRef == &fProxyRef)
|
||
|
{
|
||
|
HTTP_TRACE("Registered this session into map. Waiting to bind\n");
|
||
|
return QTSS_NoErr;
|
||
|
}
|
||
|
|
||
|
OSRefReleaser theRefReleaser(sHTTPProxyTunnelMap, rtspSessionRef); // auto release this ref
|
||
|
RTSPSession* theOtherSession = (RTSPSession*)theRefReleaser.GetRef()->GetObject();
|
||
|
|
||
|
// We must lock down this session, for we (may) be manipulating its socket & input
|
||
|
// stream, and therefore it cannot be in the process of reading data or processing a request.
|
||
|
// If it is, well, safest thing to do is probably just deny this attempt to bind.
|
||
|
if (!theOtherSession->fReadMutex.TryLock())
|
||
|
{
|
||
|
HTTP_TRACE("Found another session in map, but couldn't grab fReadMutex. Abort.\n");
|
||
|
return QTSS_RequestFailed;
|
||
|
}
|
||
|
|
||
|
if (fHTTPMethod == kHTTPMethodPost)
|
||
|
{
|
||
|
// take the input session's socket. This also grabs the other session's input stream
|
||
|
theOtherSession->SnarfInputSocket(this);
|
||
|
|
||
|
// Attempt to bind to this GET connection
|
||
|
// this will reset our state on success.
|
||
|
HTTP_TRACE_ONE( "RTSPSession POST snarfed a donor session successfuly (%s).\n", fProxySessionID )
|
||
|
fState = kSocketHasBeenBoundIntoHTTPTunnel;
|
||
|
theOtherSession->fState = kReadingRequest;
|
||
|
theOtherSession->Signal(Task::kReadEvent);
|
||
|
}
|
||
|
else if (fHTTPMethod == kHTTPMethodGet)
|
||
|
{
|
||
|
Assert( theOtherSession->fState == kWaitingToBindHTTPTunnel );
|
||
|
HTTP_TRACE_ONE( "RTSPSession GET snarfed a donor session successfuly (%s).\n", fProxySessionID )
|
||
|
|
||
|
// take the input session's socket. This also grabs the other session's input stream
|
||
|
this->SnarfInputSocket(theOtherSession);
|
||
|
|
||
|
// we assume the donor's place in the map.
|
||
|
sHTTPProxyTunnelMap->Swap( &fProxyRef );
|
||
|
|
||
|
// the 1/2 connections are bound
|
||
|
// the output Session state goes back to reading a request, this time an RTSP request
|
||
|
// the socket donor Session(rtspSessionInput) state goes to kSocketHasBeenBoundIntoHTTPTunnel to die
|
||
|
fState = kReadingRequest;
|
||
|
theOtherSession->fState = kSocketHasBeenBoundIntoHTTPTunnel;
|
||
|
theOtherSession->Signal(Task::kKillEvent);
|
||
|
}
|
||
|
|
||
|
theOtherSession->fReadMutex.Unlock();
|
||
|
return QTSS_NoErr;
|
||
|
}
|
||
|
|
||
|
OSRef* RTSPSession::RegisterRTSPSessionIntoHTTPProxyTunnelMap(QTSS_RTSPSessionType inSessionType)
|
||
|
{
|
||
|
// This function attempts to register the current session's fProxyRef into the map, and
|
||
|
// 1) returns the current session's fProxyRef if registration was successful
|
||
|
// 2) returns another session's fProxyRef if it has the same magic number and is the right sessionType
|
||
|
// 3) returns NULL if there is a session with the same magic # but it couldn't be resolved.
|
||
|
|
||
|
OSMutexLocker locker(sHTTPProxyTunnelMap->GetMutex());
|
||
|
OSRef* theRef = sHTTPProxyTunnelMap->RegisterOrResolve(&fProxyRef);
|
||
|
if (theRef == NULL)
|
||
|
return &fProxyRef;
|
||
|
|
||
|
RTSPSession* rtspSession = (RTSPSession*)theRef->GetObject();
|
||
|
|
||
|
// we can be the only user of the object right now
|
||
|
Assert(theRef->GetRefCount() > 0);
|
||
|
if (theRef->GetRefCount() > 1 || rtspSession->fSessionType != inSessionType)
|
||
|
{
|
||
|
sHTTPProxyTunnelMap->Release(theRef);
|
||
|
theRef = NULL;
|
||
|
}
|
||
|
return theRef;
|
||
|
}
|
||
|
|
||
|
void RTSPSession::CheckAuthentication() {
|
||
|
|
||
|
QTSSUserProfile* profile = fRequest->GetUserProfile();
|
||
|
StrPtrLen* userPassword = profile->GetValue(qtssUserPassword);
|
||
|
QTSS_AuthScheme scheme = fRequest->GetAuthScheme();
|
||
|
Bool16 authenticated = true;
|
||
|
|
||
|
// Check if authorization information returned by the client is for the scheme that the server sent the challenge
|
||
|
if(scheme != (fRTPSession->GetAuthScheme())) {
|
||
|
authenticated = false;
|
||
|
}
|
||
|
else if(scheme == qtssAuthBasic) {
|
||
|
// For basic authentication, the authentication module returns the crypt of the password,
|
||
|
// so compare crypt of qtssRTSPReqUserPassword and the text in qtssUserPassword
|
||
|
StrPtrLen* reqPassword = fRequest->GetValue(qtssRTSPReqUserPassword);
|
||
|
char* userPasswdStr = userPassword->GetAsCString(); // memory allocated
|
||
|
char* reqPasswdStr = reqPassword->GetAsCString(); // memory allocated
|
||
|
|
||
|
if(userPassword->Len == 0)
|
||
|
{
|
||
|
authenticated = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef __Win32__
|
||
|
// The password is md5 encoded for win32
|
||
|
char md5EncodeResult[120];
|
||
|
// no memory is allocated in this function call
|
||
|
MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult, sizeof(md5EncodeResult));
|
||
|
if(::strcmp(userPasswdStr, md5EncodeResult) != 0)
|
||
|
authenticated = false;
|
||
|
#else
|
||
|
if(::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr, userPasswdStr)) != 0)
|
||
|
authenticated = false;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
delete [] userPasswdStr; // deleting allocated memory
|
||
|
userPasswdStr = NULL;
|
||
|
delete [] reqPasswdStr;
|
||
|
reqPasswdStr = NULL; // deleting allocated memory
|
||
|
}
|
||
|
else if(scheme == qtssAuthDigest) { // For digest authentication, md5 digest comparison
|
||
|
// The text returned by the authentication module in qtssUserPassword is MD5 hash of (username:realm:password)
|
||
|
|
||
|
UInt32 qop = fRequest->GetAuthQop();
|
||
|
StrPtrLen* opaque = fRequest->GetAuthOpaque();
|
||
|
StrPtrLen* sessionOpaque = fRTPSession->GetAuthOpaque();
|
||
|
UInt32 sessionQop = fRTPSession->GetAuthQop();
|
||
|
|
||
|
do {
|
||
|
// The Opaque string should be the same as that sent by the server
|
||
|
// The QoP should be the same as that sent by the server
|
||
|
if((sessionOpaque->Len != 0) && !(sessionOpaque->Equal(*opaque))) {
|
||
|
authenticated = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(sessionQop != qop) {
|
||
|
authenticated = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// All these are just pointers to existing memory... no new memory is allocated
|
||
|
//StrPtrLen* userName = profile->GetValue(qtssUserName);
|
||
|
//StrPtrLen* realm = fRequest->GetAuthRealm();
|
||
|
StrPtrLen* nonce = fRequest->GetAuthNonce();
|
||
|
StrPtrLen method = RTSPProtocol::GetMethodString(fRequest->GetMethod());
|
||
|
StrPtrLen* digestUri = fRequest->GetAuthUri();
|
||
|
StrPtrLen* responseDigest = fRequest->GetAuthResponse();
|
||
|
//StrPtrLen hA1;
|
||
|
StrPtrLen requestDigest;
|
||
|
StrPtrLen emptyStr;
|
||
|
|
||
|
StrPtrLen* cNonce = fRequest->GetAuthCNonce();
|
||
|
// Since qtssUserPassword = md5(username:realm:password)
|
||
|
// Just convert the 16 bit hash to a 32 bit char array to get HA1
|
||
|
//HashToString((unsigned char *)userPassword->Ptr, &hA1);
|
||
|
//CalcHA1(&sAuthAlgorithm, userName, realm, userPassword, nonce, cNonce, &hA1);
|
||
|
|
||
|
|
||
|
// For qop="auth"
|
||
|
if(qop == RTSPSessionInterface::kAuthQop) {
|
||
|
StrPtrLen* nonceCount = fRequest->GetAuthNonceCount();
|
||
|
UInt32 ncValue = 0;
|
||
|
|
||
|
// Convert nounce count (which is a string of 8 hex digits) into a UInt32
|
||
|
if (nonceCount && nonceCount->Len)
|
||
|
{
|
||
|
// Convert nounce count (which is a string of 8 hex digits) into a UInt32
|
||
|
UInt32 bufSize = sizeof(ncValue);
|
||
|
StrPtrLenDel tempString(nonceCount->GetAsCString());
|
||
|
tempString.ToUpper();
|
||
|
QTSSDataConverter::ConvertCHexStringToBytes(tempString.Ptr,
|
||
|
&ncValue,
|
||
|
&bufSize);
|
||
|
ncValue = ntohl(ncValue);
|
||
|
|
||
|
}
|
||
|
// nonce count must not be repeated by the client
|
||
|
if(ncValue < (fRTPSession->GetAuthNonceCount())) {
|
||
|
authenticated = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// allocates memory for requestDigest.Ptr
|
||
|
CalcRequestDigest(userPassword, nonce, nonceCount, cNonce, &sAuthQop, &method, digestUri, &emptyStr, &requestDigest);
|
||
|
// If they are equal, check if nonce used by client is same as the one sent by the server
|
||
|
|
||
|
} // For No qop
|
||
|
else if(qop == RTSPSessionInterface::kNoQop)
|
||
|
{
|
||
|
// allocates memory for requestDigest->Ptr
|
||
|
CalcRequestDigest(userPassword, nonce, &emptyStr, &emptyStr, &emptyStr, &method, digestUri, &emptyStr, &requestDigest);
|
||
|
}
|
||
|
|
||
|
// hA1 is allocated memory
|
||
|
//delete [] hA1.Ptr;
|
||
|
|
||
|
if(responseDigest->Equal(requestDigest)) {
|
||
|
if(!(nonce->Equal(*(fRTPSession->GetAuthNonce()))))
|
||
|
fRequest->SetStale(true);
|
||
|
authenticated = true;
|
||
|
}
|
||
|
else {
|
||
|
authenticated = false;
|
||
|
}
|
||
|
|
||
|
// delete the memory allocated in CalcRequestDigest above
|
||
|
delete [] requestDigest.Ptr;
|
||
|
requestDigest.Len = 0;
|
||
|
|
||
|
} while(false);
|
||
|
}
|
||
|
|
||
|
// If authenticaton failed, set qtssUserName in the qtssRTSPReqUserProfile attribute
|
||
|
// to NULL and clear out the password and any groups that have been set.
|
||
|
if (!fRequest->GetAuthHandled())
|
||
|
{
|
||
|
if((!authenticated) || (authenticated && (fRequest->GetStale()))) {
|
||
|
debug_printf("erasing username from profile\n");
|
||
|
(void)profile->SetValue(qtssUserName, 0, sEmptyStr.Ptr, sEmptyStr.Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)profile->SetValue(qtssUserPassword, 0, sEmptyStr.Ptr, sEmptyStr.Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)profile->SetNumValues(qtssUserGroups, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bool16 RTSPSession::ParseOptionsResponse()
|
||
|
{
|
||
|
StringParser parser(fRequest->GetValue(qtssRTSPReqFullRequest));
|
||
|
Assert(fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != NULL);
|
||
|
static StrPtrLen sRTSPStr("RTSP", 4);
|
||
|
StrPtrLen theProtocol;
|
||
|
parser.ConsumeLength(&theProtocol, 4);
|
||
|
|
||
|
return (theProtocol.Equal(sRTSPStr));
|
||
|
}
|
||
|
|
||
|
void RTSPSession::Process3GPPData()
|
||
|
{
|
||
|
if (fRTPSession == NULL)
|
||
|
return;
|
||
|
|
||
|
|
||
|
RTSPRequest3GPP* request3GPPInfoPtr = fRequest->GetRequest3GPPInfo();
|
||
|
|
||
|
if (!request3GPPInfoPtr || !request3GPPInfoPtr->Is3GPP())
|
||
|
return;
|
||
|
|
||
|
fRTPSession->SetIs3GPPSession(true);
|
||
|
|
||
|
if (request3GPPInfoPtr->HasLinkChar())
|
||
|
{
|
||
|
StrPtrLen dataStr;
|
||
|
LinkCharDataFields linkCharFieldsParser;
|
||
|
if(0 == fRequest->GetHeaderDictionary()->GetValuePtr(qtss3GPPLinkCharHeader, 0, (void**) &dataStr.Ptr, &dataStr.Len, true))
|
||
|
{
|
||
|
linkCharFieldsParser.SetData(&dataStr);
|
||
|
fRTPSession->Set3GPPLinkCharData(&linkCharFieldsParser);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (request3GPPInfoPtr->HasRateAdaptation())
|
||
|
{
|
||
|
StrPtrLen dataStr;
|
||
|
RateAdapationStreamDataFields fieldsParser;
|
||
|
|
||
|
for (int numValues = 0; request3GPPInfoPtr->GetValuePtr(qtss3GPPRequestRateAdaptationStreamData, numValues, (void**) &dataStr.Ptr, &dataStr.Len) == QTSS_NoErr; numValues++)
|
||
|
{
|
||
|
//dataStr.PrintStr("RTSPSession::Process3GPPData qtss3GPPRequestRateAdaptationStreamData=[","]\n");
|
||
|
fieldsParser.SetData(&dataStr);
|
||
|
fRTPSession->Set3GPPRateAdaptionData(&fieldsParser);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void RTSPSession::SetupRequest()
|
||
|
{
|
||
|
//
|
||
|
// First parse the request
|
||
|
QTSS_Error theErr = fRequest->Parse();
|
||
|
if (theErr != QTSS_NoErr)
|
||
|
return;
|
||
|
|
||
|
// let's also refresh RTP session timeout so that it's kept alive in sync with the RTSP session.
|
||
|
//
|
||
|
// Attempt to find the RTP session for this request.
|
||
|
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
|
||
|
theErr = this->FindRTPSession(theMap);
|
||
|
|
||
|
if (fRTPSession != NULL)
|
||
|
{
|
||
|
OSMutexLocker locker(fRTPSession->GetMutex());
|
||
|
|
||
|
fRTPSession->RefreshTimeout();
|
||
|
UInt32 headerBits = fRequest->GetBandwidthHeaderBits();
|
||
|
if (headerBits != 0)
|
||
|
(void)fRTPSession->SetValue(qtssCliSessLastRTSPBandwidth, (UInt32) 0,&headerBits,sizeof(headerBits), QTSSDictionary::kDontObeyReadOnly );
|
||
|
|
||
|
|
||
|
}
|
||
|
QTSS_RTSPStatusCode statusCode = qtssSuccessOK;
|
||
|
char *body = NULL;
|
||
|
UInt32 bodySizeBytes = 0;
|
||
|
|
||
|
|
||
|
//
|
||
|
// If this is an OPTIONS request, don't even bother letting modules see it. Just
|
||
|
// send a standard OPTIONS response, and be done.
|
||
|
if (fRequest->GetMethod() == qtssOptionsMethod)
|
||
|
{
|
||
|
|
||
|
|
||
|
|
||
|
this->Process3GPPData();
|
||
|
|
||
|
StrPtrLen* cSeqPtr = fRequest->GetHeaderDictionary()->GetValue(qtssCSeqHeader);
|
||
|
if (cSeqPtr == NULL || cSeqPtr->Len == 0)
|
||
|
{
|
||
|
statusCode = qtssClientBadRequest;
|
||
|
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
|
||
|
fRequest->SendHeader();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fRequest->AppendHeader(qtssPublicHeader, QTSServerInterface::GetPublicHeader());
|
||
|
|
||
|
// DJM PROTOTYPE
|
||
|
StrPtrLen* requirePtr = fRequest->GetHeaderDictionary()->GetValue(qtssRequireHeader);
|
||
|
if ( requirePtr && requirePtr->EqualIgnoreCase(RTSPProtocol::GetHeaderString(qtssXRandomDataSizeHeader)) )
|
||
|
{
|
||
|
body = (char*) RTSPSessionInterface::sOptionsRequestBody;
|
||
|
bodySizeBytes = fRequest->GetRandomDataSize();
|
||
|
Assert( bodySizeBytes <= sizeof(RTSPSessionInterface::sOptionsRequestBody) );
|
||
|
fRequest->AppendHeader(qtssContentTypeHeader, &sContentType);
|
||
|
fRequest->AppendContentLength(bodySizeBytes);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
fRequest->SendHeader();
|
||
|
|
||
|
// now write the body if there is one
|
||
|
if (bodySizeBytes > 0 && body != NULL)
|
||
|
fRequest->Write(body, bodySizeBytes, NULL, 0);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If this is a SET_PARAMETER request, don't let modules see it.
|
||
|
if (fRequest->GetMethod() == qtssSetParameterMethod)
|
||
|
{
|
||
|
|
||
|
|
||
|
// Check that it has the CSeq header
|
||
|
StrPtrLen* cSeqPtr = fRequest->GetHeaderDictionary()->GetValue(qtssCSeqHeader);
|
||
|
if (cSeqPtr == NULL || cSeqPtr->Len == 0) // keep session
|
||
|
{
|
||
|
statusCode = qtssClientBadRequest;
|
||
|
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
|
||
|
fRequest->SendHeader();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
// If the RTPSession doesn't exist, return error
|
||
|
if (fRTPSession == NULL) // keep session
|
||
|
{
|
||
|
statusCode = qtssClientSessionNotFound;
|
||
|
fRequest->SetValue(qtssRTSPReqStatusCode, 0, &statusCode, sizeof(statusCode));
|
||
|
fRequest->SendHeader();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// refresh RTP session timeout so that it's kept alive in sync with the RTSP session.
|
||
|
if (fRequest->GetLateToleranceInSec() != -1)
|
||
|
{
|
||
|
OSMutexLocker locker(fRTPSession->GetMutex());
|
||
|
fRTPSession->SetStreamThinningParams(fRequest->GetLateToleranceInSec());
|
||
|
fRequest->SendHeader();
|
||
|
return;
|
||
|
}
|
||
|
// let modules handle it if they want it.
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If this is a DESCRIBE request, make sure there is no SessionID. This is not allowed,
|
||
|
// and may screw up modules if we let them see this request.
|
||
|
if (fRequest->GetMethod() == qtssDescribeMethod)
|
||
|
{
|
||
|
if (fRequest->GetHeaderDictionary()->GetValue(qtssSessionHeader)->Len > 0)
|
||
|
{
|
||
|
(void)QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientHeaderFieldNotValid, qtssMsgNoSesIDOnDescribe);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// If we don't have an RTP session yet, create one...
|
||
|
if (fRTPSession == NULL)
|
||
|
{
|
||
|
theErr = this->CreateNewRTPSession(theMap);
|
||
|
if (theErr != QTSS_NoErr)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
OSMutexLocker locker(fRTPSession->GetMutex());
|
||
|
UInt32 headerBits = fRequest->GetBandwidthHeaderBits();
|
||
|
if (headerBits != 0)
|
||
|
(void)fRTPSession->SetValue(qtssCliSessLastRTSPBandwidth, 0,&headerBits,sizeof(headerBits), QTSSDictionary::kDontObeyReadOnly );
|
||
|
|
||
|
// If it's a play request and the late tolerance is sent in the request use this value
|
||
|
if ((fRequest->GetMethod() == qtssPlayMethod) && (fRequest->GetLateToleranceInSec() != -1))
|
||
|
fRTPSession->SetStreamThinningParams(fRequest->GetLateToleranceInSec());
|
||
|
|
||
|
//
|
||
|
// Check to see if this is a "ping" PLAY request (a PLAY request while already
|
||
|
// playing with no Range header). If so, just send back a 200 OK response and do nothing.
|
||
|
// No need to go to modules to do this, because this is an RFC documented behavior
|
||
|
if ((fRequest->GetMethod() == qtssPlayMethod) && (fRTPSession->GetSessionState() == qtssPlayingState)
|
||
|
&& (fRequest->GetStartTime() == -1) && (fRequest->GetStopTime() == -1))
|
||
|
{
|
||
|
fRequest->SendHeader();
|
||
|
fRTPSession->RefreshTimeout();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
Assert(fRTPSession != NULL); // At this point, we must have one!
|
||
|
fRoleParams.rtspRequestParams.inClientSession = fRTPSession;
|
||
|
|
||
|
// Setup Authorization params;
|
||
|
fRequest->ParseAuthHeader();
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void RTSPSession::CleanupRequest()
|
||
|
{
|
||
|
if (fRTPSession != NULL)
|
||
|
{
|
||
|
// Release the ref.
|
||
|
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
|
||
|
theMap->Release(fRTPSession->GetRef());
|
||
|
|
||
|
// NULL out any references to this RTP session
|
||
|
fRTPSession = NULL;
|
||
|
fRoleParams.rtspRequestParams.inClientSession = NULL;
|
||
|
}
|
||
|
|
||
|
if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used.
|
||
|
{ fLastRTPSessionID[0] = 0;
|
||
|
fLastRTPSessionIDPtr.Set( fLastRTPSessionID, 0 );
|
||
|
}
|
||
|
|
||
|
if (fRequest != NULL)
|
||
|
{
|
||
|
// Check to see if a filter module has replaced the request. If so, delete
|
||
|
// their request now.
|
||
|
if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr)
|
||
|
delete [] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr;
|
||
|
|
||
|
// NULL out any references to the current request
|
||
|
delete fRequest;
|
||
|
fRequest = NULL;
|
||
|
fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
|
||
|
fRoleParams.rtspRequestParams.inRTSPHeaders = NULL;
|
||
|
}
|
||
|
|
||
|
fSessionMutex.Unlock();
|
||
|
fReadMutex.Unlock();
|
||
|
|
||
|
// Clear out our last value for request body length before moving onto the next request
|
||
|
this->SetRequestBodyLength(-1);
|
||
|
}
|
||
|
|
||
|
QTSS_Error RTSPSession::FindRTPSession(OSRefTable* inRefTable)
|
||
|
{
|
||
|
// This function attempts to locate the appropriate RTP session for this RTSP
|
||
|
// Request. It uses an RTSP session ID as a key to finding the correct RTP session,
|
||
|
// and it looks for this session ID in two places. First, the RTSP session ID header
|
||
|
// in the RTSP request, and if there isn't one there, in the RTSP session object itself.
|
||
|
|
||
|
StrPtrLen* theSessionID = fRequest->GetHeaderDictionary()->GetValue(qtssSessionHeader);
|
||
|
if (theSessionID != NULL && theSessionID->Len > 0)
|
||
|
{
|
||
|
OSRef* theRef = inRefTable->Resolve(theSessionID);
|
||
|
|
||
|
if (theRef != NULL)
|
||
|
fRTPSession = (RTPSession*)theRef->GetObject();
|
||
|
|
||
|
}
|
||
|
|
||
|
// If there wasn't a session ID in the headers, look for one in the RTSP session itself
|
||
|
if ( (theSessionID == NULL || theSessionID->Len == 0) && fLastRTPSessionIDPtr.Len > 0)
|
||
|
{
|
||
|
OSRef* theRef = inRefTable->Resolve(&fLastRTPSessionIDPtr);
|
||
|
if (theRef != NULL)
|
||
|
fRTPSession = (RTPSession*)theRef->GetObject();
|
||
|
}
|
||
|
|
||
|
return QTSS_NoErr;
|
||
|
}
|
||
|
|
||
|
QTSS_Error RTSPSession::CreateNewRTPSession(OSRefTable* inRefTable)
|
||
|
{
|
||
|
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
|
||
|
|
||
|
// This is a brand spanking new session. At this point, we need to create
|
||
|
// a new RTPSession object that will represent this session until it completes.
|
||
|
// Then, we need to pass the session onto one of the modules
|
||
|
|
||
|
// First of all, ask the server if it's ok to add a new session
|
||
|
QTSS_Error theErr = this->IsOkToAddNewRTPSession();
|
||
|
if (theErr != QTSS_NoErr)
|
||
|
return theErr;
|
||
|
|
||
|
// Create the RTPSession object
|
||
|
Assert(fRTPSession == NULL);
|
||
|
fRTPSession = NEW RTPSession();
|
||
|
|
||
|
{
|
||
|
//
|
||
|
// Lock the RTP session down so that it won't delete itself in the
|
||
|
// unusual event there is a timeout while we are doing this.
|
||
|
OSMutexLocker locker(fRTPSession->GetSessionMutex());
|
||
|
|
||
|
// Because this is a new RTP session, setup some dictionary attributes
|
||
|
// pertaining to RTSP that only need to be set once
|
||
|
this->SetupClientSessionAttrs();
|
||
|
|
||
|
// So, generate a session ID for this session
|
||
|
QTSS_Error activationError = EPERM;
|
||
|
while (activationError == EPERM)
|
||
|
{
|
||
|
fLastRTPSessionIDPtr.Len = this->GenerateNewSessionID(fLastRTPSessionID);
|
||
|
|
||
|
//ok, some module has bound this session, we can activate it.
|
||
|
//At this point, we may find out that this new session ID is a duplicate.
|
||
|
//If that's the case, we'll simply retry until we get a unique ID
|
||
|
activationError = fRTPSession->Activate(fLastRTPSessionID);
|
||
|
}
|
||
|
Assert(activationError == QTSS_NoErr);
|
||
|
}
|
||
|
Assert(fLastRTPSessionIDPtr.Ptr == &fLastRTPSessionID[0]);
|
||
|
|
||
|
// Activate adds this session into the RTP session map. We need to therefore
|
||
|
// make sure to resolve the RTPSession object out of the map, even though
|
||
|
// we don't actually need to pointer.
|
||
|
OSRef* theRef = inRefTable->Resolve(&fLastRTPSessionIDPtr);
|
||
|
Assert(theRef != NULL);
|
||
|
|
||
|
return QTSS_NoErr;
|
||
|
}
|
||
|
|
||
|
void RTSPSession::SetupClientSessionAttrs()
|
||
|
{
|
||
|
// get and pass presentation url
|
||
|
StrPtrLen* theValue = fRequest->GetValue(qtssRTSPReqURI);
|
||
|
Assert(theValue != NULL);
|
||
|
(void)fRTPSession->SetValue(qtssCliSesPresentationURL, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// get and pass full request url
|
||
|
theValue = fRequest->GetValue(qtssRTSPReqAbsoluteURL);
|
||
|
Assert(theValue != NULL);
|
||
|
(void)fRTPSession->SetValue(qtssCliSesFullURL, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// get and pass request host name
|
||
|
theValue = fRequest->GetHeaderDictionary()->GetValue(qtssHostHeader);
|
||
|
Assert(theValue != NULL);
|
||
|
(void)fRTPSession->SetValue(qtssCliSesHostName, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// get and pass user agent header
|
||
|
theValue = fRequest->GetHeaderDictionary()->GetValue(qtssUserAgentHeader);
|
||
|
Assert(theValue != NULL);
|
||
|
(void)fRTPSession->SetValue(qtssCliSesFirstUserAgent, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
|
||
|
// get and pass CGI params
|
||
|
if (fRequest->GetMethod() == qtssDescribeMethod)
|
||
|
{
|
||
|
theValue = fRequest->GetValue(qtssRTSPReqQueryString);
|
||
|
Assert(theValue != NULL);
|
||
|
(void)fRTPSession->SetValue(qtssCliSesReqQueryString, 0, theValue->Ptr, theValue->Len, QTSSDictionary::kDontObeyReadOnly);
|
||
|
}
|
||
|
|
||
|
// store RTSP session info in the RTPSession.
|
||
|
StrPtrLen tempStr;
|
||
|
tempStr.Len = 0;
|
||
|
(void) this->GetValuePtr(qtssRTSPSesRemoteAddrStr, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
|
||
|
Assert(tempStr.Len != 0);
|
||
|
(void) fRTPSession->SetValue(qtssCliRTSPSessRemoteAddrStr, (UInt32) 0, tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
|
||
|
tempStr.Len = 0;
|
||
|
(void) this->GetValuePtr(qtssRTSPSesLocalDNS, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
|
||
|
Assert(tempStr.Len != 0);
|
||
|
(void) fRTPSession->SetValue(qtssCliRTSPSessLocalDNS, (UInt32) 0, (void **)tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
|
||
|
tempStr.Len = 0;
|
||
|
(void) this->GetValuePtr(qtssRTSPSesLocalAddrStr, (UInt32) 0, (void **) &tempStr.Ptr, &tempStr.Len);
|
||
|
Assert(tempStr.Len != 0);
|
||
|
(void) fRTPSession->SetValue(qtssCliRTSPSessLocalAddrStr, (UInt32) 0, tempStr.Ptr, tempStr.Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
}
|
||
|
|
||
|
UInt32 RTSPSession::GenerateNewSessionID(char* ioBuffer)
|
||
|
{
|
||
|
//RANDOM NUMBER GENERATOR
|
||
|
|
||
|
//We want to make our session IDs as random as possible, so use a bunch of
|
||
|
//current server statistics to generate a random SInt64.
|
||
|
|
||
|
//Generate the random number in two UInt32 parts. The first UInt32 uses
|
||
|
//statistics out of a random RTP session.
|
||
|
SInt64 theMicroseconds = OS::Microseconds();
|
||
|
::srand((unsigned int)theMicroseconds);
|
||
|
UInt32 theFirstRandom = ::rand();
|
||
|
|
||
|
QTSServerInterface* theServer = QTSServerInterface::GetServer();
|
||
|
|
||
|
{
|
||
|
OSMutexLocker locker(theServer->GetRTPSessionMap()->GetMutex());
|
||
|
OSRefHashTable* theHashTable = theServer->GetRTPSessionMap()->GetHashTable();
|
||
|
if (theHashTable->GetNumEntries() > 0)
|
||
|
{
|
||
|
theFirstRandom %= theHashTable->GetNumEntries();
|
||
|
theFirstRandom >>= 2;
|
||
|
|
||
|
OSRefHashTableIter theIter(theHashTable);
|
||
|
//Iterate through the session map, finding a random session
|
||
|
for (UInt32 theCount = 0; theCount < theFirstRandom; theIter.Next(), theCount++)
|
||
|
Assert(!theIter.IsDone());
|
||
|
|
||
|
RTPSession* theSession = (RTPSession*)theIter.GetCurrent()->GetObject();
|
||
|
theFirstRandom += theSession->GetPacketsSent();
|
||
|
theFirstRandom += (UInt32)theSession->GetSessionCreateTime();
|
||
|
theFirstRandom += (UInt32)theSession->GetPlayTime();
|
||
|
theFirstRandom += (UInt32)theSession->GetBytesSent();
|
||
|
}
|
||
|
}
|
||
|
//Generate the first half of the random number
|
||
|
::srand((unsigned int)theFirstRandom);
|
||
|
theFirstRandom = ::rand();
|
||
|
|
||
|
//Now generate the second half
|
||
|
UInt32 theSecondRandom = ::rand();
|
||
|
theSecondRandom += theServer->GetCurBandwidthInBits();
|
||
|
theSecondRandom += theServer->GetAvgBandwidthInBits();
|
||
|
theSecondRandom += theServer->GetRTPPacketsPerSec();
|
||
|
theSecondRandom += (UInt32)theServer->GetTotalRTPBytes();
|
||
|
theSecondRandom += theServer->GetTotalRTPSessions();
|
||
|
|
||
|
::srand((unsigned int)theSecondRandom);
|
||
|
theSecondRandom = ::rand();
|
||
|
|
||
|
SInt64 theSessionID = (SInt64)theFirstRandom;
|
||
|
theSessionID <<= 32;
|
||
|
theSessionID += (SInt64)theSecondRandom;
|
||
|
qtss_sprintf(ioBuffer, "%"_64BITARG_"d", theSessionID);
|
||
|
Assert(::strlen(ioBuffer) < QTSS_MAX_SESSION_ID_LENGTH);
|
||
|
return ::strlen(ioBuffer);
|
||
|
}
|
||
|
|
||
|
Bool16 RTSPSession::OverMaxConnections(UInt32 buffer)
|
||
|
{
|
||
|
QTSServerInterface* theServer = QTSServerInterface::GetServer();
|
||
|
SInt32 maxConns = theServer->GetPrefs()->GetMaxConnections();
|
||
|
Bool16 overLimit = false;
|
||
|
|
||
|
if (maxConns > -1) // limit connections
|
||
|
{
|
||
|
UInt32 maxConnections = (UInt32) maxConns + buffer;
|
||
|
if ( (theServer->GetNumRTPSessions() > maxConnections)
|
||
|
||
|
||
|
( theServer->GetNumRTSPSessions() + theServer->GetNumRTSPHTTPSessions() > maxConnections )
|
||
|
)
|
||
|
{
|
||
|
overLimit = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return overLimit;
|
||
|
|
||
|
}
|
||
|
|
||
|
QTSS_Error RTSPSession::IsOkToAddNewRTPSession()
|
||
|
{
|
||
|
QTSServerInterface* theServer = QTSServerInterface::GetServer();
|
||
|
QTSS_ServerState theServerState = theServer->GetServerState();
|
||
|
|
||
|
//we may want to deny this connection for a couple of different reasons
|
||
|
//if the server is refusing new connections
|
||
|
if ((theServerState == qtssRefusingConnectionsState) ||
|
||
|
(theServerState == qtssIdleState) ||
|
||
|
(theServerState == qtssFatalErrorState) ||
|
||
|
(theServerState == qtssShuttingDownState))
|
||
|
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssServerUnavailable,
|
||
|
qtssMsgRefusingConnections);
|
||
|
|
||
|
//if the max connection limit has been hit
|
||
|
if ( this->OverMaxConnections(0))
|
||
|
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientNotEnoughBandwidth,
|
||
|
qtssMsgTooManyClients);
|
||
|
|
||
|
//if the max bandwidth limit has been hit
|
||
|
SInt32 maxKBits = theServer->GetPrefs()->GetMaxKBitsBandwidth();
|
||
|
if ( (maxKBits > -1) && (theServer->GetCurBandwidthInBits() >= ((UInt32)maxKBits*1024)) )
|
||
|
return QTSSModuleUtils::SendErrorResponse(fRequest, qtssClientNotEnoughBandwidth,
|
||
|
qtssMsgTooMuchThruput);
|
||
|
|
||
|
//if the server is too loaded down (CPU too high, whatever)
|
||
|
// --INSERT WORKING CODE HERE--
|
||
|
|
||
|
return QTSS_NoErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
void RTSPSession::SaveRequestAuthorizationParams(RTSPRequest *theRTSPRequest)
|
||
|
{
|
||
|
// Set the RTSP session's copy of the user name
|
||
|
StrPtrLen* tempPtr = theRTSPRequest->GetValue(qtssRTSPReqUserName);
|
||
|
Assert(tempPtr != NULL);
|
||
|
if (tempPtr)
|
||
|
{ (void)this->SetValue(qtssRTSPSesLastUserName, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)fRTPSession->SetValue(qtssCliRTSPSesUserName, (UInt32) 0, tempPtr->Ptr, tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
}
|
||
|
|
||
|
// Same thing... user password
|
||
|
tempPtr = theRTSPRequest->GetValue(qtssRTSPReqUserPassword);
|
||
|
Assert(tempPtr != NULL);
|
||
|
if (tempPtr)
|
||
|
{ (void)this->SetValue(qtssRTSPSesLastUserPassword, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)fRTPSession->SetValue(qtssCliRTSPSesUserPassword, (UInt32) 0, tempPtr->Ptr, tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
}
|
||
|
|
||
|
tempPtr = theRTSPRequest->GetValue(qtssRTSPReqURLRealm);
|
||
|
if (tempPtr)
|
||
|
{
|
||
|
if (tempPtr->Len == 0)
|
||
|
{
|
||
|
// If there is no realm explicitly specified in the request, then let's get the default out of the prefs
|
||
|
OSCharArrayDeleter theDefaultRealm(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm());
|
||
|
char *realm = theDefaultRealm.GetObject();
|
||
|
UInt32 len = ::strlen(theDefaultRealm.GetObject());
|
||
|
(void)this->SetValue(qtssRTSPSesLastURLRealm, 0, realm, len,QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)fRTPSession->SetValue(qtssCliRTSPSesURLRealm, (UInt32) 0,realm,len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
(void)this->SetValue(qtssRTSPSesLastURLRealm, 0, tempPtr->Ptr, tempPtr->Len,QTSSDictionary::kDontObeyReadOnly);
|
||
|
(void)fRTPSession->SetValue(qtssCliRTSPSesURLRealm, (UInt32) 0,tempPtr->Ptr,tempPtr->Len, QTSSDictionary::kDontObeyReadOnly );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QTSS_Error RTSPSession::DumpRequestData()
|
||
|
{
|
||
|
char theDumpBuffer[2048];
|
||
|
|
||
|
QTSS_Error theErr = QTSS_NoErr;
|
||
|
while (theErr == QTSS_NoErr)
|
||
|
theErr = this->Read(theDumpBuffer, 2048, NULL);
|
||
|
|
||
|
return theErr;
|
||
|
}
|
||
|
|
||
|
void RTSPSession::HandleIncomingDataPacket()
|
||
|
{
|
||
|
|
||
|
// Attempt to find the RTP session for this request.
|
||
|
UInt8 packetChannel = (UInt8)fInputStream.GetRequestBuffer()->Ptr[1];
|
||
|
StrPtrLen* theSessionID = this->GetSessionIDForChannelNum(packetChannel);
|
||
|
|
||
|
if (theSessionID == NULL)
|
||
|
{
|
||
|
Assert(0);
|
||
|
theSessionID = &fLastRTPSessionIDPtr;
|
||
|
|
||
|
}
|
||
|
|
||
|
OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
|
||
|
OSRef* theRef = theMap->Resolve(theSessionID);
|
||
|
|
||
|
if (theRef != NULL)
|
||
|
fRTPSession = (RTPSession*)theRef->GetObject();
|
||
|
|
||
|
if (fRTPSession == NULL)
|
||
|
return;
|
||
|
|
||
|
StrPtrLen packetWithoutHeaders(fInputStream.GetRequestBuffer()->Ptr + 4, fInputStream.GetRequestBuffer()->Len - 4);
|
||
|
|
||
|
OSMutexLocker locker(fRTPSession->GetMutex());
|
||
|
fRTPSession->RefreshTimeout();
|
||
|
RTPStream* theStream = fRTPSession->FindRTPStreamForChannelNum(packetChannel);
|
||
|
theStream->ProcessIncomingInterleavedData(packetChannel, this, &packetWithoutHeaders);
|
||
|
|
||
|
//
|
||
|
// We currently don't support async notifications from within this role
|
||
|
QTSS_RoleParams packetParams;
|
||
|
packetParams.rtspIncomingDataParams.inRTSPSession = this;
|
||
|
|
||
|
packetParams.rtspIncomingDataParams.inClientSession = fRTPSession;
|
||
|
packetParams.rtspIncomingDataParams.inPacketData = fInputStream.GetRequestBuffer()->Ptr;
|
||
|
packetParams.rtspIncomingDataParams.inPacketLen = fInputStream.GetRequestBuffer()->Len;
|
||
|
|
||
|
UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPIncomingDataRole);
|
||
|
for (; fCurrentModule < numModules; fCurrentModule++)
|
||
|
{
|
||
|
QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kRTSPIncomingDataRole, fCurrentModule);
|
||
|
(void)theModule->CallDispatch(QTSS_RTSPIncomingData_Role, &packetParams);
|
||
|
}
|
||
|
fCurrentModule = 0;
|
||
|
}
|