420 lines
13 KiB
C++
420 lines
13 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: QTSSRefMovieModule.cpp
|
|
|
|
Contains: Implements a module to create RTSP text ref movies
|
|
|
|
|
|
*/
|
|
|
|
#include "QTSSRefMovieModule.h"
|
|
#include "OS.h"
|
|
#include "OSMemory.h"
|
|
#include "OSArrayObjectDeleter.h"
|
|
#include "StringParser.h"
|
|
#include "StrPtrLen.h"
|
|
#include "OSMemory.h"
|
|
#include "OSHeaders.h"
|
|
#include "ev.h"
|
|
#include "QTFile.h"
|
|
#include "QTSSModuleUtils.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
//------------------------------------------------------------------------
|
|
// STATIC DATA
|
|
//------------------------------------------------------------------------
|
|
|
|
static QTSS_ModulePrefsObject sPrefs = NULL;
|
|
static QTSS_PrefsObject sServerPrefs = NULL;
|
|
static QTSS_ServerObject sServer = NULL;
|
|
|
|
// HTTP reply header
|
|
static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: QTSS/4.0\r\n"
|
|
"Connection: Close\r\nContent-Type: video/quicktime\r\n";
|
|
|
|
static Bool16 sRefMovieXferEnabled = true;
|
|
static Bool16 sDefaultRefMovieXferEnabled = true;
|
|
static UInt32 sServerIPAddr = 0x74000001; // 127.0.0.1
|
|
static UInt16 sRTSPReplyPort = 0;
|
|
static UInt16 sDefaultRTSPReplyPort = 0;
|
|
|
|
//------------------------------------------------------------------------
|
|
// FUNCTION PROTOTYPES
|
|
//------------------------------------------------------------------------
|
|
|
|
QTSS_Error QTSSRefMovieModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
|
|
static QTSS_Error Register(QTSS_Register_Params* inParams);
|
|
static QTSS_Error Initialize(QTSS_Initialize_Params* inParams);
|
|
static QTSS_Error RereadPrefs();
|
|
static QTSS_Error Filter(QTSS_Filter_Params* inParams);
|
|
|
|
static void url_strcpy(char* dest, const char* src);
|
|
static QTSS_Error SendTheResponse(QTSS_RTSPSessionObject theSession, QTSS_StreamRef stream, StrPtrLen& movie);
|
|
static Bool16 FileExists(StrPtrLen& path, StrPtrLen& movie);
|
|
static Bool16 IsHTTPGet(StrPtrLen& theRequest);
|
|
static Bool16 IsTunneledRTSP(StrPtrLen& theRequest);
|
|
static Bool16 IsAdminURL(StrPtrLen& theUrl);
|
|
static Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen);
|
|
static Bool16 IsHomeDirURL(StrPtrLen& theUrl);
|
|
|
|
//------------------------------------------------------------------------
|
|
// MODULE FUNCTIONS IMPLEMENTATION
|
|
//------------------------------------------------------------------------
|
|
|
|
QTSS_Error QTSSRefMovieModule_Main(void* inPrivateArgs)
|
|
{
|
|
return _stublibrary_main(inPrivateArgs, QTSSRefMovieModuleDispatch);
|
|
}
|
|
|
|
// Dispatch this module's role call back.
|
|
QTSS_Error QTSSRefMovieModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
|
|
{
|
|
switch (inRole)
|
|
{
|
|
case QTSS_Register_Role:
|
|
return Register(&inParams->regParams);
|
|
case QTSS_Initialize_Role:
|
|
return Initialize(&inParams->initParams);
|
|
case QTSS_RereadPrefs_Role:
|
|
return RereadPrefs();
|
|
case QTSS_RTSPFilter_Role:
|
|
return Filter(&inParams->rtspFilterParams);
|
|
}
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// Handle the QTSS_Register role call back.
|
|
QTSS_Error Register(QTSS_Register_Params* inParams)
|
|
{
|
|
// Do role & attribute setup
|
|
(void)QTSS_AddRole(QTSS_Initialize_Role);
|
|
(void)QTSS_AddRole(QTSS_RereadPrefs_Role);
|
|
(void)QTSS_AddRole(QTSS_RTSPFilter_Role);
|
|
|
|
// Tell the server our name!
|
|
static char* sModuleName = "QTSSRefMovieModule";
|
|
::strcpy(inParams->outModuleName, sModuleName);
|
|
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// Handle the QTSS_Initialize role call back.
|
|
QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
|
|
{
|
|
QTSS_Error err = QTSS_NoErr;
|
|
UInt32 ulen = sizeof(sServerIPAddr);
|
|
|
|
// Setup module utils
|
|
QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
|
|
|
|
// Get prefs object
|
|
sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule);
|
|
sServerPrefs = inParams->inPrefs;
|
|
sServer = inParams->inServer;
|
|
|
|
// Get the Server's IP address for later use.
|
|
err = QTSS_GetValue(sServer, qtssSvrDefaultIPAddr, 0, &sServerIPAddr, &ulen);
|
|
|
|
err = RereadPrefs();
|
|
|
|
return err;
|
|
}
|
|
|
|
// Handle the QTSS_RereadPrefs_Role role call back.
|
|
QTSS_Error RereadPrefs()
|
|
{
|
|
QTSSModuleUtils::GetAttribute(sPrefs, "refmovie_xfer_enabled", qtssAttrDataTypeBool16,
|
|
&sRefMovieXferEnabled, &sDefaultRefMovieXferEnabled, sizeof(sRefMovieXferEnabled));
|
|
QTSSModuleUtils::GetAttribute(sPrefs, "refmovie_rtsp_port", qtssAttrDataTypeUInt16,
|
|
&sRTSPReplyPort, &sDefaultRTSPReplyPort, sizeof(sRTSPReplyPort));
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// url_strcpy - works like strcpy except that it handles URL escape
|
|
// conversions as it copies the string.
|
|
void url_strcpy(char* dest, const char* src)
|
|
{
|
|
int c1, c2;
|
|
while(*src)
|
|
{
|
|
if (*src == '%')
|
|
{
|
|
src++;
|
|
c1 = *src++;
|
|
if (c1 >= '0' && c1 <= '9')
|
|
c1 -= '0';
|
|
else if (c1 >= 'A' && c1 <= 'F')
|
|
c1 -= 'A' + 10;
|
|
else if (c1 >= 'a' && c1 <= 'f')
|
|
c1 -= 'a' + 10;
|
|
else
|
|
c1 = 0;
|
|
c2 = *src;
|
|
if (c2 >= '0' && c2 <= '9')
|
|
c2 -= '0';
|
|
else if (c2 >= 'A' && c2 <= 'F')
|
|
c2 -= 'A' + 10;
|
|
else if (c2 >= 'a' && c2 <= 'f')
|
|
c2 -= 'a' + 10;
|
|
else
|
|
c2 = 32;
|
|
*dest = (c1 * 16) + c2;
|
|
}
|
|
else
|
|
{
|
|
*dest = *src;
|
|
}
|
|
dest++;
|
|
src++;
|
|
}
|
|
*dest = '\0';
|
|
}
|
|
|
|
// This sends the HTTP response to the server that contains the RTSPtext Ref movie
|
|
QTSS_Error SendTheResponse(QTSS_RTSPSessionObject theSession, QTSS_StreamRef stream, StrPtrLen& movie)
|
|
{
|
|
QTSS_Error err = QTSS_NoErr;
|
|
char theMovieFile[512];
|
|
theMovieFile[sizeof(theMovieFile) -1] = 0;
|
|
|
|
char tmp[600];
|
|
tmp[sizeof(tmp) -1] = 0;
|
|
|
|
char tmp2[80];
|
|
tmp2[sizeof(tmp2) -1] = 0;
|
|
|
|
UInt8 x1, x2, x3, x4;
|
|
|
|
// send the HTTP reply header to the client
|
|
err= QTSS_Write(stream, sResponseHeader, ::strlen(sResponseHeader), NULL, qtssWriteFlagsBufferData);
|
|
if (err != QTSS_NoErr)
|
|
return QTSS_NoErr;
|
|
|
|
UInt32 ip4address = 0;
|
|
UInt32 len = sizeof(ip4address);
|
|
err = QTSS_GetValue(theSession, qtssRTSPSesLocalAddr, 0, &ip4address, &len);
|
|
|
|
// Format the server IP address for building the RTSP address in the reply.
|
|
x1 = (UInt8)((ip4address >> 24) & 0xff);
|
|
x2 = (UInt8)((ip4address >> 16) & 0xff);
|
|
x3 = (UInt8)((ip4address >> 8) & 0xff);
|
|
x4 = (UInt8)((ip4address) & 0xff);
|
|
|
|
if (movie.Len > sizeof(theMovieFile) -1 )
|
|
movie.Len = sizeof(theMovieFile) -1;
|
|
|
|
::memcpy(theMovieFile, movie.Ptr, movie.Len);
|
|
theMovieFile[movie.Len] = '\0';
|
|
|
|
UInt16 port = sRTSPReplyPort;
|
|
if (0 == port)
|
|
{
|
|
len = sizeof(port);
|
|
err = QTSS_GetValue(theSession, qtssRTSPSesLocalPort, 0, &port, &len);
|
|
}
|
|
|
|
// construct the RTSP address reply string for the client.
|
|
qtss_snprintf(tmp,sizeof(tmp) -1, "rtsptext\r\nrtsp://%d.%d.%d.%d:%d%s\r\n", x1,x2,x3,x4, port, theMovieFile);
|
|
|
|
// send the 'Content-Length:' part of the HTTP reply
|
|
qtss_snprintf(tmp2, sizeof(tmp2) -1, "Content-Length: %d\r\n\r\n", (int) ::strlen(tmp));
|
|
err = QTSS_Write(stream, tmp2, ::strlen(tmp2), NULL, qtssWriteFlagsBufferData);
|
|
if (err != QTSS_NoErr)
|
|
return QTSS_NoErr;
|
|
|
|
// send the formatted RTSP reference part of the reply
|
|
err = QTSS_Write(stream, tmp, ::strlen(tmp), NULL, qtssWriteFlagsBufferData);
|
|
if (err != QTSS_NoErr)
|
|
return QTSS_NoErr;
|
|
|
|
// flush the pending write to the client.
|
|
err = QTSS_Flush(stream);
|
|
return err;
|
|
}
|
|
|
|
// This determines if the specified movie file
|
|
// exists at the designated path.
|
|
Bool16 FileExists(StrPtrLen& path, StrPtrLen& movie)
|
|
{
|
|
struct stat sb;
|
|
char fullpath[1024];
|
|
|
|
// if the movie path ends in a '/' then there is no movie file to be found.
|
|
// (This is probably a user error in typing the URL.)
|
|
if(movie.Ptr[movie.Len-1] == '/')
|
|
return false;
|
|
|
|
// copy path to our local buffer
|
|
::memcpy(fullpath, path.Ptr, path.Len);
|
|
|
|
// remove any URL escape characters when we contruct the full path.
|
|
::url_strcpy(fullpath+path.Len, (char*) movie.Ptr);
|
|
fullpath[path.Len+movie.Len] = '\0';
|
|
// check for file existance with the POSIX stat() function.
|
|
if (::stat(fullpath, &sb) != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
// This determines if an incoming request is an HTTP GET
|
|
// request.
|
|
Bool16 IsHTTPGet(StrPtrLen& theRequest)
|
|
{
|
|
StrPtrLen token = theRequest;
|
|
token.Len = 3;
|
|
return token.EqualIgnoreCase(StrPtrLen("GET"));
|
|
}
|
|
|
|
// This determines if an incoming request is actually
|
|
// an RTSP request tunneled in a HTTP request.
|
|
Bool16 IsTunneledRTSP(StrPtrLen& theRequest)
|
|
{
|
|
if (::strstr((char*)theRequest.Ptr, "x-rtsp-tunneled") != 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// This determines if a URL in an HTTP request is actually
|
|
// a server admin request.
|
|
Bool16 IsAdminURL(StrPtrLen& theUrl)
|
|
{
|
|
StrPtrLen token = theUrl;
|
|
token.Len = 15;
|
|
return token.EqualIgnoreCase(StrPtrLen("/modules/admin/"));
|
|
}
|
|
|
|
|
|
Bool16 IsHomeDirURL(StrPtrLen& theUrl)
|
|
{
|
|
StrPtrLen token = theUrl;
|
|
token.Len = 2;
|
|
return token.EqualIgnoreCase(StrPtrLen("/~"));
|
|
}
|
|
|
|
// Parse out the URL from the HTTP GET line.
|
|
Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen)
|
|
{
|
|
StringParser reqParse(&theRequest);
|
|
StrPtrLen strPtr;
|
|
|
|
::memset(outURL, 0, maxlen);
|
|
reqParse.ConsumeWord(&strPtr);
|
|
|
|
if ( !strPtr.Equal(StrPtrLen("GET")) )
|
|
{
|
|
return false;
|
|
}
|
|
reqParse.ConsumeWhitespace();
|
|
reqParse.ConsumeUntilWhitespace(&strPtr);
|
|
if (strPtr.Len == 0)
|
|
return false;
|
|
else if ((UInt16)strPtr.Len > maxlen-1)
|
|
strPtr.Len = maxlen-1;
|
|
::memcpy(outURL, strPtr.Ptr, strPtr.Len);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Handle the QTSS_RTSPFilter_Role role call back.
|
|
QTSS_Error Filter(QTSS_Filter_Params* inParams)
|
|
{
|
|
QTSS_Error err = QTSS_NoErr;
|
|
QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest;
|
|
QTSS_RTSPSessionObject theSession = inParams->inRTSPSession;
|
|
char theURL[512];
|
|
|
|
// If this module is disabled do nothing but return.
|
|
if (!sRefMovieXferEnabled)
|
|
return QTSS_NoErr;
|
|
|
|
// Get the full RTSP request from the server's attribute.
|
|
StrPtrLen theFullRequest;
|
|
err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len);
|
|
|
|
if (err != QTSS_NoErr)
|
|
{
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// if this is not an HTTP GET then ignore it.
|
|
if (!IsHTTPGet(theFullRequest))
|
|
return QTSS_NoErr;
|
|
|
|
// If this is a tunneled RTSP request we ignore it.
|
|
if (IsTunneledRTSP(theFullRequest))
|
|
{
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// if we can't parse out the URL then we just ignore this request.
|
|
if (!ParseURL(theFullRequest, theURL, 512))
|
|
{
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
// Make sure that this is not an admin request before
|
|
// we go any further.
|
|
StrPtrLen movie(theURL);
|
|
if (IsAdminURL(movie))
|
|
{
|
|
// The file path in the URL is actually an admin request.
|
|
// Just ignore it and let the admin module handle it.
|
|
return QTSS_NoErr;
|
|
}
|
|
|
|
Bool16 isHomeDir = IsHomeDirURL(movie);
|
|
|
|
// Get the server's movie folder location.
|
|
char* movieFolderString = NULL;
|
|
err = QTSS_GetValueAsString (sServerPrefs, qtssPrefsMovieFolder, 0, &movieFolderString);
|
|
if (err != QTSS_NoErr)
|
|
return QTSS_NoErr;
|
|
|
|
OSCharArrayDeleter movieFolder(movieFolderString);
|
|
StrPtrLen theMovieFolder(movieFolderString);
|
|
|
|
if (!isHomeDir && !FileExists(theMovieFolder, movie))
|
|
{
|
|
// we couldn't find a file at the specified location
|
|
// so we will ignore this HTTP request and let some other module
|
|
// deal with the issue.
|
|
return QTSS_NoErr;
|
|
}
|
|
else
|
|
{
|
|
// Eureka!!! We found a file at the specified location.
|
|
// We assume that it is a valid movie file and we send
|
|
// the client an RTSP text reference movie in the HTTP reply.
|
|
err = SendTheResponse(theSession,theRequest, movie);
|
|
}
|
|
|
|
return QTSS_NoErr;
|
|
}
|