/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: smlffpln.cpp,v 1.3.8.1 2004/07/09 01:57:20 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#define INITGUID

#include <string.h>
#include <ctype.h>
#include <time.h>

#include "smlffpln.ver"

#include "hxtypes.h"
#include "hxcom.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxformt.h"
#include "hxengin.h"
#include "hxplugn.h"
#include "hxpends.h"
#include "hxasm.h"
#include "hxprefs.h"
#include "hxvsrc.h"  /*IHXFileViewSource*/
#include "chxfgbuf.h" /*CHXFragmentedBuffer */
#include "hxmon.h"
#include "hxerror.h"

#include "hxstack.h"
#include "hxslist.h"
#include "hxstring.h"
#include "chxpckts.h"
#include "hxurl.h"
#include "hxver.h"
#include "verutil.h"
#include "perplex.h"

#include "xmlreslt.h"
#include "smlpkt.h"
#include "smlffpln.h"
#if defined(HELIX_FEATURE_VIEWSOURCE)
#include "escsmil.h" /* CEscapeSMIL */
#include "shadvsrc.h" /* CShadowViewSource */
#endif /* #if defined(HELIX_FEATURE_VIEWSOURCE) */

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static const char HX_THIS_FILE[] = __FILE__;
#endif

#ifdef _AIX
#include "dllpath.h"
ENABLE_MULTILOAD_DLLACCESS_PATHS(Smlffplin);
#endif

//#define DUMPFILEFORMATOUTPUT 1

static const UINT32 FileChunkSize = 10000;	//XXXBAB adjust
static const UINT32 MaxPacketSize = 1000;

#define XML_BAD_ATTRIBUTE_NAME		"SMIL Parse Error: Error in attribute name"
#define XML_BAD_TAG_NAME		"SMIL Parse Error: Bad Tag Name"
#define XML_MISSING_ATTRIBUTE_VALUE	"SMIL Parse Error: Attribute value is missing"
#define XML_NO_CLOSE_QUOTE		"SMIL Parse Error: No closing quote"
#define XML_MISSING_SPACE		"SMIL Parse Error: Missing space"


/****************************************************************************
 * 
 *  Function:
 * 
 *	HXCreateInstance()
 * 
 *  Purpose:
 * 
 *	Function implemented by all plugin DLL's to create an instance of 
 *	any of the objects supported by the DLL. This method is similar to 
 *	Window's CoCreateInstance() in its purpose, except that it only 
 *	creates objects from this plugin DLL.
 *
 *	NOTE: Aggregation is never used. Therefore and outer unknown is
 *	not passed to this function, and you do not need to code for this
 *	situation.
 * 
 */
STDAPI ENTRYPOINT(HXCreateInstance)
(
    IUnknown**  /*OUT*/	ppIUnknown
)
{
    *ppIUnknown = (IUnknown*)(IHXPlugin*)new CSmilFileFormat();
    if (*ppIUnknown)
    {
	(*ppIUnknown)->AddRef();
	return HXR_OK;
    }
    return HXR_OUTOFMEMORY;
}

/****************************************************************************
 * 
 *  Function:
 * 
 *	CanUnload2()
 * 
 *  Purpose:
 * 
 *	Function implemented by all plugin DLL's if it returns HXR_OK 
 *	then the pluginhandler can unload the DLL
 *
 */
STDAPI ENTRYPOINT(CanUnload2)(void)
{
    return (CHXBaseCountingObject::ObjectsActive() > 0 ? HXR_FAIL : HXR_OK );
}


const char* const CSmilFileFormat::zm_pDescription    = "RealNetworks SMIL File Format Plugin";
const char* const CSmilFileFormat::zm_pCopyright      = HXVER_COPYRIGHT;
const char* const CSmilFileFormat::zm_pMoreInfoURL    = HXVER_MOREINFO;

const char* const CSmilFileFormat::zm_pFileMimeTypes[]  = {"application/smil", NULL};
// /NOTE: here's what's sent as stream mime types; if there is no default
// namespace, then the file is treated as a SMIL 1.0 document and sent to
// our old renderer.   If the default namespace is unrecognized, then we
// send "application/smil" and let the 2.0 or later SMIL renderer parse the
// namespace in the smil file data and, if it recognizes it (e.g., the SMIL
// renderer is a SMIL 3.0 renderer and the namespace is SMIL 3.0 but this
// file format is of the SMIL 2.0 timeframe), then it plays it.  If the
// renderer doesn't recognize the namespace, it tries to auto upgrade with
// the following as the AU mime-type request: "application/smil.[namespace]"
// and the AU server then looks for a component (maybe not even an RN one)
// that can handle this type of SMIL file.
const char* const CSmilFileFormat::zm_pStreamMimeTypes[]  = 	{
	// /The following are SMIL 1.0 stream mime types that will be handled
	// by SMIL 1.0-exclusive players (e.g., RealPlayer 8):
	"application/rma-driver", // /Beta-1, SMIL 1.0 stream
	"application/vnd.rn-rmadriver", // /SMIL 1.0 strm (no dflt namespace)
	// /The following is SMIL 1.0 Rec default namespace:
	// "http://www.w3.org/TR/REC-smil"
	"application/smil",
	// /The following is SMIL 2.0 Candidate Rec (i.e., pre SMIL 2.0 W3C Rec
	// status) default namespace:
	// "http://www.w3.org/2000/SMIL20/CR/Language"
	// The following is SMIL 2.0 Proposed Rec default namespace:
	// "http://www.w3.org/2001/SMIL20/PR/Language"
	// Both CR and PR use this stream mime type:
	"application/smil",
	// /The following is the SMIL 2.0 Recommendation namespace
	// "http://www.w3.org/2001/SMIL20/Language",
	"application/smil",
	// /We'll use this for any unrecognized SMIL file version (and note:
	// this is probably going to always be the same as the SMIL20 mime
	// type; the renderer will do all the work of deciding whether or not
	// to auto upgrade if it doesn't recognize the default namespace):
	"application/smil",
	NULL};
const char* const CSmilFileFormat::zm_pFileExtensions[] = {"smi", "smil", NULL};
const char* const CSmilFileFormat::zm_pFileOpenNames[]  = {"SMIL File Format (*.smi,*.smil)", NULL};

CSmilFileFormat::CSmilFileFormat()
    :	m_lRefCount(0)
    ,	m_pContext(0)
    ,	m_pFileObject(0)
    ,	m_pFFResponse(0)
    ,	m_bHeaderSent(FALSE)
    ,	m_bChannelInit(FALSE)
    ,	m_bSentFirstChannel(FALSE)
    ,	m_bSourceInit(FALSE)
    ,	m_ulCurrentTime(0)
    ,	m_state(Ready)
    ,	m_pRequest(0)
    ,	m_pCommonClassFactory(0)
    ,	m_ulPacketCount(0)
    ,	m_ulCurrentPacket(0)
    ,	m_ulStreamVersion(0)
    ,	m_ulContentVersion(0)
    ,	m_pCurrentPacketData(NULL)
    ,	m_fileState(InContent)
    ,	m_smilFileVersion(SMILFileVersionSmil10)
    ,	m_pArrayOfPackets(0)
    ,	m_bLogErrors(FALSE)
    ,	m_pStartOfFile(NULL)
{
};

CSmilFileFormat::~CSmilFileFormat()
{
    Close();
}


/************************************************************************
 *  Method:
 *    IHXPlugin::InitPlugin
 *  Purpose:
 *    Initializes the plugin for use. This interface must always be
 *    called before any other method is called. This is primarily needed 
 *    so that the plugin can have access to the context for creation of
 *    IHXBuffers and IMalloc.
 */
STDMETHODIMP CSmilFileFormat::InitPlugin(IUnknown* /*IN*/ pContext)
{
    m_pContext = pContext;
    m_pContext->AddRef();
    m_pContext->QueryInterface(IID_IHXCommonClassFactory,
    	(void**)&m_pCommonClassFactory);
    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetPluginInfo
 *  Purpose:
 *    Returns the basic information about this plugin. Including:
 *
 *    bLoadMultiple	whether or not this plugin DLL can be loaded
 *			multiple times. All File Formats must set
 *			this value to TRUE.
 *    pDescription	which is used in about UIs (can be NULL)
 *    pCopyright	which is used in about UIs (can be NULL)
 *    pMoreInfoURL	which is used in about UIs (can be NULL)
 */
STDMETHODIMP CSmilFileFormat::GetPluginInfo
(
    REF(BOOL)	    /*OUT*/ bLoadMultiple,
    REF(const char*)/*OUT*/ pDescription,
    REF(const char*)/*OUT*/ pCopyright,
    REF(const char*)/*OUT*/ pMoreInfoURL,
    REF(ULONG32)    /*OUT*/ ulVersionNumber
)
{
    bLoadMultiple = TRUE;   // Must be true for file formats.

    pDescription    = (const char*) zm_pDescription;
    pCopyright	    = (const char*) zm_pCopyright;
    pMoreInfoURL    = (const char*) zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetObjFileFormatInfo
 *  Purpose:
 *    If this object is a file format object this method returns
 *    information vital to the instantiation of file format plugins.
 *    If this object is not a file format object, it should return
 *    HXR_UNEXPECTED.
 */
STDMETHODIMP CSmilFileFormat::GetFileFormatInfo
(
    REF(const char**) /*OUT*/ pFileMimeTypes,
    REF(const char**) /*OUT*/ pFileExtensions,
    REF(const char**) /*OUT*/ pFileOpenNames
)
{
    pFileMimeTypes  = (const char**) zm_pFileMimeTypes;
    pFileExtensions = (const char**) zm_pFileExtensions;
    pFileOpenNames  = (const char**) zm_pFileOpenNames;

    return HXR_OK;
}

// *** IUnknown methods ***

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::QueryInterface
//  Purpose:
//	Implement this to export the interfaces supported by your 
//	object.
//
STDMETHODIMP CSmilFileFormat::QueryInterface(REFIID riid, void** ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXPlugin))
    {
	AddRef();
	*ppvObj = (IHXPlugin*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXFileFormatObject))
    {
	AddRef();
	*ppvObj = (IHXFileFormatObject*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXFileResponse))
    {
	AddRef();
	*ppvObj = (IHXFileResponse*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXPendingStatus))
    {
	AddRef();
	*ppvObj = (IHXPendingStatus*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXInterruptSafe))
    {
	AddRef();
	*ppvObj = (IHXInterruptSafe*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXThreadSafeMethods))
    {
	AddRef();
	*ppvObj = (IHXThreadSafeMethods*)this;
	return HXR_OK;
    }
#if defined(HELIX_FEATURE_VIEWSOURCE)
    else if ( IsEqualIID(riid, IID_IHXFileViewSource) )
    {
	CShadowViewSource* pVsrc = new CShadowViewSource(m_pContext, 
	    (IUnknown*)(IHXPlugin*)this);
	if ( pVsrc == NULL )
	{
	    return HXR_FAIL;
	}	
	return pVsrc->QueryInterface(riid, ppvObj);
    }
#endif /* #if defined(HELIX_FEATURE_VIEWSOURCE) */

    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::AddRef
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CSmilFileFormat::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::Release
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CSmilFileFormat::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

// *** IHXFileFormatObject methods ***

STDMETHODIMP CSmilFileFormat::InitFileFormat
(
    IHXRequest*	/*IN*/	pRequest, 
    IHXFormatResponse*	/*IN*/	pFormatResponse,
    IHXFileObject*	/*IN*/  pFileObject
)
{
    HX_RESULT ret = HXR_OK;

    m_pRequest    = pRequest;
    m_pFFResponse = pFormatResponse;
    m_pFileObject = pFileObject;

    m_pRequest->AddRef();
    m_pFFResponse->AddRef();
    m_pFileObject->AddRef();

    // set stream and content versions
    m_ulStreamVersion = HX_ENCODE_PROD_VERSION(STREAM_MAJOR_VER,
					       STREAM_MINOR_VER,
					       STREAM_RELEASE,
					       STREAM_BUILD);
    m_ulContentVersion = HX_ENCODE_PROD_VERSION(CONTENT_MAJOR_VER,
						CONTENT_MINOR_VER,
						CONTENT_RELEASE,
						CONTENT_BUILD);
    
    if ( m_pArrayOfPackets )
    {
	int s = m_pArrayOfPackets->GetSize();
	for ( int i = s; i > 0; --i )
	{
	    PacketData* pckt = (PacketData*)(*m_pArrayOfPackets)[i-1];
	    HX_RELEASE(pckt->pBuffer);
	    HX_DELETE(pckt);
	    (*m_pArrayOfPackets)[i-1] = NULL;
	}
	HX_DELETE(m_pArrayOfPackets);
    }
    m_pArrayOfPackets = new CHXPtrArray;

    if ( m_pCurrentPacketData )
    {
	HX_RELEASE(m_pCurrentPacketData->pBuffer);
	HX_DELETE(m_pCurrentPacketData);
    }

    m_ulCurrentBufferPos = 0;
    m_ulCurrentPacket = 0;

    // This file format is not a container type, so it only supports one
    // stream and therefore one header, but before we do that, we want to
    // make sure the file object is initialized; we can't actually return
    // the header count until the file init is done... (See InitDone).
    m_state = InitPending;

    // Note, we need to pass ourself to the FileObject, because this is its
    // first opportunity to know that we implement the IHXFileResponse
    // interface it will call for completed pending operations
    return m_pFileObject->Init( HX_FILE_READ, this);
}	


STDMETHODIMP CSmilFileFormat::Close()
{
    HX_RELEASE(m_pContext);
    if (m_pFileObject)
    {
	m_pFileObject->Close();
	HX_RELEASE(m_pFileObject);
    }
    if ( m_pArrayOfPackets )
    {
	int s = m_pArrayOfPackets->GetSize();
	for ( int i = s; i > 0; --i )
	{
	    PacketData* pckt = (PacketData*)(*m_pArrayOfPackets)[i-1];
	    HX_RELEASE(pckt->pBuffer);
	    HX_DELETE(pckt);
	    (*m_pArrayOfPackets)[i-1] = NULL;
	    m_pArrayOfPackets->RemoveAt(i-1);
	}
	HX_DELETE(m_pArrayOfPackets);
    }
    if ( m_pCurrentPacketData )
    {
	HX_RELEASE(m_pCurrentPacketData->pBuffer);
	m_pCurrentPacketData->pNumPos = NULL;
	HX_DELETE(m_pCurrentPacketData);
    }
    HX_RELEASE(m_pFFResponse);
    HX_RELEASE(m_pRequest);
    HX_RELEASE(m_pCommonClassFactory);
    HX_RELEASE(m_pStartOfFile);
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileFormatObject::GetFileHeader
//  Purpose:
//	Called by controller to ask the file format for the number of
//	headers in the file. The file format should call the 
//	IHXFileFormatSession::HeaderCountReady() for the IHXFileFormat-
//	Session object that was passed in during initialization, when the
//	header count is available.
//
STDMETHODIMP CSmilFileFormat::GetFileHeader()
{
    // If we are not ready then something has gone wrong
    if (m_state != Ready) return HXR_UNEXPECTED;

    IHXValues* pHeader = 0;
    if (HXR_OK != m_pCommonClassFactory->CreateInstance(CLSID_IHXValues,
						    (void**)&pHeader))
    {
	return HXR_UNEXPECTED;
    }

    pHeader->SetPropertyULONG32("StreamCount", 1);

    m_pFFResponse->FileHeaderReady(HXR_OK, pHeader);

    HX_RELEASE(pHeader);

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileFormatObject::GetStreamHeader
//  Purpose:
//	Called by controller to ask the file format for the header for
//	a particular stream in the file. The file format should call 
//	IHXFileFormatSession::StreamHeaderReady() for IHXFileFormatSession
//	object that was passed in during initialization, when the header
//	is available.
//
STDMETHODIMP CSmilFileFormat::GetStreamHeader(UINT16 unStreamNumber)
{
    // If we are not ready then something has gone wrong
    if (m_state != Ready) return HXR_UNEXPECTED;

    IHXBuffer* pASM = 0;
    char pBook[256]; /* Flawfinder: ignore */

    IHXValues* pHeader = 0;
    IHXBuffer* pBuffer = 0;
    if (HXR_OK != m_pCommonClassFactory->CreateInstance(CLSID_IHXValues,
						    (void**)&pHeader))
    {
	return HXR_UNEXPECTED;
    }

    if (HXR_OK != m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
						    (void**)&pBuffer))
    {
	return HXR_UNEXPECTED;
    }

    BOOL bBeta1Player = ::IsBeta1Player(m_pRequest);

    HX_RESULT pnrver = GetSMILFileVersion();

    if (HXR_OK != pnrver  ||
		SMILFileVersionSmil10 == m_smilFileVersion)
    {
	if(bBeta1Player)
	{
	    pBuffer->Set((const BYTE*)
		   zm_pStreamMimeTypes[SMILFileVersionSmilBeta10],
		   strlen(zm_pStreamMimeTypes[SMILFileVersionSmilBeta10])+1);
	}
	else
	{
	    pBuffer->Set(
		   (const BYTE*)zm_pStreamMimeTypes[SMILFileVersionSmil10],
		   strlen(zm_pStreamMimeTypes[SMILFileVersionSmil10])+1);
	}
    }
    else if (SMILFileVersionSmil10Strict == m_smilFileVersion)
    {
	// /Send strictly-declared SMIL 1.0 streams to the SMIL 2.0 renderer
	pBuffer->Set((const BYTE*)
	       zm_pStreamMimeTypes[SMILFileVersionSmil10Strict],
	       strlen(zm_pStreamMimeTypes[SMILFileVersionSmil10Strict])+1);
    }
    else if (SMILFileVersionSmil20PreRec == m_smilFileVersion)
    {
	pBuffer->Set((const BYTE*)
	       zm_pStreamMimeTypes[SMILFileVersionSmil20PreRec],
	       strlen(zm_pStreamMimeTypes[SMILFileVersionSmil20PreRec])+1);
    }
    else if (SMILFileVersionSmil20 == m_smilFileVersion)
    {
	pBuffer->Set((const BYTE*)
		zm_pStreamMimeTypes[SMILFileVersionSmil20],
		strlen(zm_pStreamMimeTypes[SMILFileVersionSmil20])+1);
    }
    else // /Too high a version number as far as this ff is concerned:
    // /XXXEH: should we use the namespace (if there is one) to determine
    // the mime type and, if there is one, send it and let the player deal
    // with the auto update (if needed)?  Or, should we encourage server
    // (hence smil ff) updating by just not serving the stream?  That's
    // what I'm doing here:
    {
	pBuffer->Set((const BYTE*)
		zm_pStreamMimeTypes[SMILFileVersionUnknown],
		strlen(zm_pStreamMimeTypes[SMILFileVersionUnknown])+1);
    }

    pHeader->SetPropertyCString("MimeType", pBuffer);
    HX_RELEASE(pBuffer);

    pHeader->SetPropertyULONG32("StreamNumber", unStreamNumber);
    //XXXEH- removed 20000 (20sec) duration that BAB put in "to avoid problem
    // in core" in 1998(?).  0 milliseconds seems to work fine now (3/2000):
    pHeader->SetPropertyULONG32("Duration", 0);
    pHeader->SetPropertyULONG32("PreRoll", 1000);   //XXXBAB 'cause Rahul told me to
    pHeader->SetPropertyULONG32("AvgBitRate", 1000);

    pHeader->SetPropertyULONG32("StreamVersion", m_ulStreamVersion);
    pHeader->SetPropertyULONG32("ContentVersion", m_ulContentVersion);

    //
    // set ASM rule book
    //
    sprintf(pBook, "TimestampDelivery=TRUE,priority=10;"); /* Flawfinder: ignore */
    if(HXR_OK == m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
						    (void**)&pASM))
    {
	pASM->Set((BYTE*)pBook, strlen(pBook)+1);
	pHeader->SetPropertyCString("ASMRuleBook", pASM);
	HX_RELEASE(pASM);
    }

    m_bHeaderSent = TRUE;

    m_pFFResponse->StreamHeaderReady(HXR_OK, pHeader);

    HX_RELEASE(pHeader);

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileFormatObject::GetPacket
//  Purpose:
//	Called by controller to ask the file format for the next packet
//	for a particular stream in the file. The file format should call 
//	IHXFileFormatSession::PacketReady() for the IHXFileFormatSession
//	object that was passed in during initialization, when the packet
//	is available.
//
STDMETHODIMP CSmilFileFormat::GetPacket(UINT16 unStreamNumber)
{
    HX_RESULT result = HXR_OK;

    // If we are not ready then something has gone wrong
    if (m_state != Ready) return HXR_UNEXPECTED;

    if (!m_bHeaderSent)
    {
        return HXR_UNEXPECTED;
    }

    IHXPacket* pPacket = NULL;

    result = m_pCommonClassFactory->CreateInstance(CLSID_IHXPacket,
	(void**)&pPacket);
    if( SUCCEEDED(result) )
    {
	if(m_ulCurrentPacket >= m_ulPacketCount)
	{
	    m_pFFResponse->StreamDone(unStreamNumber);
	}
	else
	{
	    PacketData* pckt = (PacketData*)(*m_pArrayOfPackets)[m_ulCurrentPacket++];
	    
	    if ( pckt->pNumPos )
	    {
		char buf[10]; /* Flawfinder: ignore */
		sprintf(buf, "%u", m_ulPacketCount); /* Flawfinder: ignore */
		HX_ASSERT(strlen(buf) < 7);
		strncpy(pckt->pNumPos, buf, strlen(buf)); /* Flawfinder: ignore */
		pckt->pNumPos = NULL;
	    }
#ifdef DUMPFILEFORMATOUTPUT
	    FILE* stream = fopen( "C:\\PacketData.smil", "a" );
	    fprintf( stream, "%s\n", (const char*)pckt->pBuffer->GetBuffer());
	    fclose( stream );
#endif
	    pPacket->Set(pckt->pBuffer, 0, unStreamNumber, HX_ASM_SWITCH_ON, 0);
	    m_pFFResponse->PacketReady(HXR_OK, pPacket);
	}
    }
    HX_RELEASE(pPacket);
    return result;
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileFormatObject::Seek
//  Purpose:
//	Called by controller to tell the file format to seek to the 
//	nearest packet to the requested offset. The file format should 
//	call IHXFileFormatSession::SeekDone() for the IHXFileFormat-
//	Session object that was passed in during initialization, when 
//	the seek has completed.
//
STDMETHODIMP CSmilFileFormat::Seek(ULONG32 ulOffset)
{
    m_pFFResponse->SeekDone(HXR_OK);
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//    IHXFileResponse::InitDone
//  Purpose:
//    Notification interface provided by users of the IHXFileObject
//    interface. This method is called by the IHXFileObject when the
//    initialization of the file is complete, and the Mime type is
//    available for the request file. If the URL is not valid for the
//    file system, the status HXR_FAILED should be returned,
//    with a mime type of NULL. If the URL is valid but the mime type
//    is unknown, then the status HXR_OK should be returned with
//    a mime type of NULL.
//
STDMETHODIMP CSmilFileFormat::InitDone
(
    HX_RESULT	status
)
{
    HX_RESULT rc = HXR_OK;

    // If we are not ready then something has gone wrong
    if (m_state != InitPending) return HXR_UNEXPECTED;

    // Now it's time to read the file
    m_state = ReadPending;

    if (status != HXR_OK)
    {
	rc = m_pFFResponse->InitDone(status);
    }
    else
    {
	// check to see if we have logged an error for this file
	// in the last hour.  If we have we will not log an error.
	
	UpdateErrorCaching();
	
	rc = m_pFileObject->Read(FileChunkSize);
    }
    return rc;
}

HX_RESULT CSmilFileFormat::UpdateErrorCaching()
{
    IHXValues* pRequestHeaders = NULL;
    UINT32 ulID = 0, ulSes = 0;
 
    IHXRegistry* pReg = NULL;
    m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pReg);
    
    HX_RESULT ret = m_pRequest->GetRequestHeaders(pRequestHeaders);
    if (!pRequestHeaders)
    {
	// succeeds even when there are no request headers.
	ret = HXR_FAIL;
    }
 
    if (SUCCEEDED(ret))
    {
	IHXBuffer* pID = NULL;
	ret = pRequestHeaders->GetPropertyCString("ConnID", pID);
	if (SUCCEEDED(ret) && pID) 
	{
	    ulID = atoi((char*)pID->GetBuffer());
	}
	HX_RELEASE(pID);
    }
    
    if (SUCCEEDED(ret))
    {
	IHXBuffer* pSes = NULL;
	if (SUCCEEDED(pRequestHeaders->GetPropertyCString("SessionNumber", pSes)))
	{
	    ulSes = atoi((char*)pSes->GetBuffer());
	}
	else
	{
	    // assume 0
	    ulSes = 0;
	}
	HX_RELEASE(pSes);
    }

    IHXBuffer* pURL = NULL;
    if (SUCCEEDED(ret))
    {
	const char form[] = "client.%u.session.%u.URL";
	char pRegkey[sizeof(form) + 20]; /* Flawfinder: ignore */
	sprintf(pRegkey, form, ulID, ulSes); /* Flawfinder: ignore */
	ret = pReg->GetStrByName(pRegkey, pURL);
    }


    char* buf = NULL;
    const char regtemp[] = "server.smilerrorlog.";
    if ( SUCCEEDED(ret) )
    {
       buf = new char[sizeof(regtemp) + pURL->GetSize()];
       if (!buf)
       {
	   ret = HXR_OUTOFMEMORY;
       }
    }

    if (SUCCEEDED(ret))
    {
	strcpy(buf, regtemp); /* Flawfinder: ignore */
	char* pos = buf + sizeof(regtemp) - 1;
	const char* url = (const char*)pURL->GetBuffer();
	// escape the '.'s
	while (*url && pos < buf + sizeof(regtemp) + pURL->GetSize())
	{
	    if (*url == '.' || *url == '/')
	    {
		*pos++ = '%';
		++url;
	    }
	    else
	    {
		*pos++ = *url++;
	    }
	}
	*pos = '\0';
    }

    if (SUCCEEDED(ret))
    {
	INT32 lTime = 0;
	INT32 lNow = time(NULL);
	if (SUCCEEDED(pReg->GetIntByName(buf, lTime)))
	{
	    if ((lNow - lTime) > 3600)
	    {
		m_bLogErrors = TRUE;
		ret = pReg->SetIntByName(buf, lNow);
	    }
	    else
	    {
		m_bLogErrors = FALSE;
	    }
	}
	else
	{
	    m_bLogErrors = TRUE;
	    pReg->AddInt(buf, lNow);
	}
    }
    HX_RELEASE(pReg);
    HX_RELEASE(pRequestHeaders);
    HX_VECTOR_DELETE(buf);
    HX_RELEASE(pURL);
    return ret;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileResponse::CloseDone
//  Purpose:
//	Notification interface provided by users of the IHXFileObject
//	interface. This method is called by the IHXFileObject when the
//	close of the file is complete.
//
STDMETHODIMP CSmilFileFormat::CloseDone(HX_RESULT status)
{
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileResponse::ReadDone
//  Purpose:
//	Notification interface provided by users of the IHXFileObject
//	interface. This method is called by the IHXFileObject when the
//	last read from the file is complete and a buffer is available.
//
STDMETHODIMP CSmilFileFormat::ReadDone
(
    HX_RESULT		status,
    IHXBuffer*		pBuffer
)
{
    HX_RESULT result = HXR_OK;

    if ( SUCCEEDED(status) )
    {
	HX_ASSERT(pBuffer);

	// /For determining what type file this is (via namespace URI), we'll
	// hold onto the first pBuffer for later when we handle stream header:
	if (!m_pStartOfFile)
	{
	    pBuffer->AddRef();
	    m_pStartOfFile = pBuffer;
	}

	BreakUpBuffer(pBuffer);
	// recurse...
	m_pFileObject->Read(FileChunkSize);
    }
    else
    {
	// check see if we have a packet that needs added to the array.
	if ( m_pCurrentPacketData )
	{
	    UCHAR* pCb = m_pCurrentPacketData->pBuffer->GetBuffer();
	    pCb[m_ulCurrentBufferPos++] = '\"';
	    pCb[m_ulCurrentBufferPos++] = ')';
	    pCb[m_ulCurrentBufferPos++] = '\0';
	    m_pCurrentPacketData->pBuffer->SetSize(m_ulCurrentBufferPos);
	    m_pArrayOfPackets->Add((void*)m_pCurrentPacketData);
	    m_pCurrentPacketData = NULL;
	    m_ulCurrentBufferPos = 0;
	}
	// finished reading, calculate the number of packets to send:
	m_ulPacketCount = m_ulCurrentPacket;
	m_ulCurrentPacket = 0;
	m_state = Ready;
	m_pFFResponse->InitDone(result);
    }
    return result;
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	GetSMILFileVersion
//  Purpose:
//	This Function will break the buffer into packets.
//
//	This function maintains a state, such that multiple buffers should
//	Be able to be passed into it at any point...
//
HX_RESULT 
CSmilFileFormat::GetSMILFileVersion()
{
    HX_RESULT pnreslt = HXR_OK;
    const char* pSmilTag = NULL;
    const char* pCloseOfSmilTag = NULL;
    const char* pXmlns = NULL;
    const char* pXmlnsOpenQuotationMark = NULL;
    const char* pBuf = NULL;
    ULONG32 ulBufLen = 0;
    const char* pEqualsSign = NULL;
    const char* pTmp = NULL;
    char* pTmp2 = NULL;
    char* pszStartOfFile = NULL;
    ULONG32 ulCount = 0;
    LONG32 lNumCommentsOpen = 0;

    if (!m_pStartOfFile)
    {
	pnreslt = HXR_BUFFERTOOSMALL;
	goto cleanup;
    }

    // /Fixes PR 59282: m_pStartOfFile is not necessarily NULL-terminated,
    // so we have to limit our access of it to the size of the buffer (duh!):
    ulBufLen = m_pStartOfFile->GetSize();

    pBuf = (const char*)m_pStartOfFile->GetBuffer();

    if (!pBuf  ||  !ulBufLen)
    {
	pnreslt = HXR_BUFFERTOOSMALL;
	goto cleanup;
    }

    pszStartOfFile = new char[ulBufLen+1];
    if (!pszStartOfFile)
    {
	pnreslt = HXR_OUTOFMEMORY;
	goto cleanup;
    }

    // /Now, walk through and copy each character from non-NULL terminated buf:
    pTmp = pBuf; 
    pTmp2 = pszStartOfFile;
    while (*pTmp  &&  ulCount<ulBufLen)
    {
	*pTmp2 = *pTmp;
	pTmp++;
	pTmp2++;
	ulCount++;
    }
    pszStartOfFile[ulCount] = '\0'; // /NULL-terminate it.

    // /Now, let's walk through the start of pszStartOfFile looking for
    // namespace declaration(s) inside the <smil ...> tag, and make sure that
    // the smil tag is NOT inside of a comment:
    pTmp = pszStartOfFile;
    while (*pTmp)
    {
	if (0==strncmp(pTmp, "<!--", 4) )
	{
	    lNumCommentsOpen++;
	    pTmp+=4;
	}
	else if (0==strncmp(pTmp, "-->", 3) )
	{
	    lNumCommentsOpen--;
	    pTmp+=3;
	}
	else if (lNumCommentsOpen<=0  &&  0==strncmp(pTmp, "<smil", 5) )
	{
	    pSmilTag = pTmp; // /We found the smil tag outside of a comment.
	    break;
	}
	else
	{
	    pTmp++;
	}
    }
    if (!pSmilTag  ||  ulBufLen-(pSmilTag-pszStartOfFile) < 6) // /6==min size: "<smil>"
    {
	// /There had better be a <smil...> tag!
	pnreslt = HXR_UNEXPECTED;
	goto cleanup;
    }

    pCloseOfSmilTag = strchr(pSmilTag, '>');
    // /Add 6 so that we don't allow "<smilxmlns ..." (which is invalid):
    pXmlns = strstr(pSmilTag+6, "xmlns");
    if (pXmlns  &&  isspace(*(pXmlns-1)) ) // /"xmlns" must follow a space.
    {
	pEqualsSign = strchr(pXmlns, '=');
	if (pEqualsSign)
	{
	    pXmlnsOpenQuotationMark = strchr(pXmlns, '\"');
	}
    }
    if (pXmlns  &&   pEqualsSign  &&  pXmlnsOpenQuotationMark  &&
	    (!pCloseOfSmilTag  ||  pXmlns<pCloseOfSmilTag))
    {
	BOOL bIsValidNamespaceDeclaration = TRUE;

	// /First, make sure there is nothing but whitespace between
	// the "xmlns" and the '=' as well as between the '=' and the
	// quotation mark:
	char* pTmp = (char*)(pXmlns + strlen("xmlns"));
	while (pTmp<pEqualsSign)
	{
	    if (!isspace(*pTmp))
	    {
		bIsValidNamespaceDeclaration = FALSE;
		break;
	    }
	    pTmp++;
	}
	pTmp = (char*)(pEqualsSign+1);
	while(pTmp<pXmlnsOpenQuotationMark)
	{
	    if (!isspace(*pTmp))
	    {
		bIsValidNamespaceDeclaration = FALSE;
		break;
	    }
	    pTmp++;
	}

	if (bIsValidNamespaceDeclaration)
	{
	    // /Note: SMIL 1.0 namespace is default and is:
	    // "http://www.w3.org/TR/REC-smil".  Note that if this is
	    // tagged (first) with a SMIL 1.0 namespace followed by
	    // another namespace, then this is a 1.0 file and we'll let
	    // the 1.0 renderer get it and do what it can with the 2.0
	    // stuff (namely ignore it):
	    const char* pSMIL10Namespace = strstr(pXmlnsOpenQuotationMark,
		    "http://www.w3.org/TR/REC-smil\"");

	    // /This in the final SMIL 2.0 rec:
	    const char* pSMIL20Namespace = strstr(pXmlnsOpenQuotationMark,
		    "http://www.w3.org/2001/SMIL20/Language\"");

	    const char* pSMIL20NamespaceCandidateRec = strstr(
		    pXmlnsOpenQuotationMark,
		    "http://www.w3.org/2000/SMIL20/CR/Language\"");

	    // /Fixes PR 55749: allow new PR namespace:
	    const char* pSMIL20NamespaceProposedRec = strstr(
		    pXmlnsOpenQuotationMark,
		    "http://www.w3.org/2001/SMIL20/PR/Language\"");

	    const char* pSMIL20NamespaceLastCall = strstr(
		    pXmlnsOpenQuotationMark,
		    "http://www.w3.org/TR/REC-smil/2000/SMIL20/LC/\"");

	    // /If the SMIL 1.0 namespace is declared prior to any other
	    // version of SMIL's namespace, then this is a SMIL 1.0 doc:
	    if (pSMIL10Namespace  &&  (!pSMIL20Namespace  ||
		    pSMIL10Namespace < pSMIL20Namespace)  &&
		    (!pSMIL20NamespaceCandidateRec  ||
		    pSMIL10Namespace < pSMIL20NamespaceCandidateRec)  &&
		    (!pSMIL20NamespaceProposedRec  ||
		    pSMIL10Namespace < pSMIL20NamespaceProposedRec)  &&
		    (!pSMIL20NamespaceLastCall  ||
		    pSMIL10Namespace < pSMIL20NamespaceLastCall) )
	    {
		m_smilFileVersion = SMILFileVersionSmil10Strict;
	    }
	    else if (pSMIL20NamespaceCandidateRec  &&  (!pSMIL20Namespace  ||
		    pSMIL20NamespaceCandidateRec < pSMIL20Namespace))
	    {
		m_smilFileVersion = SMILFileVersionSmil20PreRec;
	    }
	    else if (pSMIL20NamespaceProposedRec  &&  (!pSMIL20Namespace ||
		    pSMIL20NamespaceProposedRec < pSMIL20Namespace))
	    {
		m_smilFileVersion = SMILFileVersionSmil20PreRec;
	    }
	    // /Treat both LC (Last Call) and CR (Candidate Rec) as "PreRec":
	    else if (pSMIL20NamespaceLastCall  &&  (!pSMIL20Namespace  ||
		    pSMIL20NamespaceLastCall < pSMIL20Namespace))
	    {
		m_smilFileVersion = SMILFileVersionSmil20PreRec;
	    }
	    else if (pSMIL20Namespace)
	    {
		m_smilFileVersion = SMILFileVersionSmil20;
	    }
	    else
	    {
		m_smilFileVersion = SMILFileVersionUnknown;
	    }
	}
    }

cleanup:
    if (pszStartOfFile)
    {
	HX_VECTOR_DELETE(pszStartOfFile);
    }

    return pnreslt;
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	BreakUpBuffer
//  Purpose:
//	This Function will break the buffer into packets.
//
//	This function maintains a state, such that multiple buffers should
//	Be able to be passed into it at any point...
//

// XXX  jhug
// This doesn't allow text between tags... Future versions of SMIL might
// support this... this will need a bit of a modification.
HX_RESULT 
CSmilFileFormat::BreakUpBuffer(IHXBuffer* pBuffer)
{
    // walk through the buffer and escape all \"s
    // also forget about all white space and comments...
    // also break the buffer up into packets for easy access.

    HX_RESULT result = HXR_OK;

    UCHAR* pCb = NULL;
    if ( m_pCurrentPacketData )
    {
	pCb = m_pCurrentPacketData->pBuffer->GetBuffer();
    }

    const UCHAR* pData = pBuffer->GetBuffer();
    UINT32 ulSize = pBuffer->GetSize();
    for ( UINT32 ulPos = 0; ulPos < ulSize && pData[ulPos]; ++ulPos )
    {
	// three for the end of the packet structure, and 1 in case we have 
	// to escape a char...
	if ( m_ulCurrentBufferPos >= MaxPacketSize - 4 ) 
	{
	    pCb[m_ulCurrentBufferPos++] = '\"';
	    pCb[m_ulCurrentBufferPos++] = ')';
	    pCb[m_ulCurrentBufferPos++] = '\0';
	    m_pCurrentPacketData->pBuffer->SetSize(m_ulCurrentBufferPos);
	    m_pArrayOfPackets->Add((void*)m_pCurrentPacketData);
	    m_pCurrentPacketData = NULL;
	    m_ulCurrentBufferPos = 0;
	    pCb = NULL;
	}

	if ( !m_pCurrentPacketData )
	{
	    m_pCurrentPacketData = new PacketData;
	    if ( m_pCurrentPacketData == NULL )
	    {
		result = HXR_OUTOFMEMORY;
	    }
	    else
	    {
		m_pCurrentPacketData->pBuffer = NULL;
		m_pCurrentPacketData->pNumPos = NULL;
		result = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, 
			(void**)&m_pCurrentPacketData->pBuffer);
	    }
	    if ( SUCCEEDED(result) )
	    {
		result = m_pCurrentPacketData->pBuffer->SetSize(MaxPacketSize);
	    }
	    if ( SUCCEEDED(result) )
	    {
		pCb = m_pCurrentPacketData->pBuffer->GetBuffer();
		//memset(pCb, 0xef, MaxPacketSize);
		sprintf((char*)pCb, "(smil-document (ver %s)(npkt %ld)"
		    "(ttlpkt 0     )(doc \"", 
		    RMA_DRIVER_VERSION, ++m_ulCurrentPacket);
		m_ulCurrentBufferPos = strlen((char*)pCb);
		// back up to the begining of the total number position.
		// 13 is position back in header...
		m_pCurrentPacketData->pNumPos = (char*)pCb + 
		    m_ulCurrentBufferPos - 13; 
	    }
	}

	if ( FAILED(result) )
	{
	    break;
	}
	switch ( m_fileState )
	{
	case InContent:
	    {
		if ( pData[ulPos] == '\"' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		    pCb[m_ulCurrentBufferPos++] = '\"';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		    pCb[m_ulCurrentBufferPos++] = '\"';
		}
		else if ( pData[ulPos] == '<' )
		{
		    if ( pData[ulPos+1] == '!' )
		    {
			if ( pData[ulPos+2] == '-'
			    && pData[ulPos+3] == '-' )
			{
			    ulPos += 3;
			    m_fileState = InComment;
			}
			else if ( pData[ulPos+2] == '[' && 
				  pData[ulPos+3] == 'C' &&
				  pData[ulPos+4] == 'D' &&
				  pData[ulPos+5] == 'A' &&
				  pData[ulPos+6] == 'T' &&
				  pData[ulPos+7] == 'A' &&
				  pData[ulPos+8] == '[' )
			{
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			    m_fileState = InCDATA;
			}
			else if ( pData[ulPos+2] == 'D' && 
				  pData[ulPos+3] == 'O' &&
				  pData[ulPos+4] == 'C' &&
				  pData[ulPos+5] == 'T' &&
				  pData[ulPos+6] == 'Y' &&
				  pData[ulPos+7] == 'P' &&
				  pData[ulPos+8] == 'E' )
			{
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			    m_fileState = InDTD;
			}
		    }
		    else
		    {
			pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			m_fileState = InTagName;
		    }
		}
		// preserve all lines so line number error reporting is
		// accurate
		else if ( isspace(pData[ulPos]) && pData[ulPos] != '\n'  &&
			pData[ulPos] != '\r')
		{
		    //continue...
		}
		//If we find \rx where x is not \n, then keep it else let
		// the next char (\n) be kept in the next iteration of
		// this loop.  This accomodates Mac-authored '\r'-only
		// text.  Think Different (pain is "different").
		else if (isspace(pData[ulPos])  &&  ('\r' == pData[ulPos]  &&
			(ulPos+1 < ulSize  &&  pData[ulPos+1])  &&
			'\n' == pData[ulPos+1]) )
		{
		    //continue...
		}
		else
		{
		    if ('\r' == pData[ulPos])
		    {
			pCb[m_ulCurrentBufferPos++] = '\n';
		    }
		    else
		    {
			pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		    }
		}
	    }
	    break;
	case InDTD:
	    {
		if ( pData[ulPos] == '[' )
		{
		    m_fileState = InDTDMarkup;
		}
		else if ( pData[ulPos] == '>' )
		{
		    m_fileState = InContent;
		}
		else if ( pData[ulPos] == '\"' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		pCb[m_ulCurrentBufferPos++] = pData[ulPos];
	    }
	    break;
	case InDTDMarkup:
	    {
		if ( pData[ulPos] == ']' )
		{
		    m_fileState = InDTD;
		}
		else if ( pData[ulPos] == '\"' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		pCb[m_ulCurrentBufferPos++] = pData[ulPos];
	    }
	    break;
	case InCDATA:
	    {
		if ( pData[ulPos] == ']' && pData[ulPos+1] == ']' && pData[ulPos+2] == '>' )
		{
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos++];

		    m_fileState = InContent;
		}
		else if ( pData[ulPos] == '\"' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		pCb[m_ulCurrentBufferPos++] = pData[ulPos];
	    }
	    break;
	case InTagName:
	    {
		// go to first white space
		// let's escape " and \ even though they are ilegal.
		if ( isspace(pData[ulPos]) )
		{
		    m_fileState = InBeginAttributeName;
		}
		else if ( pData[ulPos] == '>' )
		{
		    m_fileState = InContent;
		}
		else if ( pData[ulPos] == '\"' )
		{
		    ReportError(HXR_XML_GENERALERROR, XML_BAD_TAG_NAME);
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    ReportError(HXR_XML_GENERALERROR, XML_BAD_TAG_NAME);
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		pCb[m_ulCurrentBufferPos++] = pData[ulPos];
	    }
	    break;
	case InTag:
	    {
		if ( pData[ulPos] == '>' || (pData[ulPos] == '/' && 
		    pData[ulPos+1] == '>'))
		{
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		    m_fileState = InContent;
		}
		else
		{
		    // grab the first char... keep it and switch states.
		    // it should be a space... but if it isn't we will
		    // not complain because the renderer accepts no 
		    // space.
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		    m_fileState = InBeginAttributeName;
		    if (!isspace(pData[ulPos]))
		    {
			ReportError(HXR_XML_GENERALERROR,
				XML_MISSING_SPACE);
		    }
		}
	    }
	    break;
	case InBeginAttributeName:
	    {
		if ( isspace(pData[ulPos]) )
		{
		    // preserve all lines so line numbers in error messages
		    // are accurate
		    if (pData[ulPos] == '\n'  ||  ('\r' == pData[ulPos]  &&
			    ulPos+1 < ulSize  &&  '\n' != pData[ulPos+1]) )
		    {
			if ('\r' == pData[ulPos])
			{
			    pCb[m_ulCurrentBufferPos++] = '\n';
			}
			else
			{
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			}
		    }
		    // continue...
		}
		else if ( pData[ulPos] == '=' )
		{
		    ReportError(HXR_XML_GENERALERROR, 
		    		XML_BAD_ATTRIBUTE_NAME);

		    m_fileState = InBeginAttributeValue;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if ( pData[ulPos] == '>' || (pData[ulPos] == '/' 
		    && pData[ulPos+1] == '>'))
		{
		    m_fileState = InContent;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else
		{
		    m_fileState = InAttributeName;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
	    }
	    break;
	case InAttributeName:
	    {
		if ( isspace(pData[ulPos]) )
		{
		    m_fileState = InEndAttributeName;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if ( pData[ulPos] == '=' )
		{
		    m_fileState = InBeginAttributeValue;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if ( pData[ulPos] == '>' )
		{
		    ReportError(HXR_XML_GENERALERROR, 
		    		XML_MISSING_ATTRIBUTE_VALUE);
		    m_fileState = InContent;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if (pData[ulPos] == '\'' || pData[ulPos] == '"')
		{
		    ReportError(HXR_XML_GENERALERROR, 
		    		XML_BAD_ATTRIBUTE_NAME);
		    m_cQuote = pData[ulPos];
		    if (m_cQuote == '"')
		    {
			pCb[m_ulCurrentBufferPos++] = '\\';
		    }
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		    m_fileState = InAttributeValue;
		}
		else
		{
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
	    }
	    break;
	case InEndAttributeName:
	    {
		if ( isspace(pData[ulPos]) )
		{
		    // preserve all lines so line numbers in error messages
		    // are accurate
		    if (pData[ulPos] == '\n'  ||  ('\r' == pData[ulPos]  &&
			    ulPos+1 < ulSize  &&  '\n' != pData[ulPos+1]) )
		    {
			if ('\r' == pData[ulPos])
			{
			    pCb[m_ulCurrentBufferPos++] = '\n';
			}
			else
			{
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			}
		    }
		    // continue..
		}
		else if ( pData[ulPos] == '=' )
		{
		    m_fileState = InBeginAttributeValue;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if ( pData[ulPos] == '>' )
		{
		    ReportError(HXR_XML_GENERALERROR, 
		    		XML_MISSING_ATTRIBUTE_VALUE);
		    // illegal.
		    m_fileState = InContent;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else
		{
		    // hmm. we got a non whitespace before the =

		    // push a ' ' on so the render will get this error.
		    pCb[m_ulCurrentBufferPos++] = ' ';

		    //First, let's see if we have a ["] or a [']
		    // (i.e., an attribute value start) in which
		    // case the author must have forgotten to
		    // put an '=' between the name/value pair.
		    // In this case, we need to keep the renderers
		    // from firing off an error with old bad content,
		    // so we pretend we're in the "InAttributeValue"
		    // state:
		    if ( pData[ulPos] == '\'' )
		    {
			ReportError(HXR_XML_GENERALERROR, 
				XML_BAD_ATTRIBUTE_NAME);

			m_cQuote = pData[ulPos];
			m_fileState = InAttributeValue;
		    }
		    else if ( pData[ulPos] == '\"' )
		    {
			ReportError(HXR_XML_GENERALERROR,
				XML_BAD_ATTRIBUTE_NAME);
			m_cQuote = pData[ulPos];
			pCb[m_ulCurrentBufferPos++] = '\\';
			m_fileState = InAttributeValue;
		    }
		    else
		    {
			ReportError(HXR_XML_BADATTRIBUTE, 
				XML_BAD_ATTRIBUTE_NAME);
			// lets go back to the attribute name state.
			m_fileState = InAttributeName;
		    }
		    //Push the character back on so the renderer
		    // can deal with it:
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
	    }
	    break;
	case InBeginAttributeValue:
	    {
		if ( isspace(pData[ulPos]) )
		{
		    // preserve all lines so line numbers in error messages
		    // are accurate
		    if (pData[ulPos] == '\n'  ||  ('\r' == pData[ulPos]  &&
			    ulPos+1 < ulSize  &&  '\n' != pData[ulPos+1]) )
		    {
			if ('\r' == pData[ulPos])
			{
			    pCb[m_ulCurrentBufferPos++] = '\n';
			}
			else
			{
			    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
			}
		    }
		    // continue...
		}
		else if ( pData[ulPos] == '\'' )
		{
		    m_cQuote = pData[ulPos];
		    m_fileState = InAttributeValue;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
		else if ( pData[ulPos] == '\"' )
		{
		    m_cQuote = pData[ulPos];
		    pCb[m_ulCurrentBufferPos++] = '\\';
		    pCb[m_ulCurrentBufferPos++] = '\"';
		    m_fileState = InAttributeValue;
		}
		else if ( pData[ulPos] == '>' )
		{
		    ReportError(HXR_XML_GENERALERROR,
		    	XML_MISSING_ATTRIBUTE_VALUE);
		    m_fileState = InContent;
		    pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		}
	    }
	    break;
	case InAttributeValue:
	    {
		if ( pData[ulPos] == m_cQuote )
		{
		    if ( m_cQuote == '\"' )
		    {
			pCb[m_ulCurrentBufferPos++] = '\\';
		    }
		    m_fileState = InTag;
		}
		else if ( pData[ulPos] == '\"' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		else if ( pData[ulPos] == '\\' )
		{
		    pCb[m_ulCurrentBufferPos++] = '\\';
		}
		pCb[m_ulCurrentBufferPos++] = pData[ulPos];
	    }
	    break;
	case InComment:
	    {
		if ( pData[ulPos] == '-' && pData[ulPos+1] == '-' &&
		    pData[ulPos+2] == '>' )
		{
		    m_fileState = InContent;
		    ulPos += 2;
		}
		// preserve all lines so line numbers in error messages
		// are accurate
		else if (pData[ulPos] == '\n'  ||  ('\r' == pData[ulPos]  &&
			    ulPos+1 < ulSize  &&  '\n' != pData[ulPos+1]) )
		{
		    if ('\r' == pData[ulPos])
		    {
			pCb[m_ulCurrentBufferPos++] = '\n';
		    }
		    else
		    {
			pCb[m_ulCurrentBufferPos++] = pData[ulPos];
		    }
		}
		// else continue...
	    }
	    break;
	default:
	    HX_ASSERT(FALSE);
	}
    }
    return result;
}

HX_RESULT CSmilFileFormat::ReportError(HX_RESULT result, 
				       const char* errStr)
{
    if ( m_bLogErrors )
    {
	IHXErrorMessages* pLog = NULL;
	m_pContext->QueryInterface(IID_IHXErrorMessages, 
		(void**)&pLog);
	pLog->Report(HXLOG_ERR, result, 0, errStr,
	    "http://www.real.com");
	HX_RELEASE(pLog);
    }
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileResponse::WriteDone
//  Purpose:
//	Notification interface provided by users of the IHXFileObject
//	interface. This method is called by the IHXFileObject when the
//	last write to the file is complete.
//
STDMETHODIMP CSmilFileFormat::WriteDone(HX_RESULT status)
{
    // We don't ever write, so we don't expect to get this...
    return HXR_UNEXPECTED;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXFileResponse::SeekDone
//  Purpose:
//	Notification interface provided by users of the IHXFileObject
//	interface. This method is called by the IHXFileObject when the
//	last seek in the file is complete.
//
STDMETHODIMP CSmilFileFormat::SeekDone(HX_RESULT status)
{
    HX_RESULT result = HXR_OK;

    return result;
}

/************************************************************************
 *	Method:
 *	    IHXPendingStatus::GetStatus
 *	Purpose:
 *	    Called by the user to get the current pending status from an object
 */
STDMETHODIMP
CSmilFileFormat::GetStatus
(
    REF(UINT16) uStatusCode, 
    REF(IHXBuffer*) pStatusDesc, 
    REF(UINT16) ulPercentDone
)
{
    HX_RESULT hResult = HXR_OK;
    IHXPendingStatus* pFileSystemStatus = NULL;


    // asking status information from the file system object
    if (m_pFileObject)
    {
	if (HXR_OK == m_pFileObject->QueryInterface(IID_IHXPendingStatus,(void**)&pFileSystemStatus))
	{
	    hResult = pFileSystemStatus->GetStatus(uStatusCode, pStatusDesc, ulPercentDone);

	    pFileSystemStatus->Release();
	    return hResult;
	}
    }

    // by default
    uStatusCode = HX_STATUS_READY;
    pStatusDesc = NULL;
    ulPercentDone = 0;
 
    return hResult;
}

/************************************************************************
 *	Method:
 *	    IHXThreadSafeMethods::IsThreadSafe
 */
STDMETHODIMP_(UINT32)
CSmilFileFormat::IsThreadSafe()
{
    return HX_THREADSAFE_METHOD_FF_GETPACKET |
	HX_THREADSAFE_METHOD_FSR_READDONE;
}
