/*
 *
 * @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;
}
