/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: httpfsys.cpp,v 1.28.2.5.4.2 2005/09/16 20:59:04 gwright 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 ***** */

#include "hlxclib/stdio.h"
#include "hlxclib/string.h"
#include "hlxclib/stdlib.h"
#include "hlxclib/sys/types.h"
#include "hlxclib/sys/stat.h"

#include "httpfsys.ver"
#include "hxtypes.h"
#include "hxcom.h"
#include "hxstrutl.h"
#include "hxerror.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxplugn.h"
#include "hxplgns.h"
#include "hxengin.h"
#include "hxprefs.h"
#include "ihxpckts.h"
#include "hxmon.h"
#include "hxrendr.h"
#include "hxpends.h"
#include "hxauthn.h"
#include "hxtbuf.h"
#include "hxtset.h"
#include "ihxident.h"
#include "perplex.h"

#include "chxpckts.h"

#include "dbcs.h"
#include "chunkres.h"
#include "hxslist.h"
#include "httppars.h"
#include "httpmsg.h"
#include "mimehead.h"
#include "portaddr.h"
#include "hxver.h"
#include "hxtick.h"
#include "hxurl.h"

#include "ihxcookies.h"

#include "timerep.h"
#include "cache.h"
#include "unkimp.h"
#include "hxdir.h"
#include "rtsputil.h"
#if defined(HELIX_FEATURE_HTTP_GZIP)
#include "zlib.h"
#include "decoder.h"
#endif
#include "pacutil.h"
#include "httpfsys.h"
#include "ihxcookies2.h"

#ifdef _MACINTOSH
//#include "../dcondev/dcon.h"
#endif

#if defined(_CARBON) || defined(_MAC_MACHO)
#define USE_TEMP_CACHE_LOCATION 1
#include "filespecutils.h"
#endif

#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(Httpfsys);
#endif

#define DEF_HTTP_PORT       80
#define DEF_HTTP_PORT_STR   ":80/"
#define ALLOCATION_SIZE     1024

#ifdef _WIN16
#define MAX_RECURSION_LEVEL     10
#elif _LINUX
#define MAX_RECURSION_LEVEL     30
#elif defined(_MACINTOSH)
#define MAX_RECURSION_LEVEL     20
#else
// XXXNH: reduced from 200 to 50 to fix stack overflow in ActiveX
#define MAX_RECURSION_LEVEL     50
#endif

#undef  LOG_DATA
#define MAX_CACHE_SIZE          (64 * 1024 * 1024)
#define CACHE_DEFAULT_TTL_SECS  (1 * 60 * 60)

#define  LOG_FILE               "C:/Temp/cached-httpfsys.log"
#include "http_debug.h"

#include "db.h"

#include <string.h>

UINT32 g_ulDefTtl;

#ifndef USE_TEMP_CACHE_LOCATION
#define DEF_CACHE_DB            "cache_db"
#define MAX_CACHE_FILENAME      1024
#else
#define DEF_CACHE_DB            "helix_http_cache_db"
#endif // USE_TEMP_CACHE_LOCATION

CCacheEntry*   g_pCacheEntry = NULL;
IHXBuffer*    CreateBufferFromValues (IHXValues *pHeaderValues);

// default if no timeouts in preferences.
#define DEF_HTTP_SERVER_TIMEOUT     (20 * MILLISECS_PER_SECOND)
#define DEF_HTTP_CONNECT_TIMEOUT    (30 * MILLISECS_PER_SECOND)

// default user agent header
#define DEF_USER_AGENT		"RMA/1.0 (compatible; RealMedia)"

// threshhold to see if it's better to do a seek or
// just wait for old-fashioned reading to catch up
#define BYTE_RANGE_SEEK_THRESHHOLD (4 * 1024)

const char* CHTTPFileSystem::zm_pDescription    = "RealNetworks HTTP File System with CHTTP Support";
const char* CHTTPFileSystem::zm_pCopyright      = HXVER_COPYRIGHT;
const char* CHTTPFileSystem::zm_pMoreInfoURL    = HXVER_MOREINFO;
const char* CHTTPFileSystem::zm_pShortName      = "pn-http";

/// This name is used to indicate the http server is 
/// actually RealServer. If the server team changes 
/// the http response header field, Server: then
/// this value needs to be changed accordingly.
#define REALSERVER_RESPONSE_NAME    "RealServer"

//#define SUPPORT_SECURE_SOCKETS
//#define CREATE_LOG_DUMP
//#define LOG_DUMP_FILE	"c:/temp/avi.txt"

#ifdef SUPPORT_SECURE_SOCKETS
const char* CHTTPFileSystem::zm_pProtocol       = "http|chttp|https";
#else
const char* CHTTPFileSystem::zm_pProtocol       = "http|chttp";
#endif

BOOL CHTTPFileSystem::m_bSaveNextStream = FALSE;
//CHXString CHTTPFileSystem::m_SaveFileName( "" );
CHXString CHTTPFileSystem::m_SaveFileName;


static INT32 g_nRefCount_http  = 0;

#define WWW_AUTHENTICATION_RECENT_KEY "authentication.http.realm.recent"
#define PROXY_AUTHENTICATION_RECENT_KEY "proxy-authentication.http.realm.recent"

#ifdef USE_TEMP_CACHE_LOCATION
static void SetBufferToCacheFilePath(IHXBuffer* pBuffer)
{
    // For Mac Carbon, we put it in the Cleanup at Startup folder since the user may not
    // have permission to write into the app's directory
    CHXDirSpecifier tempDir = CHXFileSpecUtils::GetSystemTempDirectory();
    HX_ASSERT(CHXFileSpecUtils::DirectoryExists(tempDir));

    CHXFileSpecifier cacheFileSpec = tempDir.SpecifyChildFile(DEF_CACHE_DB);

    CHXString strCachePath = cacheFileSpec.GetPathName();
	
    pBuffer->Set((UINT8*) (const char *) strCachePath, 1 + strCachePath.GetLength());
}
#endif

/****************************************************************************
 *
 *  Function:
 *
 *      CHTTPFileSystem::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 an outer unknown is
 *      not passed to this function, and you do not need to code for this
 *      situation.
 *
 */
HX_RESULT STDAPICALLTYPE CHTTPFileSystem::HXCreateInstance
(
    IUnknown**  /*OUT*/ ppIUnknown
)
{
    // Do NOT check for expiration.  Needed for Auto Upgrade.

    *ppIUnknown = (IUnknown*)(IHXPlugin*)new CHTTPFileSystem();
    if (*ppIUnknown)
    {
        (*ppIUnknown)->AddRef();
        return HXR_OK;
    }
    return HXR_OUTOFMEMORY;
}

/****************************************************************************
 *
 *  Function:
 *
 *      CHTTPFileSystem::HXShutdown()
 *
 *  Purpose:
 *
 *      Function implemented by all plugin DLL's to free any *global*
 *      resources. This method is called just before the DLL is unloaded.
 *
 */
HX_RESULT STDAPICALLTYPE CHTTPFileSystem::HXShutdown(void)
{
    // Joshe 7/19/2001:
    // because HXShutdown sometimes gets called but httpfsys can be loaded 
    // from CHXIndependentHttp, we need this hack check to work around 
    // the crashes caused when HXShutdown is called when it shouldn't
    // it's easier to fix here than to change plugin handler to query 
    // CanUnload first, though that is probably the right thing to do eventually
    if(FAILED(CanUnload()))
    {
	return HXR_OK;
    }

    if (g_pCacheEntry) 
    {
        g_pCacheEntry->close();
        delete g_pCacheEntry;
        g_pCacheEntry = NULL;
    }

    return HXR_OK;
}

/****************************************************************************
 * 
 *  Function:
 * 
 *      CanUnload()
 * 
 *  Purpose:
 * 
 *      Function implemented by all plugin DLL's if it returns HXR_OK 
 *      then the pluginhandler can unload the DLL
 *
 */
HX_RESULT CHTTPFileSystem::CanUnload(void)
{
    return (g_nRefCount_http ? HXR_FAIL : HXR_OK);
}


BEGIN_INTERFACE_LIST(CHTTPFileSystem)
    INTERFACE_LIST_ENTRY(IID_IHXPlugin, IHXPlugin)
    INTERFACE_LIST_ENTRY(IID_IHXFileSystemObject, IHXFileSystemObject)
    INTERFACE_LIST_ENTRY(IID_IHXFileSystemCache, IHXFileSystemCache)
    INTERFACE_LIST_ENTRY(IID_IHXHTTPAutoStream, IHXHTTPAutoStream)
END_INTERFACE_LIST

CHTTPFileSystem::CHTTPFileSystem() : 
    m_pContext(NULL)
    , m_pOptions(NULL)
{
    g_nRefCount_http++;
}

CHTTPFileSystem::~CHTTPFileSystem()
{
    g_nRefCount_http--;
    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pOptions);
}

/************************************************************************
 *  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 CHTTPFileSystem::InitPlugin(IUnknown* /*IN*/ pContext)
{
    /* This plugin does not need any context */
    HX_RELEASE(m_pContext);
    m_pContext = pContext;
    if (m_pContext)
    {
        m_pContext->AddRef();
    }
    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetPluginInfo
 *  Purpose:
 *    Returns the basic information about this plugin. Including:
 *
 *    unInterfaceCount  the number of standard RMA interfaces
 *                      supported by this plugin DLL.
 *    pIIDList          array of IID's for standard RMA interfaces
 *                      supported by this plugin DLL.
 *    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 CHTTPFileSystem::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    = zm_pDescription;
    pCopyright      = zm_pCopyright;
    pMoreInfoURL    = zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

STDMETHODIMP CHTTPFileSystem::GetFileSystemInfo
(
    REF(const char*) /*OUT*/ pShortName,
    REF(const char*) /*OUT*/ pProtocol
)
{
    pShortName  = zm_pShortName;
    pProtocol   = zm_pProtocol;

    return HXR_OK;
}

STDMETHODIMP
CHTTPFileSystem::InitFileSystem(IHXValues* options)
{
    LOGX ((szDbgTemp, "InitFileSystem()"));

    if (options)
    {
	m_pOptions = options;
	m_pOptions->AddRef();
    }

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//      IHXFileSystemObject::CreateFile
//  Purpose:
//      TBD
//
STDMETHODIMP CHTTPFileSystem::CreateFile
(
    IUnknown**  /*OUT*/ ppFileObject
)
{
    LOGX ((szDbgTemp, "CreateFile()"));
    CHTTPFileObject* pFileObj = CHTTPFileObject::CreateObject();
    if (pFileObj)
    {
        pFileObj->InitObject(NULL, this, m_pContext, m_pOptions);

	if( m_bSaveNextStream )
	{
	    pFileObj->SetDestinationFile( m_SaveFileName );
            m_bSaveNextStream = FALSE;
	}

        if(HXR_OK == pFileObj->QueryInterface(IID_IUnknown,
                                            (void**)ppFileObject))
        {
            return HXR_OK;
        }
        return HXR_FAIL;
    }
    return HXR_OUTOFMEMORY;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//     CHTTPFileSystem::CreateDir
//  Purpose:
//     TBD
//
STDMETHODIMP CHTTPFileSystem::CreateDir
(
    IUnknown**    /*OUT*/ ppDirObject
)
{
    return HXR_NOTIMPL;
}




/////////////////////////////////////////////////////////////////////////
//  Method:
//	CHTTPFileSystem::RefreshCache
//
STDMETHODIMP 
CHTTPFileSystem::RefreshCache(void)
{
    LOGX ((szDbgTemp, "RefreshCache()"));
    
    if (g_pCacheEntry)
    {
        g_pCacheEntry->CleanCache(0, 0);
        
        delete g_pCacheEntry;
        g_pCacheEntry = NULL;
    }
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	CHTTPFileSystem::EmptyCache
//
STDMETHODIMP
CHTTPFileSystem::EmptyCache(void)
{
    LOGX ((szDbgTemp, "EmptyCache()"));
    
    if (g_pCacheEntry == NULL)
    {
	///XXXXXXXXXTHIS IS A TEMPORARY THING.  WE NEED TO MAKE CLEANCACHE A STATIC ROUTINE...
	// Get the location of the cache...
	IHXCommonClassFactory *commonClassFactory = NULL;
	IHXPreferences *preferences = NULL;
	IHXBuffer *buffer = NULL;
	m_pContext->QueryInterface(IID_IHXCommonClassFactory, (void **)(&commonClassFactory));
        m_pContext->QueryInterface(IID_IHXPreferences, (void **)(&preferences));
	if ((preferences != NULL) && (commonClassFactory != NULL))
	{
	    if (preferences->ReadPref("CacheFilename", buffer) != HXR_OK)
	    {
		if (HXR_OK == commonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)(&buffer)))
		{
#ifdef USE_TEMP_CACHE_LOCATION
		    SetBufferToCacheFilePath(buffer);
#else

		    char  szModule[MAX_CACHE_FILENAME + sizeof DEF_CACHE_DB + 4] = { 0 }; /* Flawfinder: ignore */
#ifdef _MACINTOSH
		    FSSpec fileSpec;
		    GetCurrentAppSpec(&fileSpec);
		    CHXString fullpath;
		    fullpath = fileSpec;
		    SafeStrCpy(szModule, fullpath, sizeof(szModule));
#elif defined(_WIN32)
                    GetModuleFileName(NULL, OS_STRING2(szModule,MAX_CACHE_FILENAME), MAX_CACHE_FILENAME);
#else
                    *szModule = '\0';
#endif

		    INT8* pFilename = ::strrchr(szModule, OS_SEPARATOR_CHAR);

		    if (pFilename == NULL)
		    {
			pFilename = ::strrchr(szModule, '/');
		    }

		    if (pFilename != NULL) // Found
		    {
			// module file name starts after the separator charactor
			*pFilename = '\0';
		    }
		    // Add on the cache database filename portion
		    ::strcat (szModule, OS_SEPARATOR_STRING); /* Flawfinder: ignore */
		    ::strcat (szModule, DEF_CACHE_DB); /* Flawfinder: ignore */
		    buffer->Set((UINT8*)szModule, strlen(szModule) + 1);
#endif // !USE_TEMP_CACHE_LOCATION
		}
	    }
	}

	// Get the max size of the cache...
	ULONG32 maxCacheSize = DEFAULT_MAX_CACHE_SIZE;
	IHXBuffer *pBuffer = NULL;
	if (preferences->ReadPref("CacheMaxSize", pBuffer) == HXR_OK)
	{
	    maxCacheSize = atoi((const char*)pBuffer->GetBuffer());

	    if (maxCacheSize < MININUM_MAX_CACHE_SIZE)
	    {
		maxCacheSize = MININUM_MAX_CACHE_SIZE;
	    }
	    HX_RELEASE(pBuffer);
	}
	HX_RELEASE(preferences);
	HX_RELEASE(commonClassFactory);

	// Create a new cache entry to use in a bit...
	g_pCacheEntry = new CCacheEntry ((char *)buffer->GetBuffer(), maxCacheSize, NULL);
	HX_RELEASE(buffer);
    }

    // We should have a cache now...
    HX_ASSERT(g_pCacheEntry != NULL);

    // Clean out the cache now...
    g_pCacheEntry->CleanCache(time(NULL), 0);

    delete g_pCacheEntry;
    g_pCacheEntry = NULL;
    return HXR_OK;
}
/////////////////////////////////////////////////////////////////////////
//  Method:
//	CHTTPFileSystem::MoveCache
//
//
STDMETHODIMP
CHTTPFileSystem::MoveCache(const char *path)
{
    LOGX ((szDbgTemp, "MoveCache()"));

    // We want to clean the cache first
    if (g_pCacheEntry)
    {
        g_pCacheEntry->CleanCache(0, 0);
        
        // Now move the directory sub-tree to the new location
        
        // Now re-open the database from the new location
	delete g_pCacheEntry;
	g_pCacheEntry = NULL;
    }
    
    return HXR_OK;
}


STDMETHODIMP_(void)
CHTTPFileSystem::SetDestinationFile( const char *pFilename )
{
    if( pFilename && ::strlen( pFilename ) )
    {
	m_bSaveNextStream = TRUE;
	m_SaveFileName = pFilename;
    }
    else
	m_bSaveNextStream = FALSE;
}


/*****************************************************************
 *
 * FileObject methods
 *
 *****************************************************************/

CHTTPFileObject* 
CHTTPFileObject::CreateObject()             
{                                                   
    LOGX ((szDbgTemp, "CreateObject()"));
    CHTTPFileObject* pNew = new CHTTPFileObject;
    if (pNew)                                       
    {                                               
        pNew->m_lCount = 1;                         
        HX_RESULT pnrRes = HXR_OK;//pNew->FinalConstruct();  
        pNew->m_lCount = 0;                         
        if (FAILED(pnrRes))                         
        {                                           
            delete pNew;                            
            return NULL;                            
        }                                           
    }          
    return pNew;                                    
}                                                   

STDMETHODIMP_(ULONG32) 
CHTTPFileObject::AddRef (THIS)
{
    return InterlockedIncrement(&m_lCount);
}

STDMETHODIMP_(ULONG32) 
CHTTPFileObject::Release (THIS)
{
    HX_ASSERT(m_lCount>=0);
    if (InterlockedDecrement(&m_lCount) > 0)
    {
        return m_lCount;
    }

    delete this;
    return 0;
}

STDMETHODIMP 
CHTTPFileObject::QueryInterface(REFIID riid, void** ppvObj)
{                                                           
    LOGX ((szDbgTemp, "QueryInterface(x0%08X)", riid));
    if (!ppvObj)                                            
        return HXR_POINTER;                                 
    if (IsEqualIID(IID_IUnknown, riid))                     
    {                                                       
        AddRef();                                           
        *ppvObj = (this);
        return HXR_OK;                                      
    }
    if (IsEqualIID(IID_IHXRequestHandler, riid))               
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXRequestHandler*)(this);          
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXFileObject, riid))           
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXFileObject*)(this);              
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXFileExists, riid))           
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXFileExists*)(this);              
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXFileStat, riid))             
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXFileStat*)(this);                
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXFileMimeMapper, riid))               
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXFileMimeMapper*)(this);          
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXGetFileFromSamePool, riid))          
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXGetFileFromSamePool*)(this);             
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXPendingStatus, riid))                
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXPendingStatus*)(this);           

        //FNH This is probably not needed
        ProcessCacheCompletions(FALSE);
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXRequestHandler, riid))               
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXRequestHandler*)(this);          
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXTimeoutSettings, riid))              
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXTimeoutSettings*)(this);         
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXClientAuthResponse, riid))           
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXClientAuthResponse*)(this);              
        return HXR_OK;                                  
    }
    if (IsEqualIID(IID_IHXHTTPRedirect, riid))           
    {                                                   
        AddRef();                                       
        *ppvObj = (IHXHTTPRedirect*)(this);              
        return HXR_OK;                                  
    }

    *ppvObj = NULL;                             
    return HXR_NOINTERFACE;                     
}

/*
BEGIN_INTERFACE_LIST(CHTTPFileObject)
    INTERFACE_LIST_ENTRY(IID_IHXRequestHandler, IHXRequestHandler)
    INTERFACE_LIST_ENTRY(IID_IHXFileObject, IHXFileObject)
    INTERFACE_LIST_ENTRY(IID_IHXFileExists, IHXFileExists)
    INTERFACE_LIST_ENTRY(IID_IHXFileStat, IHXFileStat)
    INTERFACE_LIST_ENTRY(IID_IHXFileMimeMapper, IHXFileMimeMapper)
    INTERFACE_LIST_ENTRY(IID_IHXGetFileFromSamePool, IHXGetFileFromSamePool)
    INTERFACE_LIST_ENTRY(IID_IHXPendingStatus, IHXPendingStatus)
    INTERFACE_LIST_ENTRY(IID_IHXTimeoutSettings, IHXTimeoutSettings)
    INTERFACE_LIST_ENTRY(IID_IHXClientAuthResponse, IHXClientAuthResponse)
    INTERFACE_LIST_ENTRY(IID_IHXRequestHandler, IHXRequestHandler)
END_INTERFACE_LIST
*/

CHTTPFileObject::CHTTPFileObject()
    : m_LastError (HXR_OK)

    , m_pCommonClassFactory(NULL)
    , m_pPreferences(NULL)
    , m_pScheduler(NULL)
    , m_pRegistry(NULL)
    , m_pContext(NULL)
    , m_pOptions(NULL)
    , m_pInterruptState(NULL)
#ifdef _MACINTOSH    
    , m_pReadDoneBuffer(NULL)
    , m_uReadDoneStatus(HXR_OK)
    , m_bReadDoneToBeProcessed(FALSE)
#endif        
    , m_pErrorMessages(NULL)

    , m_bMimeResponsePending(FALSE)
    , m_pMimeMapperResponse(NULL)

    , m_bFileExistsResponsePending(FALSE)
    , m_pFileExistsResponse(NULL)

    , m_bStatPending(FALSE)
    , m_pFileStatResponse(NULL)

    , m_bInitResponsePending(FALSE)
    , m_pFileResponse (NULL)

    , m_pFileSystem(NULL)

    , m_bTCPReadPending(FALSE)
    , m_pTCPResponse(NULL)

    , m_szBaseURL(NULL)

    , m_pFilename(NULL)
    , m_pPath(NULL)
    , m_pHost(NULL)

    , m_ulFlags(0)
    , m_pRequest(NULL)
    , m_pRequestHeadersOrig(NULL)
    , m_pParams(NULL)

    , m_pLanguage(NULL)

    , m_bAuthorized(FALSE)

    , m_pCallback(NULL)

    , m_bSeekPending (FALSE)
    , m_bInitPending (FALSE)
    , m_bGetProxyInfoPending(FALSE)

    , m_bInitialized(FALSE)
    , m_bInDestructor(FALSE)

    , m_ulCurrentReadPosition (0)

    , m_pDecoder(NULL)
    , m_pChunkyRes(NULL)

    , m_nPort(DEF_HTTP_PORT)

    , m_nRequestTime(0) // used to trigger request time-outs
    , m_bSocketReadTimeout(FALSE) // a socket read timeout has occurred
    , m_nConnTimeout(DEF_HTTP_CONNECT_TIMEOUT)
    , m_nServerTimeout(DEF_HTTP_SERVER_TIMEOUT)
    , m_bDisableConnectionTimeOut(FALSE)
    , m_bConnTimedOut(FALSE)
    , m_bMangleCookies(FALSE)
    , m_pMangledCookies(NULL)
    , m_bShoutcast(FALSE)
    , m_bConvertFailedSeeksToLinear(TRUE)

/**************************/
/* These member variables need to go in a struct
 * if we wanna keep HTTP connection and related downloaded
 * stuff live after the http object is destroyed.
 * This is needed in the case when the first ff plug-in realizes
 * that the file is not of its type and we then load the next
 * related plug-in which then connects to the same URL. We do not
 * want to start downloading from scratch and instead want to use data
 * that had been dowloaded during the lifetime of earlier plug-in
 */
    , m_pSocket(NULL)

    , m_bHTTP1_1(TRUE)

    , m_bConnectDone(FALSE)
    , m_bWriteDone(FALSE)
    , m_bReadHeaderDone(FALSE)
    , m_bReadContentsDone(FALSE)

    , m_bKnowContentSize(FALSE)
    , m_nContentSize(0)
    , m_bEncoded(FALSE)

    , m_bChunkedEncoding(FALSE)
    , m_pChunkedEncoding(NULL)

    , m_nContentRead(0)

    , m_nTotalRequestSize(0)
    , m_nRequestWritten(0)

    , m_nHeaderRead(0)

    , m_strHost()
    , m_strRequest()
    , m_strResource()
    , m_strMimeType()

    , m_bUseProxy(FALSE)
    , m_strProxyHost()
    , m_strProxyResource()
    , m_nProxyPort(DEF_HTTP_PORT)
    , m_uMaxRecursionLevel(MAX_RECURSION_LEVEL)
    , m_uRecursionCount(0)
    , m_bInReadDone(FALSE)
    , m_pCookies(NULL)
    , m_pCookies2(NULL)
    , m_pPAC(NULL)
    , m_pPACInfoList(NULL)
    , m_PACInfoPosition(0)
    , m_bOnServer(FALSE)
    , m_pCacheEntry(NULL)
    , m_pReadBuffer(NULL)
    , m_bCached(FALSE)
    , m_ulCreateTime(0)
    , m_ulExpiryTime(0)
    , m_ulLastModTime(0)
    , m_bReadPending(FALSE)
    , m_bCacheEnabled(TRUE)
    , m_ulCacheMaxSize(DEFAULT_MAX_CACHE_SIZE)
    , m_pCacheFile(NULL)
    , m_ulCutOffDate(0)
    , m_bMirroredServers(FALSE)
    , m_pRedirectResponse(NULL)
    , m_bSaveToFile( FALSE )
    , m_ulOffset(0)
    , m_nPostDataSize(0)
    , m_bPartialData(FALSE)
    , m_bUseHTTPS(FALSE)
    
#ifdef _IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG
    , m_bSupportsByteRanges(FALSE)
    , m_bDisableByteRanges(TRUE)
#else  //_IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG
    , m_bSupportsByteRanges(TRUE)
    , m_bDisableByteRanges(FALSE)
#endif //_IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG
    , m_bExternalByteRangeRequests(FALSE)
    , m_uByteRangeSeekOffset(0)
    , m_bByteRangeSeekPending(FALSE)
    , m_bCheckingWhetherByteRangeWorks(FALSE)
    , m_bServerPresumablyWorksWithByteRangeRequests(TRUE)
    , m_bClosed(FALSE)
    , m_pLastHeader(NULL)
    , m_ulLastHeaderSize(0)
/*************************/
{
}

void 
CHTTPFileObject::InitObject
(
    char*                       szBaseURL,
    IHXFileSystemObject*       pFS, 
    IUnknown*                   pContext,
    IHXValues*			pOptions
)
{
    LOGX ((szDbgTemp, "InitObject(%s)", NULLOK(szBaseURL)));
    if (szBaseURL)
    {
        m_szBaseURL = new_string(szBaseURL);
    }

    if (pFS)
    {
        m_pFileSystem = pFS;
        m_pFileSystem->AddRef();
    }

    if (pContext)
    {
        m_pContext = pContext;
        m_pContext->AddRef();

        m_pContext->QueryInterface(IID_IHXScheduler, (void**) &m_pScheduler);
        m_pContext->QueryInterface(IID_IHXCommonClassFactory, (void **)&m_pCommonClassFactory);
        m_pContext->QueryInterface(IID_IHXPreferences, (void **)&m_pPreferences);
        m_pContext->QueryInterface(IID_IHXRegistry, (void**)&m_pRegistry);
        m_pContext->QueryInterface(IID_IHXErrorMessages, (void**)&m_pErrorMessages);
	m_pContext->QueryInterface(IID_IHXInterruptState, (void**)&m_pInterruptState);
	m_pContext->QueryInterface(IID_IHXCookies, (void**)&m_pCookies);   
	m_pContext->QueryInterface(IID_IHXCookies2, (void**)&m_pCookies2);   

	// Figure out if we're on the server
	IHXServerControl* pServerControl = NULL;
	m_pContext->QueryInterface(IID_IHXServerControl, (void**)&pServerControl);   
	if (pServerControl)
	{
	    m_bOnServer = TRUE;
	}
	HX_RELEASE(pServerControl);
    }

    if (pOptions)
    {
	m_pOptions = pOptions;
	m_pOptions->AddRef();
    }

    //
    // get proxy info for HTTP protocol
    //

    IHXBuffer* pBuffer = NULL;
    UINT32 ulValue = 0;

    if (m_pPreferences)
    {
	IHXBuffer* pBuffer = NULL;

        // Get Language preference, if available
        if (m_pLanguage==NULL)
        {
            IHXRegistry* pRegistry = NULL;

            if (m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pRegistry) == HXR_OK)
            {
                CHXString strTemp;

                strTemp.Format("%s.%s",HXREGISTRY_PREFPROPNAME,"Language");

                pRegistry->GetStrByName(strTemp, m_pLanguage);
                pRegistry->Release();
            }
        }

        // if the language pref is an empty string, we're not
        // interested
        if (m_pLanguage)
        {
            if ( *(m_pLanguage->GetBuffer()) == '\0')
            {
                HX_RELEASE(m_pLanguage);
            }
        }

        // Get connection timeout, if available
        m_pPreferences->ReadPref("ConnectionTimeout", pBuffer);

        // If the connection timeout is an empty string, we're not
        // interested
        if (pBuffer && *(pBuffer->GetBuffer()) != '\0')
        {
            m_nConnTimeout = (UINT32) (atol((const char*)pBuffer->GetBuffer()) * MILLISECS_PER_SECOND);
        }
        HX_RELEASE(pBuffer);

	// Check the file system options last
	if (m_pOptions &&
	    HXR_OK == m_pOptions->GetPropertyULONG32("ConnectionTimeout", ulValue))
	{
	    m_nConnTimeout = ulValue * MILLISECS_PER_SECOND;
	}

	// Finally, fall back to the default
        if (m_nConnTimeout <= 0)
            m_nConnTimeout = DEF_HTTP_CONNECT_TIMEOUT;
            


        // Get server timeout, if available
        m_pPreferences->ReadPref("ServerTimeout", pBuffer);

        // If the connection timeout is an empty string, we're not
        // interested
        if (pBuffer && *(pBuffer->GetBuffer()) != '\0')
        {
            m_nServerTimeout = (UINT32) (atol((const char*)pBuffer->GetBuffer()) * MILLISECS_PER_SECOND);
        }
        HX_RELEASE(pBuffer);

	// Check the file system options last
	if (m_pOptions &&
	    HXR_OK == m_pOptions->GetPropertyULONG32("ServerTimeout", ulValue))
	{
	    m_nServerTimeout = ulValue * MILLISECS_PER_SECOND;
	}

	// Finally, fall back to the default
        if (m_nServerTimeout <= 0)
            m_nServerTimeout = DEF_HTTP_SERVER_TIMEOUT;

	// Find out if Cookie Mangling is enabled
	if (m_pOptions &&
	    HXR_OK == m_pOptions->GetPropertyULONG32("MangleCookies", ulValue))
	{
	    m_bMangleCookies = (BOOL)ulValue;
	}

//      if (szBaseURL && ::strncmp (szBaseURL, "http:", 5) == 0)
        {
            CacheSupport_InitObject();
        }
    }

    m_pChunkyRes = new CChunkyRes;
    
    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "NewChunky %lx\n", m_pChunkyRes);
        fclose(fp);
    }
    #endif

    if (m_bOnServer)
    {
	m_pChunkyRes->DisableDiskIO();
    }

#if defined(HELIX_FEATURE_HTTP_GZIP)
    m_pDecoder = new CDecoder;
    if (m_pDecoder && m_pChunkyRes)
    {
	m_pDecoder->SetOutputSink(m_pChunkyRes);
    }
#endif

    m_pCallback = HTTPFileObjCallback::CreateObject();
    if (m_pCallback)
    {
        m_pCallback->InitObject(this);

        // XXXkshoop This is illegal! (it is not an interface pointer..)
        //
        m_pCallback->AddRef();
    }
}

CHTTPFileObject::~CHTTPFileObject()
{
    LOGX ((szDbgTemp, "FileObject destructor (%s)", m_bClosed?"Closed":"Not closed") );
    if(m_bInDestructor)
    {
        return;
    }

    m_bInDestructor = TRUE;

    Close();
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Init
 *  Purpose:
 *      Associates a file object with the file response object it should
 *      notify of operation completness. This method should also check
 *      for validity of the object (for example by opening it if it is
 *      a local file).
 */
STDMETHODIMP CHTTPFileObject::Init
(
    ULONG32             /*IN*/  ulFlags,
    IHXFileResponse*   /*IN*/  pFileResponse
)
{
    LOG ("Init()");
    HX_RESULT   theErr  = HXR_OK;
    HX_RESULT   lResult = HXR_OK;
    char*       pTemp   = NULL;
    char*       pRes    = NULL;

    HX_RELEASE(m_pFileResponse);

    m_pFileResponse = pFileResponse;
    if (m_pFileResponse)
    {
        m_pFileResponse->AddRef();
    }

 /* This is temporary for Flash fileformat on Mac. We need to set a really low 
 * value for recursion guard for flash fileformat since it puts everything
 * on freaki'n stack thereby blowing the system stack limit on Mac (looks like
 * there is one since it corrupts the heap) very easily.
 */   
#if defined (_MACINTOSH)
    IHXGetRecursionLevel* pGet         = NULL;
    HX_RESULT              lThisResult  = HXR_OK;
    lThisResult = m_pFileResponse->QueryInterface(IID_IHXGetRecursionLevel, 
                                       (void**) &pGet);
    if (lThisResult == HXR_OK)
    {
            m_uMaxRecursionLevel = pGet->GetRecursionLevel();
            pGet->Release();
    }
#endif   

    if (m_bInitialized)
    {
        if (m_LastError == HXR_OK)
        {
            /* If we have already opened a file, then seek back
             * to zero during re-initialization
             */
            m_ulCurrentReadPosition = 0;
            m_bServerPresumablyWorksWithByteRangeRequests = TRUE;

            m_pFileResponse->InitDone(HXR_OK);
            return HXR_OK;
        }
        else
        {
            m_pFileResponse->InitDone(HXR_FAILED);
            return HXR_FAILED;
        }
    }

    theErr = _OpenFile(m_pFilename, ulFlags);

    if (HXR_OK == theErr || HXR_WOULD_BLOCK == theErr)
    {
        if (m_bCached)
        {
            LOG ("    Calling InitDone(OK) [cached]");
            m_ulCurrentReadPosition = 0;
            m_bServerPresumablyWorksWithByteRangeRequests = TRUE;
            m_pFileResponse->InitDone(HXR_OK);
        }
        else
        {
            m_bInitResponsePending = TRUE;
        }
    }
    else
    {
        m_pFileResponse->InitDone(HXR_FAILED);
    }

    return theErr;
}

/************************************************************************
 *  Method:
 *      IHXFileObject::GetFilename
 *  Purpose:
 *      Returns the filename (without any path information) associated
 *      with a file object.
 */
STDMETHODIMP CHTTPFileObject::GetFilename
(
    REF(const char*) /*OUT*/ pFilename
)
{
    // Find the separator character before the file name
    pFilename = ::strrchr(m_pFilename, OS_SEPARATOR_CHAR);

    // File may not be local so try a '/' separator
    // it could also be a MAC on the :// protocol point...
    if (pFilename == NULL ||
	( *pFilename == ':' && *(pFilename + 1) == '/' )  )
    {
        pFilename = ::strrchr(m_pFilename, '/');
    }

    if (pFilename != NULL) // Found
    {
        // File name starts after the separator charactor
        pFilename++;
    }
    else // Not found
    {
        pFilename = m_pFilename;
    }

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Close
 *  Purpose:
 *      Closes the file resource and releases all resources associated
 *      with the object.
 */
STDMETHODIMP CHTTPFileObject::Close()
{
    LOGX ((szDbgTemp, "Close(%s)", m_pFilename));

    if (m_bGetProxyInfoPending)
    {
	m_bGetProxyInfoPending = FALSE;
	m_pPAC->AbortProxyInfo(this);
    }

    HX_RELEASE(m_pFileSystem);
    HX_RELEASE(m_pRequest);
    HX_RELEASE(m_pRequestHeadersOrig);
    HX_RELEASE(m_pParams);
    HX_RELEASE(m_pRedirectResponse);
    HX_RELEASE(m_pCacheFile);        
    HX_RELEASE(m_pInterruptState);
    HX_RELEASE(m_pRegistry);
    HX_RELEASE(m_pCookies);
    HX_RELEASE(m_pCookies2);
    HX_RELEASE(m_pPAC);
    HX_RELEASE(m_pMangledCookies);
    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pOptions);

    // If there is a pending callback, be sure to remove it!
    if (m_pCallback && m_pCallback->m_ulPendingCallbackID && m_pScheduler)
    {
        m_pScheduler->Remove(m_pCallback->m_ulPendingCallbackID);
    }

    HX_RELEASE(m_pCallback);

    HX_RELEASE(m_pScheduler);
    HX_RELEASE(m_pCommonClassFactory);
    HX_RELEASE(m_pErrorMessages);

    if (m_pTCPResponse)
    {
        m_pTCPResponse->m_pOwner = NULL;
        HX_RELEASE(m_pTCPResponse);
    }

    HX_RELEASE(m_pSocket);

    HX_RELEASE(m_pMimeMapperResponse);
    HX_RELEASE(m_pFileExistsResponse);

    HX_RELEASE(m_pFileStatResponse);
    HX_RELEASE(m_pPreferences);

    HX_RELEASE(m_pLanguage);

    HX_VECTOR_DELETE(m_szBaseURL);

    HX_VECTOR_DELETE(m_pFilename);
    HX_VECTOR_DELETE(m_pPath);
    HX_VECTOR_DELETE(m_pHost);

#if defined(HELIX_FEATURE_HTTP_GZIP)
    HX_DELETE(m_pDecoder);
#endif
    HX_DELETE(m_pChunkyRes);

    if (m_pChunkedEncoding)
    {
	HX_VECTOR_DELETE(m_pChunkedEncoding->buf);
	HX_DELETE(m_pChunkedEncoding);
    }

    while (m_pPACInfoList && m_pPACInfoList->GetCount())
    {
	PACInfo* pPACInfo = (PACInfo*)m_pPACInfoList->RemoveHead();
	HX_DELETE(pPACInfo);
    }
    HX_DELETE(m_pPACInfoList);

    if (!m_bInDestructor)
    {
	AddRef();
	if (m_pFileResponse)
	{
	    // Call CloseDone()
            m_pFileResponse->CloseDone(HXR_OK);
            // Keeps us from getting into an infinite loop when
            // we have two FileObjects on the same file which causes the
            // ref counts on the objects to be such that this's m_pFileResponse
            // object drops the ref count on this which causes it to destruct
            // which causes it to call ::Close() which *used to* again call
            // HX_RELEASE(m_pFileResponse) which *used to* again release *this which
            // called ::Close()...etc until the stack blew up.  This fix
            // keeps it from spinning out of control by setting m_pFileResponse
            // to NULL before releasing it via temp ptr:
            IHXFileResponse* pTempFileResponse = m_pFileResponse;
            m_pFileResponse = NULL;
            HX_RELEASE(pTempFileResponse);
	}
	Release();
    }
    else if (m_pFileResponse)
    {
	HX_RELEASE(m_pFileResponse);
    }

    HX_VECTOR_DELETE(m_pLastHeader);
    
    // Flag to let us know that we've been closed
    m_bClosed = TRUE;

    return HXR_OK;
}

void
CHTTPFileObject::CallReadDone(HX_RESULT status, IHXBuffer* pBuffer)
{
    m_bInReadDone = TRUE;

    // Let the file response sink know about the buffer...
    HX_ASSERT(m_pFileResponse) ;
    if (m_pFileResponse)
    {
        #ifdef CREATE_LOG_DUMP
        FILE *fp = fopen(LOG_DUMP_FILE, "a+");
        if (fp)
        {
            fprintf(fp, "ReadDone %lx at %ld %d\n", m_pChunkyRes, m_ulCurrentReadPosition, pBuffer ? pBuffer->GetSize() : 0);
            fclose(fp);
        }
        #endif
        
        m_pFileResponse->ReadDone(status, pBuffer);
    }

    m_bInReadDone = FALSE;

    if (m_uRecursionCount > 0)
    {
        m_uRecursionCount--;
    }
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Read
 *  Purpose:
 *      Reads a buffer of data of the specified length from the file
 *      and asynchronously returns it to the caller via the
 *      IHXFileResponse interface passed in to Init.
 */
STDMETHODIMP CHTTPFileObject::Read(ULONG32 ulCount)
{
    HX_RESULT           lResult      = HXR_OK;
    static UINT32       ulReadCount  = 0;
      
    LOGX((szDbgTemp, "Read #%03lu, Size is %4lu, Recurs is %2u, FileObj is '%s'",
          ++ulReadCount, ulCount, m_uRecursionCount + 1, NULLOK(m_pFilename)));

    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "Read %lx from %ld %ld\n", m_pChunkyRes, m_ulCurrentReadPosition, ulCount);
        fclose(fp);
    }
    #endif

    if (m_LastError)
    {
        return m_LastError;
    }

    /* It is illegal to call read if a seek is pending */
    if (m_bSeekPending)
    {
        #ifdef CREATE_LOG_DUMP
        FILE *fp = fopen(LOG_DUMP_FILE, "a+");
        if (fp)
        {
            fprintf(fp, "Cancel Read: Seek Pending %lx %ld\n", m_pChunkyRes, ulCount);
            fclose(fp);
        }
        #endif

        CallReadDone(HXR_SEEK_PENDING ,NULL);
        return HXR_UNEXPECTED;
    }

    /* mark it as a pending read request..
     * We do this since a call to readdone may result in a call to read
     * which may mess up the sequence in which we read data and pass it to
     * the caller (specially when some reads are pendiing
     */
    m_PendingReadList.AddTail((void*) ulCount);


    if (m_bInReadDone)
    {
        m_uRecursionCount++;
    }

    if (m_uRecursionCount > m_uMaxRecursionLevel)
    {
        if (m_pCallback && !m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending     = TRUE;
            m_pCallback->m_ulPendingCallbackID  = m_pScheduler->RelativeEnter(m_pCallback, 0);
        }

        return HXR_OK;
    }

    // This adds greatly to the stack depth when Read()s are recursed --fnh
#ifdef _MACINTOSH
    /* 
     * We do not call ProcessPending reads at interrupt time on Mac
     * This is because we use ChunkyRes which writes/reads data to
     * disk. We do not want it to happen at interrupt time since
     * chunkyres has synchronous interface and it makes synchronous
     * file io calls. We should someday change chunkyres interface to
     * be async and also modify it to use aysnc file io to read/write
     */
    if (m_pInterruptState && m_pInterruptState->AtInterruptTime())
    {
        if (m_pCallback && !m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending     = TRUE;
            m_pCallback->m_ulPendingCallbackID  =
                        m_pScheduler->RelativeEnter(m_pCallback, 0);
        }
        return HXR_OK;  
    }
    else
    {
        lResult = ProcessPendingReads();
    }
#else
    lResult = ProcessPendingReads();
#endif  

    return lResult;
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Write
 *  Purpose:
 *      Writes a buffer of data to the file and asynchronously notifies
 *      the caller via the IHXFileResponse interface passed in to Init,
 *      of the completeness of the operation.
 */
STDMETHODIMP CHTTPFileObject::Write(IHXBuffer* pBuffer)
{
    HX_RESULT nRetVal = HXR_FAILED;

    if (m_pSocket && m_nPostDataSize && pBuffer)
    {
	nRetVal = m_pSocket->Write(pBuffer);

	if(nRetVal == HXR_OK)
	{
	    if(m_nPostDataSize >= pBuffer->GetSize())
		m_nPostDataSize -= pBuffer->GetSize();
	    else
		m_nPostDataSize = 0;
	}
    }

    return nRetVal;
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Seek
 *  Purpose:
 *      Seeks to an offset in the file and asynchronously notifies
 *      the caller via the IHXFileResponse interface passed in to Init,
 *      of the completeness of the operation.
 */
STDMETHODIMP CHTTPFileObject::Seek(ULONG32 ulOffset, BOOL bRelative)
{
    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "Seek %lx %ld\n", m_pChunkyRes, ulOffset);
        fclose(fp);
    }
    #endif
    
    if (m_LastError)
    {
        return m_LastError;
    }

    #ifdef CREATE_LOG_DUMP
    if (m_PendingReadList.GetCount())
    {
        FILE *fp = fopen(LOG_DUMP_FILE, "a+");
        if (fp)
        {
            fprintf(fp, "_Reopen %lx pending reads %ld\n", m_pChunkyRes, m_PendingReadList.GetCount());
            fclose(fp);
        }
    }
    #endif

    while (m_PendingReadList.GetCount())
    {
        m_PendingReadList.RemoveHead();
        m_pFileResponse->ReadDone(HXR_CANCELLED, NULL);
    }
    
    if (bRelative)
    {
        m_ulCurrentReadPosition += ulOffset;
    }
    else
    {
        m_ulCurrentReadPosition = ulOffset;
    }

    /* check if there were any pending reads */
    if (m_bSeekPending || m_bReadPending) 
    {
        #ifdef CREATE_LOG_DUMP
        FILE *fp = fopen(LOG_DUMP_FILE, "a+");
        if (fp)
        {
            fprintf(fp, "Cancel Seek: Seek Pending %lx %ld\n", m_pChunkyRes, ulOffset);
            fclose(fp);
        }
        #endif
        
        m_bSeekPending  = FALSE;
        m_pFileResponse->SeekDone(HXR_CANCELLED);
    }

    if (m_pChunkyRes->HasPartialData(1, m_ulCurrentReadPosition))
    {
        // Don't call SeekDone until we are actaully done with the seek
        if (m_bSupportsByteRanges)
	{
	    _EnsureThatWeAreReadingWisely();
            
            // m_bByteRangeSeekPending means we reconnecting the socket 
            // a new location.
            if (m_bByteRangeSeekPending)
                m_bSeekPending = TRUE;
            else
            {
                #ifdef CREATE_LOG_DUMP
                FILE *fp = fopen(LOG_DUMP_FILE, "a+");
                if (fp)
                {
                    fprintf(fp, "SeekDone no reconnect %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                    fclose(fp);
                }
                #endif
                
                m_pFileResponse->SeekDone(HXR_OK);
            }
	}
        else
        {
            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "SeekDone %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                fclose(fp);
            }
            #endif
            
            m_pFileResponse->SeekDone(HXR_OK);
        }
    }
    else
    {
        if (m_bSupportsByteRanges)
        {
	    // signal the caller about the end of content
	    if (m_ulCurrentReadPosition == m_nContentRead)
	    {
                #ifdef CREATE_LOG_DUMP
                FILE *fp = fopen(LOG_DUMP_FILE, "a+");
                if (fp)
                {
                    fprintf(fp, "SeekDone Gaps read=write %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                    fclose(fp);
                }
                #endif
	        
                m_pFileResponse->SeekDone(HXR_OK);
	    }
	    else if (m_bKnowContentSize && m_ulCurrentReadPosition == m_nContentSize)
            {
                #ifdef CREATE_LOG_DUMP
                FILE *fp = fopen(LOG_DUMP_FILE, "a+");
                if (fp)
                {
                    fprintf(fp, "SeekDone gaps read=end %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                    fclose(fp);
                }
                #endif
		
                m_pFileResponse->SeekDone(HXR_OK);
            }
            else
	    {
                // We need to reconnect if:
                // 1. The read pointer is behind the write pointer and there are gaps OR
                // 2. The write pointer is behind the read pointer with too many bytes to simply wait for
                if (!m_bByteRangeSeekPending)
                {
                    if (m_ulCurrentReadPosition < m_nContentRead ||
                        m_ulCurrentReadPosition - m_nContentRead > BYTE_RANGE_SEEK_THRESHHOLD)
                    {
                        _HandleByteRangeSeek(m_ulCurrentReadPosition);
                    }
                }                    
		m_bSeekPending = TRUE;
	    }
        }
        else
        {
            /* is it a valid value to seek to?*/
	    if (m_bReadContentsDone)
	    {
	        // signal the caller about the end of content
	        if (m_ulCurrentReadPosition == m_nContentRead)
	        {
		    m_pFileResponse->SeekDone(HXR_OK);
	        }
	        else
	        {
		    HX_ASSERT(m_ulCurrentReadPosition > m_nContentRead);
		    m_pFileResponse->SeekDone(HXR_FAILED);
	        }
            }
            else /* add it to the seek pending queue...*/
            {
	        m_bSeekPending = TRUE;
            }
        }
    }

    return (HXR_OK);
}



/************************************************************************
 * Method:
 *      IHXFileObject::Stat
 * Purpose:
 *      Collects information about the file that is returned to the
 *      caller in an IHXStat object
 */
STDMETHODIMP CHTTPFileObject::Stat(IHXFileStatResponse* pFileStatResponse)
{
    LOGX ((szDbgTemp, "Stat(%s)", m_pFilename));
    /*
     * XXXGH...Need to get real statistics
     */
    if(!m_bReadHeaderDone)
    {
        m_bStatPending = TRUE;
        m_pFileStatResponse = pFileStatResponse;
        m_pFileStatResponse->AddRef();
    }
    else
    {
        pFileStatResponse->StatDone(HXR_OK, m_nContentSize, 0, 0, 0, 0);
    }

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *      IHXFileObject::Advise
 *  Purpose:
 *      To pass information to the File Object
 */
STDMETHODIMP CHTTPFileObject::Advise(ULONG32 ulInfo)
{
    HX_RESULT pnr = HXR_OK;

    // XXXJEFFA it's possible that for cached HTTP we want to
    // return HXR_OK from this instead of prefer linear since
    // the file is local in the cache but that's an optimization
    // we can make later JEFFA 11/8/98

    // disable the HTTP1.1 support if the file format
    // doesn't support async seek(i.e. AVI)
    if (ulInfo == HXR_ADVISE_NOASYNC_SEEK)
    {
        m_bDisableByteRanges = TRUE;
        m_bSupportsByteRanges = FALSE;
    }
    else if (ulInfo == HX_FILEADVISE_RANDOMACCESS)
    {
        if (m_bSupportsByteRanges && !IsLiveStream(m_strMimeType))
            pnr = HXR_OK;
        else
            pnr = HXR_ADVISE_PREFER_LINEAR;
    }
    else if (HX_FILEADVISE_RANDOMACCESSONLY == ulInfo)
    {
        m_bConvertFailedSeeksToLinear = FALSE;
    }
    else if (HX_FILEADVISE_ANYACCESS == ulInfo)
    {
        m_bConvertFailedSeeksToLinear = TRUE;
    }


    return pnr;
}


/************************************************************************
 *      Method:
 *          IHXFileObject::GetFileObjectFromPool
 *      Purpose:
 *      To get another FileObject from the same pool.
 */
STDMETHODIMP CHTTPFileObject::GetFileObjectFromPool(
    IHXGetFileFromSamePoolResponse* response)
{
    LOGX ((szDbgTemp, "GetFileObjectFromPool(%s)", m_pFilename));

    HX_RESULT           lReturnVal = HXR_FAIL;
    CHTTPFileObject*    pFileObject = NULL;
    IUnknown*           pUnknown = NULL;
    CHXString           sBaseURL;
    INT32               nBeforeFile;

    sBaseURL = m_pFilename;

    nBeforeFile = max
    (
        sBaseURL.ReverseFind('\\'),
        sBaseURL.ReverseFind('/')
    );

    if(nBeforeFile > -1)
    {
        sBaseURL = sBaseURL.Left(nBeforeFile);

        pFileObject = CHTTPFileObject::CreateObject();
        if (pFileObject)
        {
            pFileObject->InitObject
            (
                sBaseURL.GetBuffer(1), 
                m_pFileSystem, 
                m_pContext,
		m_pOptions
            );

            lReturnVal = pFileObject->QueryInterface
            (
                IID_IUnknown, 
                (void**)&pUnknown
            );
        }
	else
	{
            response->FileObjectReady(HXR_OUTOFMEMORY, NULL);
            return HXR_OUTOFMEMORY;
	}
    }

    response->FileObjectReady
    (
        lReturnVal == HXR_OK ? HXR_OK : HXR_FAIL,
        pUnknown
    );

    HX_RELEASE(pUnknown);

    return lReturnVal;
}

// IHXFileExists interface
/************************************************************************
 *      Method:
 *          IHXFileExists::DoesExist
 *      Purpose:
 */
STDMETHODIMP CHTTPFileObject::DoesExist(
                        const char*             /*IN*/  pPath,
                        IHXFileExistsResponse* /*IN*/  pFileExistsResponse)
{
    LOGX ((szDbgTemp, "DoesExist(%s)", pPath));

    HX_RESULT theErr = HXR_OK;
    IHXRequestHandler* pReqHandler = NULL;
    IHXRequest* pRequest = NULL;
    IHXProxyAutoConfig* pPAC = NULL;

    if (m_bInitialized)
    {
        if (m_LastError == HXR_OK)
        {
	    AddNoCacheHeader();
            pFileExistsResponse->DoesExistDone(TRUE);
            return HXR_OK;
        }
        else
        {
            pFileExistsResponse->DoesExistDone(FALSE);
            return HXR_OK; 
        }
    }

    // If we have received a request object, extract from it the
    // full URL. This will include any URL parameters which we 
    // need, but which are not contained in the simple file name 
    // that was passed in as our first parameter. - DPS
    HX_ASSERT(m_pRequest);
    if (m_pRequest)
    {
	m_pRequest->GetURL(pPath);
    }

    // we don't want use PAC if we are within the process of PAC detecting
    if (HXR_OK == pFileExistsResponse->QueryInterface(IID_IHXProxyAutoConfig, (void**)&pPAC))
    {
	theErr = _OpenFile(pPath, HX_FILE_READ | HX_FILE_BINARY | HX_FILE_NOPAC);
    }
    else
    {
	theErr = _OpenFile(pPath, HX_FILE_READ | HX_FILE_BINARY);
    }
    HX_RELEASE(pPAC);

    if (HXR_OK == theErr || HXR_WOULD_BLOCK == theErr)
    {
        if (m_bCached)
        {
            pFileExistsResponse->DoesExistDone(TRUE);
        }
        else
        {
        m_pFileExistsResponse = pFileExistsResponse;
        m_pFileExistsResponse->AddRef();
        m_bFileExistsResponsePending = TRUE;
        }
        return HXR_OK;
    }
    else
    {
        pFileExistsResponse->DoesExistDone(FALSE);
        return HXR_OK; 
    }
}


/*
 *      IHXFileMimeMapper methods
 */
/************************************************************************
 *      Method:
 *          IHXFileMimeMapper::FindMimeType
 *      Purpose:
 */
STDMETHODIMP CHTTPFileObject::FindMimeType(
                const char*                 /*IN*/  pURL,
                IHXFileMimeMapperResponse* /*IN*/  pMimeMapperResponse)
{
    LOGX ((szDbgTemp, "FindMimeType(%s)", pURL));

    HX_RESULT theErr = HXR_OK;

    if (m_bInitialized)
    {
        if (m_LastError == HXR_OK)
        {
            // Determine mime type here!
            const char* pMimeType = NULL;
            if (!m_strMimeType.IsEmpty()) pMimeType = m_strMimeType;

            LOGX ((szDbgTemp, "    MimeTypeFound(%s)", pMimeType));
            pMimeMapperResponse->MimeTypeFound(HXR_OK, pMimeType);
            return HXR_OK;
        }
        else
        {
            LOGX ((szDbgTemp, "    MimeTypeFound(NULL)"));
            pMimeMapperResponse->MimeTypeFound(m_LastError, NULL);
            return HXR_FAILED;
        }
    }

    // If we have received a request object, extract from it the
    // full URL. This will include any URL parameters which we 
    // need, but which are not contained in the simple file name 
    // that was passed in as our first parameter.
    HX_ASSERT(m_pRequest);
    if (m_pRequest)
    {
    m_pRequest->GetURL(pURL);
    }
    
    theErr = _OpenFile(pURL,HX_FILE_READ|HX_FILE_BINARY);

    if (HXR_OK == theErr || HXR_WOULD_BLOCK == theErr)
    {
        if (m_bCached)
        {
            const char* pMimeType = NULL;
            if (!m_strMimeType.IsEmpty())
            {
                pMimeType = m_strMimeType;
            }

            LOGX ((szDbgTemp, "    Cached MimeType='%s'", NULLOK(m_strMimeType)));
            pMimeMapperResponse->MimeTypeFound(HXR_OK, pMimeType);
        }
        else
        {
        m_pMimeMapperResponse = pMimeMapperResponse;
        m_pMimeMapperResponse->AddRef();
        m_bMimeResponsePending = TRUE;
        }
        return HXR_OK;
    }
    else
    {
        pMimeMapperResponse->MimeTypeFound(HXR_INVALID_PATH, NULL);
        return HXR_FAILED;
    }
}

/*
 * IHXPendingStatus methods
 */

/************************************************************************
 *      Method:
 *          IHXPendingStatus::GetStatus
 *      Purpose:
 *          Called by the user to get the current pending status from an object
 */
STDMETHODIMP CHTTPFileObject::GetStatus(REF(UINT16) uStatusCode,
                                        REF(IHXBuffer*) pStatusDesc,
                                        REF(UINT16) ulPercentDone)
{
    /* Default values*/
    uStatusCode     = HX_STATUS_READY;
    pStatusDesc     = 0;
    ulPercentDone   = 0;

    if (!m_bConnectDone)
    {
        uStatusCode     = HX_STATUS_CONTACTING;
        if (!m_strHost.IsEmpty())
        {
            CHXString statusDesc = "Contacting ";
	    statusDesc += m_strHost;
	    statusDesc += "...";
            pStatusDesc = new CHXBuffer;
            pStatusDesc->AddRef();
            pStatusDesc->Set((UCHAR*)(const char*) statusDesc,
                             strlen((const char*)statusDesc)+1);
        }
        ulPercentDone   = 0;
    }
    else if (m_bReadContentsDone)
    {
        uStatusCode     = HX_STATUS_READY;
        ulPercentDone   = 0;
    }
    else if (m_bSeekPending || !m_PendingReadList.IsEmpty())
    {
        uStatusCode = HX_STATUS_BUFFERING;

        ULONG32 ulReadCount = 0;
        if (!m_PendingReadList.IsEmpty())
        {
            ulReadCount = (ULONG32)(PTR_INT)m_PendingReadList.GetHead();
        }

        if (m_ulCurrentReadPosition+ulReadCount)
        {
            ulPercentDone = (UINT16) ((m_nContentRead*100)/(m_ulCurrentReadPosition+ulReadCount));
            ulPercentDone = ulPercentDone <= 100 ? (UINT16) ulPercentDone : 100;
        }
        else
        {
            ulPercentDone = (UINT16) 100;
        }
    }

    return HXR_OK;
}

/************************************************************************
 *      Method:
 *          Private interface::OpenFile
 *      Purpose:
 *          This common method is used from Init() and GetFileObjectFromPool()
 */
HX_RESULT CHTTPFileObject::_OpenFile(const char* url,
                                      ULONG32     ulFlags)
{
    HX_RESULT	    theErr  = HXR_OK;
    HX_RESULT	    lResult = HXR_OK;
    UINT16	    un16Temp = 0;
    char*	    pTemp   = NULL;
    IHXBuffer*	    pBuffer = NULL;
    IHXBuffer*	    pProxyName = NULL;
    IHXBuffer*	    pProxyPort = NULL;  
    IHXProxyManager*   pProxyManager = NULL;

    LOG("_OpenFile");
    LOGX((szDbgTemp, "    URL='%s'", url));
    // Make local copy of url
    CHXString   strTemp = url;
    char*       pTempURL    = strTemp.GetBuffer(strTemp.GetLength());
    char*       pURL	    = NULL;
    char*       pOrigURL    = NULL;

    CHXURL* pCHXURL = new CHXURL(pTempURL);
    if (pCHXURL)
    {
	IHXValues* pHeader = pCHXURL->GetProperties();
	if(pHeader)
	{
	    IHXBuffer* pUrlBuffer = NULL;
	    if(HXR_OK == pHeader->GetPropertyBuffer(PROPERTY_URL, pUrlBuffer) &&
		pUrlBuffer)
	    {
		pURL = ::new_string((const char*)pUrlBuffer->GetBuffer());
		HX_RELEASE(pUrlBuffer);
	    }
	    HX_RELEASE(pHeader);
	}
	delete pCHXURL;
    }

    // if somehow the URL is messed up and the URL parser freaks out, 
    // we fall back to the original URL.
    if (!pURL)
    {
	pURL = ::new_string(pTempURL);
    }

    pOrigURL = pURL;

    if (pOrigURL && (strncasecmp(pOrigURL, "https:", 6) == 0))
    {
	m_bUseHTTPS = TRUE;
    }
    else
    {
	m_bUseHTTPS = FALSE;
    }
    

    // HTTP requires '/' as the element delimiter.
    // (Or at least the TIS proxy does)
    // So change all '\\' to '/'.
    // Also, try not to proccess any parameters.
    pTemp = pURL;
    while(*pTemp && *pTemp!='?' && *pTemp!='#')
    {
        if(*pTemp == '\\')
        {
            *pTemp = '/';
        }
        pTemp++;
    }

    // We always store the entire original URL as the proxy resource!
    m_strProxyResource = pURL;

    m_strHost           = "";
    m_nPort             = DEF_HTTP_PORT;
    m_strResource       = "";

    // if the url's first five characters are "http:"
    // then jump past them... Otherwise, just assume
    // the URL starts with the host... This will allow
    // someone to mount the HTTP file system on the
    // server and proxy another web server... <g>
    // [Modified to look for ':' character - fnh]
    char* pcColon = (char *) HXFindChar (pURL, ':');
    char* pcQuery = (char *) HXFindChar (pURL, '?');
    if (pcColon && (pcQuery == 0 || pcColon < pcQuery))
    {
        pURL = pcColon + 1;
    }

    // Jump past the double whack if present...
    if (HXCompareStrings(pURL, "//", 2) == 0)
    {
        pURL += 2;
    }

    pTemp = (char *)HXFindChar(pURL, '/');

    if (pTemp)
    {
	// Fix for PR104465
	// CHXURL now always unescapes the URL which means we need to make sure that resource is correctly encoded
	// before sending to the server.
	CHXURL::encodeURL(pTemp, m_strResource); //  /* Remainder is resource */

        *pTemp = '\0';
    }

    // Look for the '@' character which means there is a username and/or password.  We skip over this part to the
    // hostname.  The '@' character is reserved according to RFC 1738 show it shouldn't appear in a URL
    pTemp = (char *)HXFindChar (pURL, '@');
    if (pTemp)
        pURL += (pTemp - pURL + 1);

    /* Port (optional) */
    pTemp = (char *)HXFindChar (pURL, ':');
    if (pTemp)
    {
        *pTemp = '\0';
        m_nPort = ::atoi(pTemp+1);

        // port '0' is invalid, but we'll get that if the url had ':' but no port number
        // following. Setting to default http_port in this case will mimic the
        // behaviour of pnm: and rtsp: if no port specified after ':'
        if (m_nPort==0)
        {
            m_nPort = DEF_HTTP_PORT;
        }
    } /* if (pTemp) */

    m_strHost = pURL;

    if (m_pPreferences->ReadPref("HTTPProxyAutoConfig", pBuffer) == HXR_OK)
    {
	un16Temp = atoi((const char*) pBuffer->GetBuffer());
    }
    // previously released Enterprise player may use "ProxyAutoConfig" for
    // HTTP proxy auto config
    else if (m_pPreferences->ReadPref("ProxyAutoConfig", pBuffer) == HXR_OK)
    {
	un16Temp = atoi((const char*) pBuffer->GetBuffer());
    }
    HX_RELEASE(pBuffer);

    // HTTP Proxy Auto Config
    if (un16Temp && !(HX_FILE_NOPAC & ulFlags))
    {
	if (!m_pPAC)
	{
	    m_pContext->QueryInterface(IID_IHXProxyAutoConfig, (void**)&m_pPAC);
	}

	if (m_pPAC && 
	    (!m_pPACInfoList || 0 == m_pPACInfoList->GetCount()))
	{	   
	    theErr = m_pPAC->GetHTTPProxyInfo((IHXProxyAutoConfigCallback*)this,
					      url,
					      m_strHost);
    	}
	// attempt the next proxy info from m_pPACInfoList
	else if (m_pPACInfoList && m_PACInfoPosition)
	{
	    PACInfo* pPACInfo = (PACInfo*)m_pPACInfoList->GetNext(m_PACInfoPosition);
	    if (pPACInfo && pPACInfo->type != PAC_DIRECT)
	    {
		m_bUseProxy = TRUE;
		m_nProxyPort = pPACInfo->ulPort;
		m_strProxyHost = pPACInfo->pszHost;
	    }
	}

	// XXX HP TBD
	// we should attempt the next proxy info from m_pPACInfoList
	if (HXR_WOULD_BLOCK == theErr)
	{
	    m_bGetProxyInfoPending = TRUE;
	    goto cleanup;
	}
    }
    else if (m_pPreferences->ReadPref("HTTPProxySupport", pBuffer) == HXR_OK)
    {
        if (atoi((const char*)pBuffer->GetBuffer()))
        {
            if(m_pPreferences->ReadPref("HTTPProxyHost", pProxyName) == HXR_OK &&
               m_pPreferences->ReadPref("HTTPProxyPort", pProxyPort) == HXR_OK)
            {
                m_nProxyPort = atoi((const char*)pProxyPort->GetBuffer());                              
                m_strProxyHost = (const char*)pProxyName->GetBuffer();

                if (m_strProxyHost.GetLength() > 0 && m_nProxyPort > 0)
                {
		    if (HXR_OK == m_pContext->QueryInterface(IID_IHXProxyManager, (void**)&pProxyManager) &&
			pProxyManager)
		    {
			m_bUseProxy = !(pProxyManager->IsExemptionHost((char*)(const char*)m_strHost));		    
		    }
#if defined(HELIX_FEATURE_PROXYMGR)
		    else
		    {
			pProxyManager = new HXProxyManager();
			pProxyManager->AddRef();

			if (HXR_OK == pProxyManager->Initialize(m_pContext))
			{
			    m_bUseProxy = !(pProxyManager->IsExemptionHost((char*)(const char*)m_strHost));
			}
		    }
#endif /* #if defined(HELIX_FEATURE_PROXYMGR) */
		    HX_RELEASE(pProxyManager);
                }
            }
            HX_RELEASE(pProxyName);
            HX_RELEASE(pProxyPort);
        }
    }
    HX_RELEASE(pBuffer);

    theErr = _OpenFileExt();

cleanup:

    HX_VECTOR_DELETE(pOrigURL);

    return theErr;
}

HX_RESULT
CHTTPFileObject::_OpenFileExt()
{
    HX_RESULT	theErr = HXR_OK;

    CacheSupport_OpenFile();

    if (m_bCached)
    {
        m_bInitPending = FALSE;
    }
    else
    {
        /* connect to the host and start getting the data */
        
        theErr = BeginGet(m_uByteRangeSeekOffset);

        if (!theErr)
        {
            m_bInitPending = TRUE;
        }
    }

    return theErr;
}

/////////////////////////////////////////////////////////////////////////////
//
//      Method:
//
//              CHTTPFileObject::ProcessIdle()
//
//      Purpose:
//
//              Continues retrieval of an http URL asyncronously.
//
//      Parameters:
//
//              None.
//
//      Return:
//
//              HX_RESULT err
//              HXR_OK if things are continuing correctly.
//
//
HX_RESULT CHTTPFileObject::ProcessIdle()
{
    HX_RESULT theErr = HXR_OK;
    HX_RESULT  lResult = HXR_OK;
    const UINT16 bufSize = 0xffff;

#ifdef _MACINTOSH
    /* 
     * If we have a pending readdone callback from interrupt time
     * handle it now
     */
    if (m_bReadDoneToBeProcessed)
    {
        m_bReadDoneToBeProcessed = FALSE;
        HX_RESULT theReadStatus = m_uReadDoneStatus;
        IHXBuffer* pReadBuffer = m_pReadDoneBuffer;
        m_pReadDoneBuffer = NULL;
        m_uReadDoneStatus = HXR_OK;
        ReadDone(theReadStatus, pReadBuffer);
        HX_RELEASE(pReadBuffer);
    }
#endif

    // ProcessIdle doesn't get scheduled until the connection is established,
    // unless a connection timeout has been trigged.
    if (!m_bConnectDone && !m_bDisableConnectionTimeOut)
    {
	if (m_bOnServer)
	{
	    ReportConnectionTimeout();
	}

        // connection failed, (ie we've hit a connect time-out)
        m_bConnTimedOut = TRUE;
        theErr = HXR_NET_CONNECT;
        HX_RELEASE(m_pSocket);
    }

    if (m_pSocket)
    {
        if (!m_bWriteDone)
        {
            UINT16 actual = m_nTotalRequestSize - m_nRequestWritten;
            IHXBuffer* pBuffer = new CHXBuffer;
            pBuffer->AddRef();
            pBuffer->Set((UCHAR*) (const char*) m_strRequest.Mid(m_nRequestWritten),
                         (ULONG32) actual);

            lResult = m_pSocket->Write(pBuffer);
            if (lResult == HXR_OK)
            {
                m_nRequestWritten += actual;

                if (m_nRequestWritten == m_nTotalRequestSize)
                {
		    m_bWriteDone = TRUE;
                }
            }
            else
            {
                theErr = HXR_SERVER_DISCONNECTED;
            }

            pBuffer->Release();
        }
	else if(m_nPostDataSize)
	{
	    // This prompts client to write Post Data.
	    if (m_pFileResponse)
	    {
                theErr = m_pFileResponse->WriteDone(HXR_OK);
	    }
	}
        else if (!m_bReadContentsDone && !m_bTCPReadPending && !m_bSocketReadTimeout)
        {
            m_bTCPReadPending   = TRUE;
            m_bSocketReadTimeout = FALSE;
            m_nRequestTime = HX_GET_TICKCOUNT();

            // Use copy of socket since the member var
            // can be released in ReadDone()
            //
            IHXTCPSocket* pSocket = m_pSocket;
            pSocket->AddRef();

            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "ReadSocketReq %lx from %ld\n", m_pChunkyRes, m_nContentRead);
                fclose(fp);
            }
            #endif
            
            lResult = pSocket->Read(bufSize);
            
            pSocket->Release();
        }
#if 1 // time-out check to detect lost connection..
        else if (m_bTCPReadPending &&
                 CALCULATE_ELAPSED_TICKS(m_nRequestTime,HX_GET_TICKCOUNT()) > m_nServerTimeout)
        {
            // we've timed-out waiting for a read response on the socket..
            m_bSocketReadTimeout = TRUE;
            m_bReadContentsDone = TRUE;
            
	    ReportServerTimeout();

	    theErr = HXR_SERVER_TIMEOUT;
	    HX_RELEASE(m_pSocket);
        }
#endif
    }

    /* We only consider the server to be disconnected if we are yet in
     * the initialization state. We need to mask this error otherwise
     */
    if (!m_bInitPending && (lResult != HXR_OK))
    {
        m_bReadContentsDone = TRUE;
        theErr = HXR_OK;
    }

    // If we have read enough to process are "seek".  We did a small
    // seek forward, so instead of doing a reconnect, we just wait
    // until we receive enough data from the socket.
    if (!theErr && m_bSeekPending && !m_bByteRangeSeekPending)
    {
        if (m_ulCurrentReadPosition <= m_nContentRead)
        {
            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "SeekDone buffer done %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                fclose(fp);
            }
            #endif
            
            m_bSeekPending  = FALSE;
            m_pFileResponse->SeekDone(HXR_OK);
        }
        else if (m_bReadContentsDone)
        {
            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "SeekDone fail..eof %lx %ld\n", m_pChunkyRes, m_ulCurrentReadPosition);
                fclose(fp);
            }
            #endif
            
            m_bSeekPending  = FALSE;
            m_pFileResponse->SeekDone(HXR_FAILED);
        }
    }


    HX_RESULT ReadErr = ProcessPendingReads();
    if(!theErr)
    {
        theErr = ReadErr;
    }

    /* Do we need to read more data */
    if (!theErr && !m_bReadContentsDone && m_pCallback && !m_pCallback->m_bCallbackPending)
    {
                m_pCallback->m_bCallbackPending     = TRUE;
                m_pCallback->m_ulPendingCallbackID  =
                                            m_pScheduler->RelativeEnter(m_pCallback, 50);
    }

    // Preserve previous errors, if any..
    if (FAILED(m_LastError))
    {
        theErr = m_LastError;
    }
    /* Report this error - crucial */
    else if (theErr)
    {
        /* This value is checked in every function */
        m_LastError = theErr;
    }

    if (m_bInitPending)
    {
                /* report InitDone if we have any error */
                if (theErr)
                {
		    if (m_pPACInfoList && m_pPACInfoList->GetCount() && m_PACInfoPosition)
		    {    
			m_bConnTimedOut = FALSE;
			m_bUseProxy = FALSE;
			m_nProxyPort = 0;
			m_strProxyHost.Empty();

			m_bConvertFailedSeeksToLinear = TRUE;

			theErr = _ReOpen();
			m_LastError = theErr;
		    }
		    else
		    {			
			m_bInitPending = FALSE;
			m_bInitialized = TRUE;

			if (m_bInitResponsePending && m_pFileResponse)
			{
				    m_bInitResponsePending = FALSE;
				    m_pFileResponse->InitDone(HXR_FAILED);
			}

			if (m_bFileExistsResponsePending && m_pFileExistsResponse)
			{
				    m_bFileExistsResponsePending = FALSE;
				    m_pFileExistsResponse->DoesExistDone(FALSE);
				    m_pFileExistsResponse->Release();
				    m_pFileExistsResponse = 0;
			}

			if (m_bMimeResponsePending && m_pMimeMapperResponse)
			{
				    m_bMimeResponsePending = FALSE;
				    m_pMimeMapperResponse->MimeTypeFound(theErr, NULL);
				    // MimeTypeFound() may cause us to close if the Response object
				    // decides it doesn't like our mimetype and shuts us down.
				    // Therefore, m_pMimeMapperResponse could be NULL now..
				    HX_RELEASE(m_pMimeMapperResponse);
			}
		    }
                }
                /* report InitDone if we have read some data, or
                   if ReadContents is done. ContentRead=0 & ContentsDone=TRUE
                   happens when the repsonse contains no data, only headers
                 */
                else if 
                (
                    (m_nContentRead > 0 || m_bReadContentsDone)
                    &&
                    m_bAuthorized
                )
                {
                    m_bInitPending = FALSE;
                    m_bInitialized = TRUE;

                    // Determine mime type here!
                    const char* pMimeType = NULL;
                    if (!m_strMimeType.IsEmpty())
                                pMimeType = m_strMimeType;

                    if (m_bInitResponsePending && m_pFileResponse)
                    {
                                m_bInitResponsePending = FALSE;
                                m_pFileResponse->InitDone(m_bPartialData ? HXR_RESOURCE_PARTIALCOPY : HXR_OK);
                    }

                    if (m_bFileExistsResponsePending && m_pFileExistsResponse)
                    {
			AddNoCacheHeader();
                        m_bFileExistsResponsePending = FALSE;
                        m_pFileExistsResponse->DoesExistDone(TRUE);
                        HX_RELEASE(m_pFileExistsResponse);
                    }

                    if (m_bMimeResponsePending && m_pMimeMapperResponse)
                    {
                        m_bMimeResponsePending = FALSE;
                        m_pMimeMapperResponse->MimeTypeFound(HXR_OK, pMimeType);

                        // MimeTypeFound() may cause us to close if the Response object
                        // decides it doesn't like our mimetype and shuts us down.
                        // Therefore, m_pMimeMapperResponse could be NULL now..
                        HX_RELEASE(m_pMimeMapperResponse);
                    }
                }
    }

    return theErr;
}


/////////////////////////////////////////////////////////////////////////////
//
//  Method:
//
//      CHTTPFileObject::::ProcessPendingReads()
//
//  Purpose:
//
//      See if there are any pending reads and process them if enough data is
//      available.
//
//  Parameters:
//
//      None.
//
//  Return:
//
//
//
HX_RESULT CHTTPFileObject::ProcessPendingReads(void)
{
    IHXBuffer*             pBuffer             = NULL;
    BOOL                    bDone               = FALSE;
    ULONG32                 ulReadCount         = 0;
    HX_RESULT               lResult             = HXR_OK;


    /* process all pending read requests.. */
    while (!bDone && !m_PendingReadList.IsEmpty())
    {
        ulReadCount = (ULONG32)(PTR_INT)m_PendingReadList.GetHead();

        // If we have enough contiguous bytes we can satisfy this read request
        if (m_pChunkyRes->GetContiguousLength(m_ulCurrentReadPosition) >= ulReadCount)
        {
            /* IT IS OK */
        }
        else if (m_bReadContentsDone)
        {
            /* Read the last few bytes */
            if (m_ulCurrentReadPosition < m_nContentRead)
            {
                ulReadCount = m_nContentRead - m_ulCurrentReadPosition;
            }
            else
            {
                /* Remove from pending list */
                m_PendingReadList.RemoveHead();

                // We're done (ie, we have read everything we can) 
                // but we have pending reads. The user called too many reads, or the connection
                // has died before all data was sent.
                LOG ("*** Read() failed due to insufficient content");
                CallReadDone(HXR_FAIL , NULL);
                continue; // CallReadDone(HXR_FAIL..) for remaining reads in pending list
            }
        }
        else
        {
                // We have to wait for more data to come in the from socket
                // before we can process this Read request.
            LOG ("... Read() blocked; awaiting data");
            bDone = TRUE;
            break;
        }

        if (HXR_OK == m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pBuffer))
        {
            // Ask the buffer to create a space to read into...
            if (HXR_OK == pBuffer->SetSize(ulReadCount))
            {
                // Read directly into the buffer
                ULONG32 ulActual = 0;

                HX_RESULT hxr = m_pChunkyRes->GetData(m_ulCurrentReadPosition,
                                    (char*) pBuffer->GetBuffer(),
                                    ulReadCount,
                                    &ulActual);

                HX_ASSERT(ulActual == ulReadCount);
                m_ulCurrentReadPosition += ulActual;

                /* Remove from pending list */
                m_PendingReadList.RemoveHead();

                CallReadDone(HXR_OK ,pBuffer);
            }
            else
            {
                lResult = HXR_OUTOFMEMORY;
                bDone = TRUE;
            }

            // Release our reference on the buffer!
            pBuffer->Release();
            pBuffer     = 0;
        }
        else
        {
            lResult = HXR_OUTOFMEMORY;
            bDone       = TRUE;
        }
    }

    // if the httpfsys is already in an err state, we'll return that error since all
    // read have been processed (so far).
    if (m_LastError)
    {
        lResult =  m_LastError;
    }

    return lResult;
}



/////////////////////////////////////////////////////////////////////////////
//
//  Method:
//
//      CHTTPFileObject::::BeginGet()
//
//  Purpose:
//
//      Begins retrieval of an http URL.
//
//  Parameters:
//
//      start and end of desired range, if it supports http 1.1.
//      if the end is zero then get to the end of the file.
//      Both parameters default to zero.
//
//  Return:
//
//      HX_RESULT err
//      HXR_OK if the URL was good and setup for retrival completes
//      without error.
//
//
HX_RESULT CHTTPFileObject::BeginGet(ULONG32 ulOffsetStart)
{
//    HX_ASSERT(m_pSocket == NULL);

    HX_RESULT theErr = HXR_OK;
    HX_RESULT  lResult = HXR_OK;
    CHXURL* pHXURL = NULL;
    IHXValues* pURLProperties = NULL;
    IHXBuffer* pHost = NULL;
    IHXBuffer* pPath = NULL;
    IHXBuffer* pCookies = NULL;
    IHXBuffer* pPlayerCookies = NULL;
    IHXBuffer*	pUserAgent = NULL;
    IHXNetworkServices* pNetworkServices = 0;
    UINT32 uValueLength = 0;

    // Keep-Alive Header text
    //
    const char szKeepAlive[] = "\r\nConnection: Keep-Alive";

    const char szUserAgent[] = "\r\nUser-Agent: ";
    
    const char szHostHeader[] = "\r\nHost: ";

    const char szAcceptLang[] = "\r\nAccept-Language: ";

    const char szAcceptEncoding[] = "\r\nAccept-Encoding: gzip";

    const char* pActualHost = GetActualHost();
    const char* pResource = GetActualResource();
    ULONG32     ulPlatformData  = 0;
    IHXValues* pHeaders = 0;
    char*       pOutBuffer = 0;

    BOOL	bAlreadyHasAuthorization = FALSE;
    BOOL	bAlreadyHasProxyAuthorization = FALSE;
    HX_RESULT retVal = HXR_OK;
    IHXRegistry* pRegistry = NULL;
    
#if defined (_WINDOWS) || defined (_WIN32)
    ulPlatformData  = (ULONG32) GetModuleHandle(NULL);
#endif // defined (_WINDOWS) || defined (_WIN32)

    if (!pActualHost)
    {
        theErr = HXR_INVALID_URL_HOST;
        goto exit;
    }

    if (!*pActualHost)
    {
        theErr = HXR_INVALID_URL_HOST;
        goto exit;
    }

#if 0
    if (!pResource)
    {
        theErr = HXR_INVALID_URL_PATH;
        goto exit;
    }

    if (!*pResource)
    {
        theErr = HXR_INVALID_URL_PATH;
        goto exit;
    }
#endif

    m_nPostDataSize = 0;
    m_bPartialData  = FALSE;

    /*
     * Get the RFC822 headers from the IHXRequest object
     */

    if (m_pRequest &&
        m_pRequest->GetRequestHeaders(pHeaders) == HXR_OK)
    {
        if (pHeaders)
        {
	    if (m_pCookies || m_pCookies2)
	    {
		pHXURL = new CHXURL(m_pFilename);
        
		pURLProperties = pHXURL->GetProperties();
		if (pURLProperties)
		{
		    if (HXR_OK == pURLProperties->GetPropertyBuffer(PROPERTY_HOST, pHost) && pHost &&
			HXR_OK == pURLProperties->GetPropertyBuffer(PROPERTY_PATH, pPath) && pPath)
		    {
			HX_VECTOR_DELETE(m_pPath);
			HX_VECTOR_DELETE(m_pHost);

			StrAllocCopy(m_pHost, (char*)pHost->GetBuffer());
			StrAllocCopy(m_pPath, (char*)pPath->GetBuffer());
			HX_RESULT res = HXR_FAIL;
			if(m_pCookies2)
			    res = m_pCookies2->GetCookies(m_pHost, m_pPath, pCookies, pPlayerCookies);
			else if(m_pCookies)
			    res = m_pCookies->GetCookies(m_pHost, m_pPath, pCookies);

			if (res == HXR_OK && pCookies)
			{
			    pHeaders->SetPropertyCString("Cookie", pCookies);
			    if(pPlayerCookies)
			    {
				pHeaders->SetPropertyCString("PlayerCookie", pPlayerCookies);
			    }
			}
			HX_RELEASE(pCookies);
			HX_RELEASE(pPlayerCookies);
		    }
		    HX_RELEASE(pHost);
		    HX_RELEASE(pPath);
		}
		HX_RELEASE(pURLProperties);
		HX_DELETE(pHXURL);
	    }
        
            IHXValues* pValuesRequestHeaders = new CHXHeader();
            pValuesRequestHeaders->AddRef();

            CHXHeader::mergeHeaders(pValuesRequestHeaders, pHeaders);

            if (m_pRequestHeadersOrig)
            {
                CHXHeader::mergeHeaders
                (
                    pValuesRequestHeaders, 
                    m_pRequestHeadersOrig
                );
            }

            /*
             * First spin through the headers to see how much space they
             * require (the extra 4 bytes per header is for overhead)
             */

            const char* pName;
            IHXBuffer* pValue;
            HX_RESULT result;
            UINT32 uBufferSize = 0;
            UINT32 uBufferPtr = 0;

            result = pValuesRequestHeaders->GetFirstPropertyCString(pName, pValue);

            while (result == HXR_OK)
            {
		if (strcasecmp(pName, "User-Agent") &&
		    strcasecmp(pName, "Host") &&
		    (!m_bOnServer || strcasecmp(pName, "GUID")))
		{
		    uBufferSize += strlen(pName);
		    uValueLength = pValue->GetSize();
		    uBufferSize += (uValueLength > 0 ? (uValueLength - 1) : 0);
		    uBufferSize += 4;

		}

		if (!strcasecmp(pName, "Range"))
		{
			m_bExternalByteRangeRequests = TRUE;
			m_bSupportsByteRanges = FALSE;
			// xxxbobclark since someone externally is handling
			// all the range mechanics, internally httpfsys will
			// behave like it did in the olden days w.r.t.
			// m_nContentSize and m_ulCurrentReadPosition.
			// comlib's indhttp is who typically does this. They
			// never use httpfsys's seek().
		}

		if (!strcasecmp(pName, "Content-length"))
		    m_nPostDataSize = atoi((const char*)pValue->GetBuffer());

	        pValue->Release();
                result = pValuesRequestHeaders->GetNextPropertyCString(pName, pValue);
            }

            /*
             * Allocate space for trailing '\0'
             */

            uBufferSize++;

            pOutBuffer = new char[uBufferSize];

            /*
             * Now spin through and build the outgoing string
             */

            result = pValuesRequestHeaders->GetFirstPropertyCString(pName, pValue);

            while (result == HXR_OK)
            {
		if (strcasecmp(pName, "User-Agent") &&
		    strcasecmp(pName, "Host") &&
		    (!m_bOnServer || strcasecmp(pName, "GUID")))
		{
		    uValueLength = pValue->GetSize();
		    if (uValueLength > 0)
		    {
		    	uValueLength--;
		    }

		    if (!strcasecmp(pName, "Authorization")
			    || !strcasecmp(pName, "Proxy-Authorization"))
		    {
			HX_RESULT retVal = HXR_OK;
			IHXRegistry* pRegistry = NULL;
			retVal = m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pRegistry);
			
			if (SUCCEEDED(retVal))
			{
			    IHXBuffer* pBuffer = NULL;
			    retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pBuffer);
			    UINT32 regid = 0;
			    
			    HX_ASSERT(SUCCEEDED(retVal));
			    if (SUCCEEDED(retVal))
			    {
				IHXBuffer* pHeaderBuffer = NULL;

				CHXString key;
				CHXString recentRealmInfo = "";

				if (!strcasecmp(pName, "Authorization"))
				{
				    bAlreadyHasAuthorization = TRUE;

				    key = "authentication.http:";
				    retVal = pRegistry->GetStrByName(WWW_AUTHENTICATION_RECENT_KEY,
						pHeaderBuffer);
					
				    if (SUCCEEDED(retVal))
				    {
					HX_ASSERT(pHeaderBuffer);
					recentRealmInfo = CHXString((const char*)pHeaderBuffer->GetBuffer(), pHeaderBuffer->GetSize());
					HX_RELEASE(pHeaderBuffer);
				    }

				    key += m_strHost.IsEmpty() ? pActualHost : (const char*)m_strHost;
				}
    
				if (!strcasecmp(pName, "Proxy-Authorization"))
				{
				    bAlreadyHasProxyAuthorization = TRUE;

				    key = "proxy-authentication.http:";
				    retVal = pRegistry->GetStrByName(PROXY_AUTHENTICATION_RECENT_KEY,
						pHeaderBuffer);

				    if (SUCCEEDED(retVal))
				    {
					HX_ASSERT(pHeaderBuffer);
					recentRealmInfo = CHXString((const char*)pHeaderBuffer->GetBuffer(), pHeaderBuffer->GetSize());
					HX_RELEASE(pHeaderBuffer);
				    }

				key += pActualHost;
				}

				key += ":";
				key += recentRealmInfo;

				HX_ASSERT(!key.IsEmpty());
				pBuffer->Set(pValue->GetBuffer(), pValue->GetSize());

				regid = pRegistry->GetId((const char*)key);
				if (!regid)
				{
				    pRegistry->AddStr((const char*)key, pBuffer);
				}
				else
				{
				    pRegistry->SetStrByName((const char*)key, pBuffer);
				}

				HX_RELEASE(pBuffer);
				HX_RELEASE(pHeaderBuffer);
			    }
			    HX_RELEASE(pRegistry);
			}
		    }
		    
		    memcpy(&pOutBuffer[uBufferPtr], "\r\n", 2); /* Flawfinder: ignore */
		    uBufferPtr += 2;
		    memcpy(&pOutBuffer[uBufferPtr], pName, strlen(pName)); /* Flawfinder: ignore */
		    uBufferPtr += strlen(pName);
		    memcpy(&pOutBuffer[uBufferPtr], ": ", 2); /* Flawfinder: ignore */
		    uBufferPtr += 2;
		    memcpy(&pOutBuffer[uBufferPtr], /* Flawfinder: ignore */
			   pValue->GetBuffer(),
			   uValueLength);
		    uBufferPtr += uValueLength;

		}
	        pValue->Release();
                result = pValuesRequestHeaders->GetNextPropertyCString(pName, pValue);
            }

            HX_ASSERT(uBufferPtr == uBufferSize - 1);

            pOutBuffer[uBufferPtr] = '\0';

            HX_RELEASE(pValuesRequestHeaders);

            HX_RELEASE(pHeaders);
        }
    }

    // The chttp: label must appear as a standard http: label to outsiders
    if (pResource && !strncasecmp (pResource, "chttp:", 6))
        pResource++;

    // The request is a standard HTTP based request created from the resource...
    // since string.Format() has a max length of 512 chars, we'll build up the string manually
    if(m_nPostDataSize)
	m_strRequest = "POST ";
    else
	m_strRequest = "GET ";

    m_strRequest += (*pResource ? pResource : "/");
    if (m_bDisableByteRanges)
    {
	m_strRequest += " HTTP/1.0\r\nAccept: */*";
    }
    else
    {
	m_strRequest += " HTTP/1.1\r\nAccept: */*";
    }

    m_strRequest += szUserAgent;
    GetUserAgent(pUserAgent);
    m_strRequest += (const char*)pUserAgent->GetBuffer();
    HX_RELEASE(pUserAgent);

    m_strRequest += "\r\nIcy-MetaData: 1";

    m_strRequest += (pOutBuffer ? pOutBuffer : "");
    m_strRequest += szKeepAlive;

    if(!m_strHost.IsEmpty())
    {
        m_strRequest += szHostHeader;
        // Use actual host, even when going through proxy..
        m_strRequest += m_strHost;
        if(m_nPort != 80)
        {
            m_strRequest += ":";
            m_strRequest.AppendULONG(m_nPort);
        }
    }
    
    if (m_pLanguage)
    {
        m_strRequest += szAcceptLang;
        m_strRequest += m_pLanguage->GetBuffer();
    }

    // Tell the server that we support encoded content
    m_strRequest += szAcceptEncoding;
    
    if (m_bByteRangeSeekPending)
    {
	HX_ASSERT(m_bSupportsByteRanges);
	
	m_strRequest += "\r\nRange:bytes=";
	m_strRequest.AppendULONG(ulOffsetStart);
	m_strRequest += "-";
	
	// XXXbobclark when we get smarter we can remember
	// the end of the range we're getting, and simply
	// do m_strRequest.AppendULONG(ulOffsetEnd); but
	// for now it'll just be starting at ulOffsetStart
	// and reading until it hits the end.
    }

    retVal = m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pRegistry);
    if (SUCCEEDED(retVal))
    {
	IHXBuffer* pBuffer = NULL;
	
	CHXString key("no-authentication-information");
	
	CHXString recentAuthRealmInfo;
	CHXString recentProxyAuthRealmInfo;
	
	IHXBuffer* pHeaderBuffer = NULL;
	
	retVal = pRegistry->GetStrByName(WWW_AUTHENTICATION_RECENT_KEY, pHeaderBuffer);
	if (SUCCEEDED(retVal))
	{
	    HX_ASSERT(pHeaderBuffer);
	    recentAuthRealmInfo = CHXString((const char*)pHeaderBuffer->GetBuffer(), pHeaderBuffer->GetSize());
	    HX_RELEASE(pHeaderBuffer);
	}
	
	retVal = pRegistry->GetStrByName(PROXY_AUTHENTICATION_RECENT_KEY, pHeaderBuffer);
	if (SUCCEEDED(retVal))
	{
	    HX_ASSERT(pHeaderBuffer);
	    recentProxyAuthRealmInfo = CHXString((const char*)pHeaderBuffer->GetBuffer(), pHeaderBuffer->GetSize());
	    HX_RELEASE(pHeaderBuffer);
	}
	
	if (!bAlreadyHasAuthorization)
	{
	    key = "authentication.http:";
	    key += m_strHost.IsEmpty() ? pActualHost : (const char*)m_strHost;
	    key += ":";
	    key += recentAuthRealmInfo;

	    if (HXR_OK == pRegistry->GetStrByName((const char*)key, pBuffer) )
	    {
		if (pBuffer && 0 != strncasecmp((const char*)pBuffer->GetBuffer(), "NTLM", 4))
		{
		    CHXString authString((const char*)pBuffer->GetBuffer());

		    m_strRequest += "\r\nAuthorization: ";
		    m_strRequest += (const char*)authString;
		}
    		HX_RELEASE(pBuffer);
	    }
	}
	if (!bAlreadyHasProxyAuthorization)
	{
	    key = "proxy-authentication.http:";
	    key += pActualHost;
	    key += ":";
	    key += recentProxyAuthRealmInfo;

	    if (HXR_OK == pRegistry->GetStrByName((const char*)key, pBuffer) )
	    {
		if (pBuffer && 0 != strncasecmp((const char*)pBuffer->GetBuffer(), "NTLM", 4))
		{
		    CHXString authString((const char*)pBuffer->GetBuffer());

		    m_strRequest += "\r\nProxy-Authorization: ";
		    m_strRequest += (const char*)authString;
		}
		HX_RELEASE(pBuffer);
	    }
	}
		
	HX_RELEASE(pRegistry);
    }
    

    m_strRequest += "\r\n\r\n";

    HX_VECTOR_DELETE(pOutBuffer);

    if(!m_pSocket)
    {
        if (HXR_OK != m_pContext->QueryInterface( IID_IHXNetworkServices,
                                                (void **)&pNetworkServices))
        {
            theErr = HXR_INVALID_PARAMETER;
            goto exit;
        }

        lResult = pNetworkServices->CreateTCPSocket(&m_pSocket);
        pNetworkServices->Release();
        if (lResult != HXR_OK || !m_pSocket)
        {
            theErr = HXR_INVALID_PARAMETER;
            goto exit;
        }

        if(!m_pTCPResponse)
        {
            m_pTCPResponse = HTTPTCPResponse::CreateObject();
            if (m_pTCPResponse)
            {
                m_pTCPResponse->InitObject(this);

                // XXXkshoop This is illegal! (it is not an 
                // interface pointer..)
                //
                m_pTCPResponse->AddRef();
            }
            else
            {
                theErr = HXR_OUTOFMEMORY;
                goto exit;
            }
        }
        
#ifdef SUPPORT_SECURE_SOCKETS
        
        if (m_bUseHTTPS)
        {
	    IHXTCPSecureSocket* pSecureSocket = NULL;
	    if (HXR_OK == m_pSocket->QueryInterface(IID_IHXTCPSecureSocket, (void**)&pSecureSocket))
	    {
		HX_ASSERT(pSecureSocket);
		
		pSecureSocket->SetSecure(TRUE);
		pSecureSocket->Release();
		m_nPort = 443;
	    }
        }
#endif
        
        
        m_pSocket->Init(m_pTCPResponse);

        /* connect to the host */
        m_bSocketReadTimeout = FALSE;
        lResult = m_pSocket->Connect(pActualHost, GetActualPort());
        if (lResult != HXR_OK)
        {
            theErr = HXR_INVALID_HOST;
            goto exit;
        }

        #ifdef CREATE_LOG_DUMP
        FILE *fp = fopen(LOG_DUMP_FILE, "a+");
        if (fp)
        {
            fprintf(fp, "SOCKET CONNECTED at %lx %ld\n\n", m_pChunkyRes, ulOffsetStart);
            fclose(fp);
        }
        #endif

        if(!m_bConnectDone)
        {
            // If there is a pending callback, be sure to remove it!
            // (there might be the connection timeout callback pending)
            if (m_pCallback &&
                m_pCallback->m_bCallbackPending &&
                m_pCallback->m_ulPendingCallbackID && 
                m_pScheduler)
            {
                m_pScheduler->Remove(m_pCallback->m_ulPendingCallbackID);
                m_pCallback->m_bCallbackPending = FALSE;
            }
        }
        
        // Ask the scheduler for callback to catch connection
        // timeout (in ProcessIdle)
        if (!m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending = TRUE;
            m_pCallback->m_ulPendingCallbackID = m_pScheduler->RelativeEnter(m_pCallback, m_nConnTimeout);
        }
    }
    else
    {
        // Ask the scheduler to call ProcessIdle()
        // as m_pSocket!=NULL
        if (!m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending = TRUE;
            m_pCallback->m_ulPendingCallbackID = m_pScheduler->RelativeEnter(m_pCallback, 0);
        }
    }

    m_nTotalRequestSize = (UINT16)m_strRequest.GetLength();

exit:

    return theErr;
}


const char*
CHTTPFileObject::GetActualHost()    const
{
    return !m_bUseProxy ? (const char*)m_strHost : (const char*)m_strProxyHost;
}

const char*
CHTTPFileObject::GetActualResource()    const
{
    return !m_bUseProxy ? (const char*)m_strResource : (const char*)m_strProxyResource;
}

HX_RESULT
CHTTPFileObject::GetUserAgent(REF(IHXBuffer*) pBuffer)
{
    // If we received UserAgent as a parameter to the request, use that
    if (!m_pParams ||
	FAILED(m_pParams->GetPropertyBuffer("Agent", pBuffer)))
    {
	// If not, see if we received it as a file system option
	if (!m_pOptions ||
	    FAILED(m_pOptions->GetPropertyBuffer("Agent", pBuffer)))
	{
	    // Finally, fall back to the default User Agent value
	    pBuffer = new CHXBuffer();
	    pBuffer->AddRef();
	    pBuffer->Set((UCHAR*)DEF_USER_AGENT, strlen(DEF_USER_AGENT) + 1);
	}
    }

    return HXR_OK;
}

int
CHTTPFileObject::GetActualPort()    const
{
    return !m_bUseProxy ? m_nPort : m_nProxyPort;
}


/*
 *      IHXTCPResponse methods
 */
STDMETHODIMP CHTTPFileObject::ConnectDone(HX_RESULT status)
{
    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "SocketConnectDone %lx from %ld\n", m_pChunkyRes, m_nContentRead);
        fclose(fp);
    }
    #endif
    
    if (FAILED(status))
    {
        m_LastError = HXR_INVALID_HOST;
        HX_RELEASE(m_pSocket);

	if (m_bOnServer)
	{
	    ReportConnectionFailure();
	}
    }
    else if (m_bConnTimedOut)
    {
	return HXR_OK;
    }

    // connection is made
    m_bConnectDone = TRUE;

    // If there is a pending callback, be sure to remove it!
    // (there might be the connection timeout callback pending)
    if (m_pCallback && m_pCallback->m_ulPendingCallbackID && m_pScheduler)
    {
        m_pScheduler->Remove(m_pCallback->m_ulPendingCallbackID);
    }

    m_pCallback->m_bCallbackPending = TRUE;
    m_pCallback->m_ulPendingCallbackID = m_pScheduler->RelativeEnter(m_pCallback, 0);

    return HXR_OK;
}


STDMETHODIMP CHTTPFileObject::ReadDone(HX_RESULT   status,
                                           IHXBuffer* pBuffer)
{
    HX_RESULT   theErr  = HXR_OK;
    UINT16      size    = 0;
#ifdef _MACINTOSH
    if (m_pInterruptState && m_pInterruptState->AtInterruptTime())
    {
        if (m_pCallback && !m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending     = TRUE;
            m_pCallback->m_ulPendingCallbackID  =
                        m_pScheduler->RelativeEnter(m_pCallback, 0);
        }
        
        HX_ASSERT(m_pReadDoneBuffer == NULL && m_bReadDoneToBeProcessed == FALSE);
        
        m_pReadDoneBuffer = pBuffer;
        if (m_pReadDoneBuffer)
        {
            m_pReadDoneBuffer->AddRef();
        }
        
        m_uReadDoneStatus = status;
        m_bReadDoneToBeProcessed = TRUE;
        return HXR_OK;  
    }
#endif

    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "SocketReadDone %lx from %ld\n", m_pChunkyRes, m_nContentRead);
        fclose(fp);
    }
    #endif
    
    // There are several points in this function (like when we call 
    // HandleFail()) that we can be destructed due to our last ref
    // being released. So we'll AddRef() ourselves here and Release()
    // ourselves at the end to prevent crashes due to early cleanup.
    AddRef();

    m_bTCPReadPending = FALSE;

    if (pBuffer)
        size = (UINT16)  pBuffer->GetSize();

    if (status == HXR_OK && size>0)
    {
        // If we've already read the header. then we
        // know we can just slap the contents onto the
        // end of the results buffer.
        if (m_bReadHeaderDone)
        {
	    if (m_bEncoded)
	    {
#if defined(HELIX_FEATURE_HTTP_GZIP)
		theErr = m_pDecoder->SetData(m_nContentRead, 
		    (char*)pBuffer->GetBuffer(), size, m_bChunkedEncoding);
		m_nContentRead = m_pDecoder->GetContentRead();

		if (FAILED(theErr))
		{
		    // A failure occurred while trying to decode 
		    // the data, so we must quit
		    m_bReadContentsDone = TRUE;
		}
#else
                /* We have gzip-encoded data, but do not have
                   gzip support compiled in, so we must fail. */
                theErr = HXR_FAIL;
                m_bReadContentsDone = TRUE;
#endif /* #if defined(HELIX_FEATURE_HTTP_GZIP) */
	    }
	    else if (m_bChunkedEncoding)
	    {
		HX_ASSERT(m_pChunkedEncoding);
		if (!m_pChunkedEncoding)
		{
		    m_pChunkedEncoding = new HTTPChunkedEncoding;
		    m_pChunkedEncoding->size = 0;
		    m_pChunkedEncoding->read = 0;
		    m_pChunkedEncoding->lastchunk = FALSE;
		    m_pChunkedEncoding->state = CE_HEADER_PENDING;
		    m_pChunkedEncoding->buf = new char[MAX_CHUNK_SIZE];
		}

		DecodeChunkedEncoding(m_pChunkedEncoding,
				      (const char*)pBuffer->GetBuffer(),
				      size);
	    }
	    else
	    {
                #ifdef CREATE_LOG_DUMP
                FILE *fp = fopen(LOG_DUMP_FILE, "a+");
                if (fp)
                {
                    fprintf(fp, "WriteChunky %lx at %ld size %ld\n", m_pChunkyRes, m_nContentRead, size);
                    fclose(fp);
                }
                #endif
		
                m_pChunkyRes->SetData(m_nContentRead, 
		    (char*)pBuffer->GetBuffer(), size);
		m_nContentRead += size;
	    }

            if(m_bKnowContentSize && m_nContentRead >= m_nContentSize)
            {
		// XXX HP
		// set m_bReadContentsDone to TRUE whenever we have
		// read to the end of the content regardless of the seek
		// since m_bReadContentDone will be reset to FALSE when
		// seek occurs
		m_bReadContentsDone = TRUE;
            }
        }
        else
        {
        char* szHeader = (char*)pBuffer->GetBuffer();
        UINT32 ulLength = pBuffer->GetSize();

        m_bShoutcast = FALSE;
	    // If there is no HTTP/ at the beginning of the response message,
	    // add a default OK response code. The popular browsers all handle
	    // this case, so we should too.
	    if ((ulLength > 4))
	    {
		// XXX HP
		// Darwin HTTP server returns RTSP response on a HTTP request?!
		if (0 == strncasecmp((const char*)szHeader, "RTSP/", 5))
        
		{
		    theErr = _HandleFail(400);
		    goto cleanup;
		}

        // The Darwin server streams mp3 using ShoutCast (or at least they
                // they use the ShoutCast mechanism for including meta data in
                // the mp3 stream).  So, check the header for "icy-metaint", and
                // enable ShoutCast mode.


                // only way to determine if it's a shoutcast server
		if ( (strncasecmp((const char*)szHeader, "ICY", 3) == 0) ||
                     (StrNStr((const char*)szHeader,"icy-metaint", ulLength, 11))

                   )
		{
		    m_bShoutcast = TRUE;
		}
		if (strncasecmp((const char*)szHeader, "HTTP/", 5) != 0 || m_bShoutcast)
		{
		    const char pDefHdr[] = "HTTP/1.1 200 OK\r\n\r\n";
                    UINT32 ulDefHdrSize = sizeof(pDefHdr) - 1;
                    UINT32 ulHdrShift = ulDefHdrSize; 
                    char* pHdr = (char*)pDefHdr;

                    if (m_bShoutcast && 
                        (StrNStr((const char*)szHeader, 
                                 "Content-Type", ulLength, 12) != 0) &&
                        (StrNStr((const char*)szHeader, 
                                 "application/ogg", ulLength, 16) != 0))
                    {
                        // This is an Ogg Icecast stream. Modify the
                        // ulDefHdrSize so that it will turn
                        //  ICY 200 OK   into HTTP/1.1 200 OK
                        ulDefHdrSize = 8;
                        ulHdrShift = 5;
                    }

		    // If we had an http packet that was just a header, concatenate
                    // the current packet to it.
                    if (m_pLastHeader)
                    {
                        pHdr = m_pLastHeader;
                        ulDefHdrSize = m_ulLastHeaderSize;
                        ulHdrShift = ulDefHdrSize;
                    }

                    // Grow Buffer...
		    
	            pBuffer->SetSize(pBuffer->GetSize()+ulHdrShift);

		    // Move Existing Data
		    memmove(pBuffer->GetBuffer() + ulHdrShift, 
			pBuffer->GetBuffer(), 
			ulLength);

		    // Insert Default Response Header
		    memcpy(pBuffer->GetBuffer(), /* Flawfinder: ignore */
			(const char*)pHdr,
			ulDefHdrSize);

                    HX_VECTOR_DELETE(m_pLastHeader);

            szHeader = (char*)pBuffer->GetBuffer();
            ulLength = pBuffer->GetSize();
		}
	    }

            HTTPParser Parser;
            HTTPResponseMessage* pMessage = 0;

            UINT32 ulHeaderLength = ulLength;

            // Parse headers from message
            //
            pMessage = (HTTPResponseMessage*)Parser.parse
            (
                (const char*)szHeader,
                ulHeaderLength
            );

            if
            (
                pMessage
                &&
                pMessage->tag() == HTTPMessage::T_RESP
            )
            {
                // We now have the entire header!
                //
                m_bReadHeaderDone = TRUE;

		if (pMessage->majorVersion() <= 1 && 
		    pMessage->minorVersion() == 0)
		{
		    m_bHTTP1_1 = FALSE;
		}

		HX_VECTOR_DELETE(m_pLastHeader);

                // setup the MIME-type appropriately for shoutcast situations
                CHXString sMimeType = pMessage->getHeaderValue("content-type");
		if (m_bShoutcast && !sMimeType.GetLength())
                {
                    pMessage->addHeader("content-type", "audio/rn-mpeg");
                }

                // XXXkshoop YUCK!!! copying the headers to the response 
                // var in the request object
                // yuck indeed
                IUnknown*   pUnknown = NULL;
		IHXKeyValueList* pResponseHeaders = NULL;

		if (HXR_OK == m_pCommonClassFactory->CreateInstance(CLSID_IHXKeyValueList, (void**)&pUnknown))
		{
		    if (HXR_OK == pUnknown->QueryInterface(IID_IHXKeyValueList, (void**)&pResponseHeaders))
		    {
			MIMEHeaderValue*        pHeaderValue = NULL;
			MIMEHeader*             pHeader = pMessage->getFirstHeader();

                        BOOL bNotAcceptRanges = FALSE;
                        BOOL bRealServer = FALSE;
			while (pHeader)
			{
			    pHeaderValue = pHeader->getFirstHeaderValue();

			    CHXString HeaderString;

			    while (pHeaderValue)
			    {
				CHXString TempString;

				pHeaderValue->asString(TempString);

				HeaderString += TempString;

				pHeaderValue = pHeader->getNextHeaderValue();

				if (pHeaderValue)
				{
				    HeaderString += ", ";
				}
			    }

			    IHXBuffer *pBuffer = NULL;
			    CHXBuffer::FromCharArray((const char*)HeaderString, &pBuffer);
			    pResponseHeaders->AddKeyValue(pHeader->name(),pBuffer);
                           
                            // Disable http 1.1 support if the server explicitly says to
                            if (!strcmpi(pHeader->name(), "Accept-Ranges"))
                            {
                                if (!strcmpi((const char*)pBuffer->GetBuffer(), "none"))
                                {
                                    bNotAcceptRanges = TRUE;
                                }
                            }
                            else if (!strcmpi(pHeader->name(), "Server"))
                            {
                                if (!strcmpi((const char*)pBuffer->GetBuffer(), REALSERVER_RESPONSE_NAME))
                                {
                                    bRealServer = TRUE;
                                }
                            }
                            
                            HX_RELEASE(pBuffer);
			    pHeader = pMessage->getNextHeader();
			}

                        /// Now the logic to decide if we want to support byte range
                        if ((bRealServer && !m_bHTTP1_1) || bNotAcceptRanges)
                        {
                            m_bSupportsByteRanges = FALSE;
                        }

			if (m_pCookies)
			{
			    // retrieve cookies from the response header
			    // XXX showell - possible performance enhancement --
			    // you can put the call to SetCookie in the above 
			    // loop, rather than iterating again through the 
			    // list
			    IHXKeyValueListIterOneKey *pListIter = NULL;
			    pResponseHeaders->GetIterOneKey("Set-Cookie", pListIter);

			    IHXBuffer *pCookie = NULL;
			    while (pListIter->GetNextString(pCookie) == HXR_OK)
			    {
				m_pCookies->SetCookies(m_pHost, m_pPath, pCookie);
				HX_RELEASE(pCookie);
			    }
			    HX_RELEASE(pListIter);
			}
    
			if (m_pRequest)
			{
			    // XXX showell - Maybe in the future we will able to 
			    // change IHXRequest::GetResponseHeaders to return 
			    // an IHXKeyValueList*, so we don't have to support
			    // old-style IHXValues.  Until then, we have to play
			    // these little games.
			    IHXKeyValueList* pKeyedResponseValues = NULL;
			    IHXValues* pResponseValues = NULL;             

			    if ((HXR_OK == m_pRequest->GetResponseHeaders(pResponseValues) &&
				pResponseValues))
			    {
				if (HXR_OK == pResponseValues->QueryInterface(IID_IHXKeyValueList, 
				    (void**)&pKeyedResponseValues))
				{
				    const char*		    pName = NULL;
				    IHXBuffer*		    pValue = NULL;
				    IHXKeyValueListIter*   pListIter = NULL;

				    pKeyedResponseValues->GetIter(pListIter);
				    HX_ASSERT(pListIter);

				    // merge the previous response header to the current HTTP response
				    // excluding the ones already existing in current HTTP response
				    while (pListIter->GetNextPair(pName, pValue) == HXR_OK)
				    {
					if (!pResponseHeaders->KeyExists(pName))
					{
					    pResponseHeaders->AddKeyValue(pName, pValue);
					}
					HX_RELEASE(pValue);
				    }
				    HX_RELEASE(pListIter);
				}
				HX_RELEASE(pKeyedResponseValues);
				HX_RELEASE(pResponseValues);

				if (HXR_OK == pResponseHeaders->QueryInterface(IID_IHXValues, (void**)&pResponseValues))
				{
				    theErr = m_pRequest->SetResponseHeaders(pResponseValues);
				}
			    }
			    else
			    {
				if (HXR_OK == pResponseHeaders->QueryInterface(IID_IHXValues, (void**)&pResponseValues))
				{
				    theErr = m_pRequest->SetResponseHeaders(pResponseValues);
				}
			    }
			    HX_RELEASE(pResponseValues);
			}
		    }
		    HX_RELEASE(pResponseHeaders);
		}
		HX_RELEASE(pUnknown);   

		if (m_bMangleCookies)
		{
		    // If cookie mangling is enabled, 
		    // mangle all received Set-Cookie values
		    MangleAllSetCookies(m_pRequest);
		}

                UINT32 ulHTTPStatus = atoi(pMessage->errorCode());

                if(pMessage->majorVersion() > 0)
                {
                    switch(ulHTTPStatus)
                    {
                    case 206: // Success. Partial data.
			m_bPartialData = TRUE;
			if (m_bCheckingWhetherByteRangeWorks)
			{
			    m_bCheckingWhetherByteRangeWorks = FALSE;
			    // woo hoo, the byte range seek was successful
			    // xxxbobclark there are servers that advertise
			    // "accept-ranges: bytes" but DON'T! That's why
			    // we must verify that byte ranges work!

#ifdef _IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG
                m_bSupportsByteRanges = FALSE;
#else  //_IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG
                m_bSupportsByteRanges = TRUE;
#endif //_IIS_HTTP_SERVER_NO_SEEK_SUPPORT_BUG

                // Confirm the byte offset seek worked
                if (m_bByteRangeSeekPending)
                {
                    m_bByteRangeSeekPending = FALSE;
                    m_bSeekPending = FALSE;

                    m_uByteRangeSeekOffset = 0;
                    Seek(m_ulCurrentReadPosition, FALSE);
                }
			}

                    case 200: // Success
                        {
			    if (m_bCheckingWhetherByteRangeWorks)
			    {
				// xxxbobclark Oh no, we've stumbled across
				// one of the servers that doesn't really
				// support byte ranges. So what we'll do is
				// revert so httpfsys does NOT support byte
				// ranges for this guy, and reset m_nContentRead
				// to match what we're expecting with what we're
				// getting.
				
				// turn off byte range support for this file
				m_bCheckingWhetherByteRangeWorks = FALSE;
				m_bServerPresumablyWorksWithByteRangeRequests = FALSE;
				m_bSupportsByteRanges = FALSE;
				
				// The byte offset seek failed so the server will give
                                // us the data at byte offset 0. Reset our read/write
                                // pointers to offset 0.
                                m_nContentRead = 0;

                                if (m_bByteRangeSeekPending)
                                {
                                    m_bByteRangeSeekPending = FALSE;
                                    m_uByteRangeSeekOffset = 0;

                                    // Turn this failed byte-ranged seek into a pending
                                    // 1.0 seek (wait for the data to be downloaded) if 
                                    // we were advised to in the Advise method.
                                    if (m_bConvertFailedSeeksToLinear)
                                        m_bSeekPending = TRUE;
                                    else
                                    {
                                        m_bSeekPending = FALSE;
                                        m_pFileResponse->SeekDone(HXR_FAILED);
                                    }
                                }

                                m_bReadContentsDone = FALSE;
                            }
                            theErr = _HandleSuccess(pMessage, pBuffer, ulHeaderLength);
                        }
                        break;
                    case 400: // Fail
                    case 404: // Not Found
                        {
                            theErr = _HandleFail(ulHTTPStatus);
                        }
                        break;
                    case 401: // Not Authorized
                    case 407: // Proxy Authentication Required
                        {
			    theErr = _HandleUnAuthorized(pMessage);
                        }
                        break;
                    case 301: // Redirect
                    case 302: // Redirect
                        {
                            theErr = _HandleRedirect(pMessage);
                        }
                        break;
                    
                    case 416: // Invalid range request
                        m_LastError = HXR_INVALID_PARAMETER;
                        m_pFileResponse->SeekDone(HXR_FAILED);
                        theErr = _HandleFail(ulHTTPStatus);
                        break;
                    
                    default:
                        {
                            theErr = _HandleFail(400);
                        }
                        break;
                    };
                }
            }
            
            // If the http packet is just the header, save for the next packet
            else if (ulLength == ulHeaderLength)
            {
                m_pLastHeader = new char[ulLength];
                memcpy(m_pLastHeader, szHeader, ulLength);
                m_ulLastHeaderSize = ulLength;
            }
            delete pMessage;
        }
    }
    // status != HXR_OK (read failed....)
    else 
    {
        // XXXBJP : Old behaviour to support Mac TCP bug (where initial
        // read returns no data, but subsequent OK) removed since it
        // broke httpfsys. If MacTCP bug still needs to be handled, then
        // MACTCP-only implementation should handle it.
        m_bReadContentsDone = TRUE;

        if(m_bKnowContentSize && m_nContentRead < m_nContentSize)
        {
            theErr = HXR_SERVER_DISCONNECTED;
        }
        else
        {
            // an error was recevied in ReadDone(), so nothing more is coming from the server.
            // Have we parsed the header yet??
            if (!m_bReadHeaderDone)
            {
                // We couldn't recognize the headers, so fail.
                theErr = _HandleFail(400);
            }
            else
            {           
                // either we read all the data we knew we had to, or we didn't
                // know how much data to read in the first place (!m_bKnowContentSize)
                // If we didn't know content size, we have to assume we're ok.

                theErr = HXR_OK;
            }
        }
    }

    LOGX ((szDbgTemp, "    Read %5d of %5d bytes of '%s'",
           m_nContentRead,
           m_bKnowContentSize ? m_nContentSize : 0,
           m_pFilename));
           
    if (m_pCacheEntry)
    {
        CacheSupport_ReadDone();        
    }

    // Saving a copy of the file if appropriate
    if( m_bSaveToFile )
    {
	ASSERT( !m_SaveFileName.IsEmpty() );

	// FIX  This is SUCH bad code.
	if( m_nContentRead )
	{
	    ULONG32 ulActual = 0;
	    
	    ULONG32 ulCurBlockSize = m_nContentRead-m_ulOffset;
	    char *pContent = new char[ ulCurBlockSize  ];

	    m_pChunkyRes->GetData( m_ulOffset, pContent, ulCurBlockSize , &ulActual );
	    FILE *pFile = ::fopen( m_SaveFileName, "ab" );//opens for binary mode writing at end of file 
	    if( pFile )
	    {
		
		::fwrite( pContent, sizeof(char), ulActual, pFile );
		::fclose( pFile );
	    }
	    delete pContent;
	    m_ulOffset=m_nContentRead;
	}

	//m_bSaveToFile = FALSE;
    }

cleanup:

    if(SUCCEEDED(m_LastError))
    {
        m_LastError = theErr;

        if (FAILED(theErr) && m_pCallback && !m_pCallback->m_bCallbackPending)
        {
            m_pCallback->m_bCallbackPending = TRUE;
            
            m_pCallback->m_ulPendingCallbackID = 
             m_pScheduler->RelativeEnter(m_pCallback, 0);
        }
    }

    // It's now safe to clean ourselves up if our last reference
    // was released within this function.
    Release();

    return HXR_OK;
}

//
//
//
//
//
//
HX_RESULT 
CHTTPFileObject::_HandleRedirect(HTTPResponseMessage* pMessage)
{
    HX_RESULT   theErr = HXR_OK;
    CHXString   sLocation;
    IHXValues* pReqHeaders = NULL;

    sLocation = pMessage->getHeaderValue("location");

    // XXX HP according to RFC on HTTP headers - "location" allows only absolute URL:
    //	      http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    //	      
    //	      it makes support of HTTP redirect on different protocols tricky if we
    //	      allow relative URL
/*
    // handle relative url in redirects.
    if (!sLocation.IsEmpty() &&
	strncasecmp((const char*) sLocation, "http://", 7) != 0 &&
        strncasecmp((const char*) sLocation, "rtsp://", 7) != 0 &&
        strncasecmp((const char*) sLocation, "chttp://", 8) != 0)
    {
	CHXString   sTempLocation;
	sTempLocation = "http://" + m_strHost;

	if (m_nPort != DEF_HTTP_PORT)
	{
	    char szport[255];
	    itoa(m_nPort, szport, 10);
	    sTempLocation += ":";
	    sTempLocation += szport;
	}

	if ((*(const char *) sLocation) != '/')
	{
	    sTempLocation += '/';
	}

	sTempLocation += sLocation;
	sLocation = sTempLocation;
    }
*/
    if (m_pRedirectResponse)
    {
	BOOL bContinueRedirect = FALSE;
	if (sLocation.IsEmpty())
	{
	    if (HXR_NOTIMPL == m_pRedirectResponse->RedirectDone(NULL))
		bContinueRedirect = TRUE;
	}
	else
	{
	    IHXBuffer* pRedirectURL = new CHXBuffer();
        
	    pRedirectURL->AddRef();
	    pRedirectURL->Set((const UCHAR*)(const char*)sLocation, 
		sLocation.GetLength()+1);

	    if (HXR_NOTIMPL == m_pRedirectResponse->RedirectDone(pRedirectURL))
		bContinueRedirect = TRUE;

	    HX_RELEASE(pRedirectURL);
	}

	HX_RELEASE(m_pRedirectResponse);

	if (!bContinueRedirect)
	    return theErr;
    }

    if(!sLocation.IsEmpty())
    {
        if (m_pCacheEntry)
        {
            // If an HTTP redirection is done, maintain the chttp
            // protocol label for the new URL.
            if (sLocation.Find ("http://") == 0) 
            {
                sLocation.FindAndReplace ("http://", "chttp://");
            }
            if (sLocation.Find ("HTTP://") == 0) 
            {
                sLocation.FindAndReplace ("HTTP://", "chttp://");
            }
        }
        LOGX ((szDbgTemp, "Redirected to '%s'", sLocation));            

        if(m_pRequest)
        {
            m_pRequest->SetURL(sLocation);

            // Keep the request alive..
            m_pRequest->AddRef();

	    // Find out if the User-Agent was explicitly set last time
	    IHXBuffer* pBuffer = NULL;
	    if (m_pParams)
	    {
	        m_pParams->GetPropertyBuffer("Agent", pBuffer);
	    }

            // Reset this object to new request..
            SetRequest(m_pRequest);

	    // Reuse the User-Agent from the last request
	    if (m_pParams && pBuffer)
	    {
		m_pParams->SetPropertyBuffer("Agent", pBuffer);
	    }
	    HX_RELEASE(pBuffer);

	    if (m_bMangleCookies)
	    {
		// Reset the Cookie header to its original value
		m_pRequest->GetRequestHeaders(pReqHeaders);
		if (pReqHeaders && m_pMangledCookies)
		{
		    pReqHeaders->SetPropertyCString("Cookie", m_pMangledCookies);
		}
		HX_RELEASE(pReqHeaders);

		// Unmangle all incoming cookies and keep only those that apply
		// to the new request URL
		UnmangleAllCookies(m_pRequest);
	    }

            // Release the "keep alive" ref..
            m_pRequest->Release();

            // Cleanup for reopen..
            //

            // Redirect is absolute..
            delete [] m_szBaseURL;
            m_szBaseURL = NULL;

            // Need new connection
            HX_RELEASE(m_pSocket);

            m_bHTTP1_1 = TRUE;
            m_bConnectDone = FALSE;

            LOG ("_HandleRedirect()");
            m_bCached = FALSE;

            m_bConvertFailedSeeksToLinear = TRUE;

            theErr = _ReOpen();
        }
    }

    ProcessCacheCompletions(TRUE);
    return theErr;
}

HX_RESULT CHTTPFileObject::_EnsureThatWeAreReadingWisely()
{
    HX_ASSERT( m_bSupportsByteRanges );
    
#ifdef _MACINTOSH
    if (m_pInterruptState && m_pInterruptState->AtInterruptTime())
    {
	// it will soon return here anyway, so we'll
	// just wait until it comes at system time.
	return HXR_OK;  
    }
#endif
    
    // if the range from m_ulCurrentReadPosition to m_nContentRead
    // is valid, that's extraordinarily cool because that means
    // that it's correctly reading into the range that it's
    // currently playing. On the other hand, if there's a gap
    // between this valid region and (the valid region) where
    // it's currently reading, then we should stop reading
    // the current place and instead start reading where it
    // stops being valid.
    
    if (!m_bSeekPending && !m_bByteRangeSeekPending && m_pChunkyRes)
    {
	// xxxbobclark assert that it either has some data or it's
	// at the very end of the resource.
	
	HX_ASSERT(m_pChunkyRes->HasPartialData(1, m_ulCurrentReadPosition)
		 || (m_ulCurrentReadPosition == m_nContentSize));

	if (m_nContentRead >= m_ulCurrentReadPosition)
	{
	    ULONG32 ulLength = m_pChunkyRes->GetContiguousLength(m_ulCurrentReadPosition);

	    if (ulLength < m_nContentRead - m_ulCurrentReadPosition)
	    {
		// That means it's reading way down the line, i.e. a foolish
		// place.

		_HandleByteRangeSeek(m_ulCurrentReadPosition + ulLength);
		
		// xxxbobclark here is where it would conceivably be
		// convenient to be able to tell the length of the
		// invalid range that starts at m_ulCurrentReadPosition
		// + ulLength. Then we could seek to the end of that
		// invalid range and save a packet or two of overwriting
		// because currently it will keep reading until it
		// detects that it's not reading as wisely as possible and
		// starts reading at a wiser place.
	    }
	    else if (ulLength > m_nContentRead - m_ulCurrentReadPosition)
	    {
		// This means that it's currently reading into an
		// already-valid place. Maybe we seeked into the file,
		// then seeked backwards, and has now finished reading
		// up to the already-valid place. So we need to
		// kick-start reading way down the line.
		_HandleByteRangeSeek(m_ulCurrentReadPosition + ulLength);
	    }
	}
	else
	{
	    // XXXbobclark OK, it's plausible that something could be
	    // implemented that, if a Seek jumped over part of the file,
	    // then we finished reading from that place to the end of
	    // the file, would "fill in" non-valid parts of the file.
	    // If that's the case and the reason we're here, then
	    // we should ensure that we've actually filled in the
	    // whole file from m_ulCurrentReadPosition to the length
	    // of the file. If not, then kick-start another byte range
	    // seek.
	    
	    // This is the number of contigous bytes we have
        ULONG32 ulLength = m_pChunkyRes->GetContiguousLength(m_ulCurrentReadPosition) + m_ulCurrentReadPosition;
        _HandleByteRangeSeek(ulLength);

        //HX_ASSERT(!"Reading BEHIND the current seek position");
	}
    }
    
    return HXR_OK;
}

//
// _HandleByteRangeSeek is called when we're connected to an HTTP 1.1
// server that supports grabbing a range of bytes, and Seek has
// been called to an invalid place of the file.
HX_RESULT
CHTTPFileObject::_HandleByteRangeSeek(ULONG32 ulSeekLocation)
{
    HX_RESULT   theErr = HXR_OK;
    
    if (m_bKnowContentSize)
    {
        // Only issue a reconnect if there are empty bytes between
        // the current read location and the end of the file.
        if (ulSeekLocation >= m_nContentSize)
            return HXR_FAIL;
    }
    
    // It should never reach this function unless it's an
    // HTTP 1.1 server that supports byte ranges.
    
    HX_ASSERT(m_bSupportsByteRanges);
    HX_ASSERT(!m_bSeekPending);
    HX_ASSERT(!m_bByteRangeSeekPending);

    m_uByteRangeSeekOffset = ulSeekLocation;
    m_bByteRangeSeekPending = TRUE;
    
    delete [] m_szBaseURL;
    m_szBaseURL = NULL;
    
    // Need new connection
    
    // XXXbobclark I'm thinking of this kind of like a redirect,
    // only instead of redirecting to a whole nuther URL it seeks
    // within this file.
    
    // If there is a pending callback, be sure to remove it!
    // (there might be the connection timeout callback pending)
    if (m_pCallback &&
        m_pCallback->m_bCallbackPending &&
        m_pCallback->m_ulPendingCallbackID && 
        m_pScheduler)
    {
        m_pScheduler->Remove(m_pCallback->m_ulPendingCallbackID);
        m_pCallback->m_bCallbackPending = FALSE;
    }
    
    HX_RELEASE(m_pSocket);
    
    m_bHTTP1_1 = TRUE;
    m_bConnectDone = FALSE;
    
    m_bCached = FALSE;
    
    _ReOpen();
    
    return theErr;
}

//
//
//
//
//
//
HX_RESULT 
CHTTPFileObject::_HandleUnAuthorized(HTTPResponseMessage* pMessage)
{
    HX_RESULT   ResultStatus = HXR_OK;
    UINT32      ulAltURL = 0;
    CHXString   sConnection;
    IHXValues* pRequestHeaders = NULL;

    if (!m_pRequest)
    {
        return HXR_UNEXPECTED;
    }

    HX_RESULT retVal = HXR_OK;
    IHXRegistry* pRegistry = NULL;
    retVal = m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pRegistry);
    if (SUCCEEDED(retVal))
    {
	IHXBuffer* pBuffer = NULL;
	retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pBuffer);
	if (SUCCEEDED(retVal))
	{
	    HX_ASSERT(pBuffer);
	    
	    CHXString authString;
	    
	    authString = pMessage->getHeaderValue("Proxy-Authenticate");
	    if (!authString.IsEmpty())
	    {
		pBuffer->Set((const unsigned char*)(const char*)authString,
			authString.GetLength()+1);
		UINT32 regid = pRegistry->GetId(PROXY_AUTHENTICATION_RECENT_KEY);
		if (!regid)
		{
		    pRegistry->AddStr(PROXY_AUTHENTICATION_RECENT_KEY, pBuffer);
		}
		else
		{
		    pRegistry->SetStrByName(PROXY_AUTHENTICATION_RECENT_KEY, pBuffer);
		}
	    }
	    
	    authString = pMessage->getHeaderValue("WWW-Authenticate");
	    if (!authString.IsEmpty())
	    {
		pBuffer->Set((const unsigned char*)(const char*)authString,
			authString.GetLength()+1);
		UINT32 regid = pRegistry->GetId(WWW_AUTHENTICATION_RECENT_KEY);
		if (!regid)
		{
		    pRegistry->AddStr(WWW_AUTHENTICATION_RECENT_KEY, pBuffer);
		}
		else
		{
		    pRegistry->SetStrByName(WWW_AUTHENTICATION_RECENT_KEY, pBuffer);
		}
	    }
	    
	    HX_RELEASE(pBuffer);
	}
	HX_RELEASE(pRegistry);
    }
    
    if (HXR_OK == m_pRequest->GetRequestHeaders(pRequestHeaders) && pRequestHeaders)
    {   
        // determine AltURL
        pRequestHeaders->GetPropertyULONG32("IsAltURL", ulAltURL);      
	HX_RELEASE(pRequestHeaders);
    }
    
    if (ulAltURL == 1)
    {
        // no support for Authenication in AltURL mode
        ResponseReady(HXR_NOT_AUTHORIZED, m_pRequest);
    }
    else
    {
        IHXValues* pResponseHeaders = NULL;
        DECLARE_SMART_POINTER(IHXBuffer) spBufferConnection;

        //XXXkshoop Don't Timeout while we are waiting for authentication!!
        // need to disable relevant timeouts here.
        m_bDisableConnectionTimeOut = TRUE;

        if 
        (
            HXR_OK == m_pRequest->GetResponseHeaders(pResponseHeaders) 
            && 
            pResponseHeaders
        )
        {
	    IHXBuffer* pServerHeaderBuffer = NULL;
	    HX_RESULT retVal = HXR_OK;
	    
	    // Keep this connection alive?
            pResponseHeaders->GetPropertyCString
            (
                "Connection", 
                spBufferConnection.ptr_reference()
            );

	    // assume it's persistent connection in HTTP1.1
	    if (m_bHTTP1_1)
	    {
		if (spBufferConnection.IsValid()    &&
		    !strncasecmp((char*)spBufferConnection->GetBuffer(), "close", 5))
		{
		    HX_RELEASE(m_pSocket);
		    m_bHTTP1_1 = TRUE;
		    m_bConnectDone = FALSE;
		}
	    }
	    // assume it's not a persistent connection in HTTP1.0
	    else
	    {
		if (!spBufferConnection.IsValid() ||
		    0 != strncasecmp((char*)spBufferConnection->GetBuffer(), "keep-alive", 10))
		{
		    // Get rid of this connection
		    HX_RELEASE(m_pSocket);
		    m_bHTTP1_1 = TRUE;
		    m_bConnectDone = FALSE;
		}
	    }

	    // Add the fake _server value that's used
	    // in IHXAuthenticationManager2 implementations. xxxbobclark
	    HX_ASSERT(m_pHost);
	    if (m_pHost)
	    {
		retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
		    	    (void**)&pServerHeaderBuffer);
		if (SUCCEEDED(retVal))
		{
		    UINT32 ulHTTPStatus = atoi(pMessage->errorCode());
		    if (m_bUseProxy && ulHTTPStatus == 407 && m_strProxyHost.GetLength())
		    {
			pServerHeaderBuffer->Set((UCHAR*)(const char*)m_strProxyHost,
				    m_strProxyHost.GetLength()+1);
		    }
		    else
		    {
			pServerHeaderBuffer->Set((UCHAR*)m_pHost, strlen(m_pHost)+1);
		    }
		    pResponseHeaders->SetPropertyCString("_server", pServerHeaderBuffer);
		    HX_RELEASE(pServerHeaderBuffer);
		}
	    }

	    // Add the protocol to the response headers because TLC needs it
	    IHXBuffer* pProtocol = NULL;
	    if (SUCCEEDED(m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pProtocol)))
	    {
		pProtocol->Set((UCHAR*)"http", strlen("http") + 1);
		pResponseHeaders->SetPropertyCString("_protocol", pProtocol);
		HX_RELEASE(pProtocol);
	    }
        }
        HX_RELEASE(pResponseHeaders);

        if
        (
            m_spClientAuthConversationAuthenticator.IsValid()
            && 
            m_spClientAuthConversationAuthenticator->IsDone()
        )
        {
            // Well we tried to authenticate already, 
            // so it must have failed

            m_spClientAuthConversationAuthenticator->Authenticated(FALSE);

            // Cleanup so that we can re-auth
            m_spClientAuthConversationAuthenticator.Release();
        }

        if (!m_spClientAuthConversationAuthenticator.IsValid())
        {
            DECLARE_SMART_POINTER_UNKNOWN spUnknownAuthenticator;
            DECLARE_SMART_POINTER
            (
                IHXObjectConfiguration
            ) spObjectConfigurationAuthenticator;
            DECLARE_SMART_POINTER
            (
                IHXCommonClassFactory
            ) spCommonClassFactoryHXCore;

            spCommonClassFactoryHXCore = m_pContext;

            // Starting conversation
            ResultStatus = spCommonClassFactoryHXCore->CreateInstance
            (
                CLSID_CHXClientAuthenticator,
                (void**)&spUnknownAuthenticator
            );

            if 
            (
                SUCCEEDED(ResultStatus) 
                && 
                spUnknownAuthenticator.IsValid()
            )
            {
                spObjectConfigurationAuthenticator = 
                (
                    spUnknownAuthenticator
                );

                spObjectConfigurationAuthenticator->SetContext(m_pContext);

                m_spClientAuthConversationAuthenticator = 
                (
                    spUnknownAuthenticator
                );
            }
        }

        if
        (
            m_spClientAuthConversationAuthenticator.IsValid()
            && 
            !m_spClientAuthConversationAuthenticator->IsDone()
        )
        {
            HX_ASSERT(m_pRequest);
            if (m_pRequest)
            {
                ResultStatus = 
                (
                    m_spClientAuthConversationAuthenticator->MakeResponse
                    (
                        this,
                        m_pRequest
                    )
                );

                // Flow continues in ResponseReady()
            }
            else
            {
                // Auth Failed!
                m_spClientAuthConversationAuthenticator->Authenticated(FALSE);
                ResponseReady(HXR_NOT_AUTHORIZED, m_pRequest);
            }
        }
        else
        {
            // Auth Failed!
            if (m_spClientAuthConversationAuthenticator.IsValid())
            {
                m_spClientAuthConversationAuthenticator->Authenticated(FALSE);
            }
            ResponseReady(HXR_NOT_AUTHORIZED, m_pRequest);
        }
    }

    return ResultStatus;
}

// IHXClientAuthResponse
STDMETHODIMP 
CHTTPFileObject::ResponseReady
(
    HX_RESULT   ResultStatus,
    IHXRequest* pRequestResponse
)
{
    if(SUCCEEDED(ResultStatus))
    {
        m_bConvertFailedSeeksToLinear = TRUE;
        _ReOpen();
    }
    else
    {
        m_LastError = HXR_NOT_AUTHORIZED;

        if
        (
            m_bMimeResponsePending
            &&
            m_pMimeMapperResponse
        )
        {
            m_bMimeResponsePending = FALSE;

            m_pMimeMapperResponse->MimeTypeFound
            (
                HXR_NOT_AUTHORIZED,
                NULL
            );
        }

        if(m_bFileExistsResponsePending && m_pFileExistsResponse)
        {
            // *XXXJR I realize it seems a little odd to say a file
            // * exists when actually all we know is we weren't authorized,
            // * but without doing this, we'd never be able to serve any
            // * authentication-required document.  The fs manager would
            // * always forget about us as soon as we said a file didn't exist.
            // *
	    AddNoCacheHeader();
            m_bFileExistsResponsePending = FALSE;
            m_pFileExistsResponse->DoesExistDone(TRUE);
        }

        if(m_bInitResponsePending)
        {
            m_bInitResponsePending = FALSE;
            m_pFileResponse->InitDone(HXR_NOT_AUTHORIZED);
        }
    }
    
    return HXR_OK;
}

void
CHTTPFileObject::AddNoCacheHeader()
{
    // Add the "Pragma: no-cache" response header so that Proxies
    // don't think they can cache our dynamically generated content
    if (m_pRequest)
    {
	IHXValues* pResHeaders = NULL;

	m_pRequest->GetResponseHeaders(pResHeaders);
	if (!pResHeaders)
	{
	    IUnknown* pUnknown = NULL;

	    m_pCommonClassFactory->CreateInstance(CLSID_IHXKeyValueList, 
		(void**)&pUnknown);

	    pUnknown->QueryInterface(IID_IHXValues, 
		(void**)&pResHeaders);

	    m_pRequest->SetResponseHeaders(pResHeaders);

	    HX_RELEASE(pUnknown);
	}

 	IHXBuffer* pNoCache = NULL;
	m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, 
	    (void**)&pNoCache);
	pNoCache->Set((UCHAR*)"no-cache", 9); 

	pResHeaders->SetPropertyCString("Pragma", pNoCache);

	HX_RELEASE(pNoCache);
	HX_RELEASE(pResHeaders);
    }
}

//
//
//
//
//
//
HX_RESULT 
CHTTPFileObject::_HandleFail(UINT32 ulHTTPError)
{
    HX_RESULT   theErr = HXR_OK;

    if (ulHTTPError == 400)
    {
        theErr = HXR_FAIL;

	if (m_bOnServer)
	{
	    ReportGeneralFailure();
	}
    }
    else
    {
        theErr = HXR_DOC_MISSING;

	if (m_bOnServer)
	{
	    ReportDocumentMissing();
	}
    }

    if(m_bInitPending)
    {
        // since any of these callbacks could have us killed
        AddRef();
        
        if (m_bInitResponsePending && m_pFileResponse)
        {
            m_bInitResponsePending = FALSE;
            m_pFileResponse->InitDone(theErr);
        }

        if
        (
            m_bFileExistsResponsePending
            && m_pFileExistsResponse
        )
        {
            m_bFileExistsResponsePending = FALSE;
            m_pFileExistsResponse->DoesExistDone(FALSE);
        }
        
        if
        (
            m_bMimeResponsePending
            && m_pMimeMapperResponse
        )
        {
            m_bMimeResponsePending = FALSE;
            m_pMimeMapperResponse->MimeTypeFound(theErr, NULL);
        }

        Release();      
    }

    return theErr;
}

//
//
//
//
//
//
HX_RESULT 
CHTTPFileObject::_HandleSuccess
(
    HTTPResponseMessage*    pMessage, 
    IHXBuffer*              pBuffer,
    UINT32                  ulHeaderLength
)
{
    HX_RESULT   theErr = HXR_OK;

    // If we've been closed, then exit early
    if (m_bClosed) return HXR_OK;

    m_bAuthorized = TRUE;
    m_bDisableConnectionTimeOut = FALSE;

    if (SUCCEEDED(theErr))
    {
        // Find the content length to support percent done
        // handling...
        UINT32 ulValue = 0;
        if (pMessage->getHeaderValue("content-length", ulValue))
        {
            m_nContentSize = ulValue;
            
	    // xxxbobclark the problem is that m_nContentSize is really
	    // the content size of this particular GET. And if we're getting
	    // a byte range -- say, from the 7-megabyte mark of an 8-megabyte
	    // file, then m_nContentSize will be one megabyte. Kinda
	    // right but kinda wrong. So we'll look for the denominator in the
	    // content-range header and set m_nContentSize according to the
	    // total size of the resource we're GETting.
            
            m_bKnowContentSize = TRUE;

            CHXString theContentRange = pMessage->getHeaderValue("content-range");
            
            //HX_ASSERT(theContentRange.GetLength() > 0);
            
            if (m_bSupportsByteRanges && theContentRange.GetLength() > 0)
            {
		ULONG32 numFields = theContentRange.CountFields('/');
		
		HX_ASSERT(numFields == 2);
		
		if (numFields == 2)
		{
		    CHXString theDenominator = theContentRange.NthField('/', 2);
		    m_nContentSize = strtol((const char*)theDenominator, 0, 10);
		}
            }
        }

        if (m_pCacheEntry)
        {
            CacheSupport_HandleSuccess(pMessage);
        }

        // Find the mime type to support mime reporting...
        CHXString sMimeType;
        sMimeType = pMessage->getHeaderValue("content-type");

        // Some Java Server Pages (JSP) servers (version 2.0
        // and higher) may append the "charset" after the 
        // mime type in the "Content-Type" header like this:
        //
        // Content-Type: application/smil;charset=ISO-8859-1
        //
        // as specified in the JSP 2.0 specification:
        // http://www.jcp.org/aboutJava/communityprocess/review/jsr152
        //
        // Therefore, we should check to see if there's a ';'
        // in the content type. If there is, then only use the string
        // up to that point
        INT32 lSemi = sMimeType.Find(';');
        if (lSemi > 0)
        {
            // We have a semi-colon, so only use the content-type
            // up to the semi-colon but not include it or anything
            // past it
            sMimeType = sMimeType.Left(lSemi);
        }

        // IF "application/octet-stream"
        // mask mimetype to get actual mimetype
        // based on extension later in the core.

        // XXXBJP
        // Apache (with it's 44% share of the web server market):
        // if it doesn't know the file type (ie, unrecognized extension)
        // will look at the contents. If the contents are binary it'll return
        // "application/octet-stream". If the contents look like text, it returns
        // "text/plain". This means a standard Apache installation will return
        // "text/plain" for imf/rp/rt files). Since we don't
        // use text/plain, we'll wipe it out and let the core use the file
        // extension to determine the content type.
        if (sMimeType.IsEmpty() ||
            (sMimeType == "application/octet-stream") ||
            (sMimeType == "text/plain"))
        {
            m_strMimeType = "*";
        }
        else
        {
            m_strMimeType = sMimeType;
        }

	if (IsLiveStream((const char*)sMimeType))
	{
	    // This is a live stream, so to avoid using up all available
	    // disk space, we will tell chunky res to discard data after
	    // we have read it once.
	    m_pChunkyRes->DiscardUsedData();
	}

	// Find out if the content is encoded
	CHXString sEncoding;
	m_bEncoded = FALSE;
	sEncoding = pMessage->getHeaderValue("content-encoding");
	if (sEncoding == "gzip")
	{
	    // The content is gzipped. Make a note of this, so
	    // we can unzip it properly after it is received
	    m_bEncoded = TRUE;

	    // We don't know what the unencoded size will be
            m_nContentSize = 0;
            m_bKnowContentSize = FALSE;
	}

	CHXString sTransferEncoding;
	m_bChunkedEncoding = FALSE;
	sTransferEncoding = pMessage->getHeaderValue("transfer-encoding");
	if (sTransferEncoding == "chunked")
	{
	    m_bChunkedEncoding = TRUE;
	}

        // Handle adding any trailing content data to the start
        // of the content buffers...
        INT32 nContentLen = pBuffer->GetSize() - ulHeaderLength;
        if (nContentLen > 0)
        {
	    if (m_bEncoded)
	    {
#if defined(HELIX_FEATURE_HTTP_GZIP)
		theErr = m_pDecoder->SetData(0, 
		    (const char*)pBuffer->GetBuffer() + ulHeaderLength,
		    nContentLen, m_bChunkedEncoding);
		m_nContentRead = m_pDecoder->GetContentRead();

		if (FAILED(theErr))
		{
		    // A failure occurred while trying to decode 
		    // the data, so we must quit
		    m_bReadContentsDone = TRUE;
		}
#else
                /* We have gzip-encoded data, but do not have
                   gzip support compiled in, so we must fail. */
                theErr = HXR_FAIL;
                m_bReadContentsDone = TRUE;
#endif /* #if defined(HELIX_FEATURE_HTTP_GZIP) */
	    }
	    else if (m_bChunkedEncoding)
	    {
		m_pChunkedEncoding = new HTTPChunkedEncoding;
		m_pChunkedEncoding->size = 0;
		m_pChunkedEncoding->read = 0;
		m_pChunkedEncoding->lastchunk = FALSE;
		m_pChunkedEncoding->state = CE_HEADER_PENDING;
		m_pChunkedEncoding->buf = new char[MAX_CHUNK_SIZE];

		DecodeChunkedEncoding(m_pChunkedEncoding,
                      (const char*)pBuffer->GetBuffer() + ulHeaderLength,
				      nContentLen);
	    }
	    else
	    {
	    
                #ifdef CREATE_LOG_DUMP
                FILE *fp = fopen(LOG_DUMP_FILE, "a+");
                if (fp)
                {
                    fprintf(fp, "WriteChunky %lx at %ld size %ld\n", m_pChunkyRes, m_nContentRead, nContentLen);
                    fclose(fp);
                }
                #endif
                
                    m_pChunkyRes->SetData(m_nContentRead,
		        (const char*)pBuffer->GetBuffer() + ulHeaderLength,
		        nContentLen);
		    m_nContentRead += nContentLen;

	    }
        }

        // See if the data trailing after the header contains all
        // the content (or note the fact if their won't be any contents
        // data at all...)
        if(m_bKnowContentSize && m_nContentRead >= m_nContentSize)
        {
	    // XXX HP
	    // set m_bReadContentsDone to TRUE whenever we have
	    // read to the end of the content regardless of the seek
	    // since m_bReadContentDone will be reset to FALSE when
	    // seek occurs
	    m_bReadContentsDone = TRUE;
        }

        if(m_bStatPending)
        {
            m_bStatPending = FALSE;

            m_pFileStatResponse->StatDone
            (
                HXR_OK,
                m_nContentSize,
                0, 0, 0, 0
            );

            m_pFileStatResponse->Release();
        }
    }

    return theErr;
}

BOOL CHTTPFileObject::IsLiveStream(const char* pMimeType)
{
    BOOL bLive = FALSE;

    // Note: We know this is a live stream if we previously determined
    // the server to be a Shoutcast server, or the length is set to
    // one of the magic numbers that some of these mpeg audio servers 
    // use to indicate a "live" stream - DPS
    if (m_bShoutcast)
    {
	bLive = TRUE;
    }
    else if (m_bKnowContentSize && 
		(m_nContentSize == 99999999 ||
		 m_nContentSize == 54000000))
    {
	if (!strcasecmp(pMimeType, "audio/mpeg"))
	{    
	    bLive = TRUE;
	}
    }

    return bLive;
}

STDMETHODIMP CHTTPFileObject::WriteReady(HX_RESULT status)
{
    return HXR_OK;
}

STDMETHODIMP CHTTPFileObject::Closed(HX_RESULT  status)
{
    #ifdef CREATE_LOG_DUMP
    FILE *fp = fopen(LOG_DUMP_FILE, "a+");
    if (fp)
    {
        fprintf(fp, "SocketClosed %lx from %ld\n", m_pChunkyRes, m_nContentRead);
        fclose(fp);
    }
    #endif
    
    return HXR_OK;
}

HX_RESULT
CHTTPFileObject::_ReOpen()
{
    LOG("_ReOpen");
    LOGX((szDbgTemp, "    URL='%s'", m_pFilename));

    HX_RESULT	rc = HXR_OK;

    m_bWriteDone = FALSE;
    m_nRequestWritten = 0;
    m_nTotalRequestSize = 0;

    // xxxbobclark set these member variables only if we're not in the middle of
    // an http 1.1 byte range seek
    
    if (!m_bByteRangeSeekPending)
    {
	m_bKnowContentSize = FALSE;
	m_nContentSize = 0;
    }

    m_bEncoded = FALSE;
    
    if (!m_bSupportsByteRanges)
    {
        m_nContentRead = 0;
        m_ulCurrentReadPosition = 0;
    }
    
    m_nHeaderRead = 0;
    m_bReadHeaderDone = FALSE;
    m_bReadContentsDone = FALSE;
    m_strRequest = "";

    m_bTCPReadPending = FALSE;

    if (m_pCallback && m_pCallback->m_bCallbackPending && m_pScheduler)
    {
        m_pScheduler->Remove(m_pCallback->m_ulPendingCallbackID);
        m_pCallback->m_ulPendingCallbackID  = 0;
        m_pCallback->m_bCallbackPending = FALSE;
    }

#ifdef _MACINTOSH
    m_bReadDoneToBeProcessed = FALSE;
    HX_RELEASE(m_pReadDoneBuffer);
    m_uReadDoneStatus = HXR_OK;
#endif    

    rc = _OpenFile(m_pFilename, HX_FILE_READ|HX_FILE_BINARY);

    // mask HXR_WOULD_BLOCK so we dont report this as an error
    if (HXR_WOULD_BLOCK == rc)
    {
	rc = HXR_OK;
    }

    return rc;
}

STDMETHODIMP
CHTTPFileObject::SetRequest
(
    IHXRequest* pRequest
)
{
    char*  pTemp	    = NULL;
    const char*  pURL		    = NULL;;
    IHXValues*	 pReqHeaders = NULL;

    HX_RELEASE(m_pRequest);
    m_pRequest = pRequest;

    if (m_pRequest)
    {
        m_pRequest->AddRef();

        CHXString   sPath;

        if (m_pRequest->GetURL(pURL) != HXR_OK)
        {
            return HXR_FAIL;
        }

        LOGX((szDbgTemp, "    GetURL() returns: '%s'", pURL));
        
        // Handle RP bug PR6655
        char *pStr;
        pStr = (char *)strstr (pURL, "/chttp://");
        if (pStr)
        {
                LOG ("    Jumping past buggy prefix");
                pURL = pStr + strlen("/chttp:");
                LOGX((szDbgTemp, "    pURL = %s", pURL));
        }

        // if URL contains <protocol>:// then DON'T reuse basepath
        // from original request, instead just use the new URL.
        if (m_szBaseURL && pURL 
         && (strncasecmp(pURL,"chttp://", 8) != 0)
         && (strncasecmp(pURL,"http://",  7) != 0))
        {
            sPath = m_szBaseURL;
            if(sPath[sPath.GetLength()-1] != '/')
            {
                sPath += '/';
            }
        }
        else
        {
            sPath = "";
        }

        if (pURL)
        {
            sPath += pURL;
        }

        // Remove default HTTP port specification, if present
        sPath.FindAndReplace(DEF_HTTP_PORT_STR, "/");

        delete[] m_pFilename;
        m_pFilename = new_string(sPath.GetBuffer(1));

        // Save these headers.
        HX_RELEASE(m_pRequestHeadersOrig);
        m_pRequest->GetRequestHeaders(m_pRequestHeadersOrig);
    }
    
    // Set if URL is cachable
    if (m_bCacheEnabled && m_pCacheFile && m_pFilename &&
        strncasecmp(m_pFilename, "chttp://", 8) == 0)
    {
        LOGX((szDbgTemp, "    m_pFilename='%s'", m_pFilename));
        LOGX((szDbgTemp, "    m_pCacheFile='%s'", (char *)m_pCacheFile->GetBuffer()));

        // Create the cache database if need be
        if (g_pCacheEntry == NULL)
        {
            g_pCacheEntry = new CCacheEntry ((char *)m_pCacheFile->GetBuffer(),
                                             m_ulCacheMaxSize,
                                             m_pFilename);
        }
        if (m_pCacheEntry == NULL)
            m_pCacheEntry = g_pCacheEntry;
    }

    // Strip off any URL parameters and store them
    HX_RELEASE(m_pParams);
    if (m_pFilename)
    {
	char* pTemp = NULL;

	if (m_bOnServer)
	{
	    // Check for a bitrate parameter. If it exists, remove it
	    // from the URL we are about to request
	    char* pBitrate = strstr(m_pFilename, "bitrate=");
	    if (pBitrate)
	    {
		char* pEnd = pBitrate;
		while (*pEnd && *pEnd != '&')
		{
		    pEnd++;
		}

		// Make sure the URL is properly fixed up
                INT32 lSize = strlen(m_pFilename) + 1;
		pTemp = new char[lSize];
		if ((*(pBitrate - 1) == '?') && (*pEnd != '\0'))
		{
		    pEnd++;
		}
		else
		{
		    *(pBitrate - 1) = '\0';
		}
		SafeSprintf(pTemp, lSize, "%s%s", m_pFilename, *pEnd ? pEnd : "");

		// Set the request object to use this URL instead
		m_pRequest->SetURL(pTemp);

		HX_VECTOR_DELETE(m_pFilename);
		m_pFilename = pTemp;
		pTemp = NULL;
	    }
	}

        // In order to parse the URL with the CHXURL class, we must ensure
	// that the URL is fully qualified. Add a bogus protocol and domain
	// if necessary...
	if ((strncasecmp(pURL,"chttp://", 8) != 0) && 
	    (strncasecmp(pURL,"http://",  7) != 0))
	{
		int lenTemp = strlen(m_pFilename) + strlen("http://x/") + 1;
	    pTemp = new char[lenTemp];
	    SafeSprintf(pTemp, lenTemp, "http://x/%s", m_pFilename); /* Flawfinder: ignore */
	}
	else
	{
	    pTemp = new_string(m_pFilename);
	}

        CHXURL urlParser(pTemp);
        if (SUCCEEDED(urlParser.GetLastError()))
	{
	    IHXBuffer* pBuffer = NULL;
	    IHXBuffer* pRealURL = NULL;
	    IHXBuffer* pFinalURL = NULL;

            m_pParams = (IHXValues*)urlParser.GetOptions();

	    // If there is a parameter called "RealURL", the value is the
	    // URL that we should request instead of m_pFilename
	    if (HXR_OK == m_pParams->GetPropertyBuffer("RealURL", pRealURL))
	    {
		// The RealURL parameter is perplexed, so we must deperplex 
		// it before using it.
		DePerplexBuffer(pRealURL, pFinalURL);

		// Set the request object to use this URL instead
		m_pRequest->SetURL((char*)pFinalURL->GetBuffer());

		HX_VECTOR_DELETE(m_pFilename);
		m_pFilename = new_string((char*)pFinalURL->GetBuffer());
		HX_RELEASE(pFinalURL);

		// If there is a parameter called "Cookies", it is an encoded
		// set of cookies that should be added to the request headers
		// when we actually make the "RealURL" request
		if (HXR_OK == m_pParams->GetPropertyBuffer("Cookies", pBuffer))
		{
		    // Create a temporary buffer
		    char* pBuf = new char[pBuffer->GetSize()];

		    // Unescape the cookies
		    INT32 nLength = URLUnescapeBuffer((char*)pBuffer->GetBuffer(), 
			pBuffer->GetSize(), pBuf);

		    if (nLength)
		    {
			pBuf[nLength] = '\0';

			// Add it to the request headers
			IHXBuffer* pCookie = NULL;

			m_pRequest->GetRequestHeaders(pReqHeaders);
			if (pReqHeaders)
			{
			    // Find out if a Cookie Request header already exists
			    pReqHeaders->GetPropertyCString("Cookie", pCookie);
			    if (pCookie)
			    {
				// Concatenate this cookie to the end
				INT32 nCatLen = nLength + pCookie->GetSize() + 1;
				char* pCatBuf = new char[nCatLen];
				SafeSprintf(pCatBuf, nCatLen, "%s;%s", pBuf, (char*)pCookie->GetBuffer());

				// Replace the existing cookie header with the new one
				HX_RELEASE(pCookie);
				m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pCookie);
				if (pCookie)
				{
				    pCookie->Set((UCHAR*)pCatBuf, nCatLen);
				    pReqHeaders->SetPropertyCString("Cookie", pCookie);
				}

				HX_VECTOR_DELETE(pCatBuf);
			    }
			    else
			    {
				// Create a new buffer
				m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pCookie);
				if (pCookie)
				{
				    pCookie->Set((UCHAR*)pBuf, nLength + 1);
				    pReqHeaders->SetPropertyCString("Cookie", pCookie);
				}
			    }
			    HX_RELEASE(pCookie);
			}
			HX_RELEASE(pReqHeaders);
		    }

		    HX_VECTOR_DELETE(pBuf);
		}
		HX_RELEASE(pBuffer);
	    }
	    HX_RELEASE(pRealURL);
	}
	HX_VECTOR_DELETE(pTemp);
    }

    if (m_bMangleCookies)
    {
	// Store the master set of cookies, in case we get redirected
	// and need to redecide which cookies to send
	HX_RELEASE(m_pMangledCookies);
	m_pRequest->GetRequestHeaders(pReqHeaders);
	if (pReqHeaders)
	{
	    pReqHeaders->GetPropertyCString("Cookie", m_pMangledCookies);
	}
	HX_RELEASE(pReqHeaders);

	// If Cookie mangling is enabled, unmangle all incoming cookies,
	// keeping only those which apply to the request URL
	UnmangleAllCookies(m_pRequest);
    }

    return HXR_OK;
}

STDMETHODIMP
CHTTPFileObject::GetRequest
(
    REF(IHXRequest*) pRequest
)
{
    pRequest = m_pRequest;

    if (pRequest)
    {
        pRequest->AddRef();
    }

    return HXR_OK;
}


/************************************************************************
 *      Method:
 *          IHXTimeSettings::Get/SetConnnectionTimeout
 *      Purpose:
 *          Get/Set the connection timeout setting, in seconds
 */
STDMETHODIMP
CHTTPFileObject::GetConnectionTimeout(REF(UINT32)   /*OUT*/ nSeconds)
{
    nSeconds = m_nConnTimeout;
    return HXR_OK;
}

STDMETHODIMP
CHTTPFileObject::SetConnectionTimeout(UINT32 /*IN*/ nSeconds)
{
    m_nConnTimeout = nSeconds;
    return HXR_OK;
}

/************************************************************************
 *      Method:
 *          IHXTimeSettings::Get/SetServerTimeout
 *      Purpose:
 *          Get/Set the server timeout setting, in seconds
 */
STDMETHODIMP
CHTTPFileObject::GetServerTimeout(REF(UINT32) /*OUT*/ nSeconds)
{
    nSeconds = m_nServerTimeout;
    return HXR_OK;
}

STDMETHODIMP
CHTTPFileObject::SetServerTimeout(UINT32 /*IN*/ nSeconds)
{
    m_nServerTimeout = nSeconds;
    return HXR_OK;
}

/************************************************************************
 *  Method:
 *      IHXHTTPRedirect::Init
 *  Purpose:
 *      Initialize the response object
 */
STDMETHODIMP
CHTTPFileObject::Init(IHXHTTPRedirectResponse* pRedirectResponse)
{
    HX_RESULT hr = SetResponseObject(pRedirectResponse);

    if (HXR_OK == hr)
    {
        hr = _OpenFile(m_pFilename, HX_FILE_READ);
    }

    return hr;
}

/************************************************************************
 *  Method:
 *      IHXHTTPRedirect::Init
 *  Purpose:
 *      Initialize the response object
 */
STDMETHODIMP
CHTTPFileObject::SetResponseObject(IHXHTTPRedirectResponse* pRedirectResponse)
{
    HX_RESULT hr = HXR_FAILED;

    if (pRedirectResponse)
    {
        HX_RELEASE(m_pRedirectResponse);

        m_pRedirectResponse = pRedirectResponse;
        m_pRedirectResponse->AddRef();

        hr = HXR_OK;
    }

    return hr;
}


// HTTPFileObjCallback
BEGIN_INTERFACE_LIST(HTTPFileObjCallback)
    INTERFACE_LIST_ENTRY(IID_IHXCallback, IHXCallback)
END_INTERFACE_LIST

HTTPFileObjCallback::HTTPFileObjCallback()
    : m_HTTPFileObject (NULL)
    , m_bCallbackPending (FALSE)
    , m_ulPendingCallbackID (0)
{
}

void 
HTTPFileObjCallback::InitObject(CHTTPFileObject* pFileObject)
{
    m_HTTPFileObject = pFileObject;
}

HTTPFileObjCallback::~HTTPFileObjCallback()
{
}

/*
 *      IHTTPFileObjCallback methods
 */
STDMETHODIMP HTTPFileObjCallback::Func(void)
{
    /*
     * To ensure the CHTTPFileObject does not get destroyed before we are
     * done with it, we AddRef()/Release() it here
     */

    if (m_HTTPFileObject)
    {
        m_HTTPFileObject->AddRef();
        m_bCallbackPending      = FALSE;
        m_ulPendingCallbackID   = 0;
        m_HTTPFileObject->ProcessIdle();
        m_HTTPFileObject->Release();
    }

    return HXR_OK;
}

BEGIN_INTERFACE_LIST(HTTPTCPResponse)
    INTERFACE_LIST_ENTRY(IID_IHXTCPResponse, IHXTCPResponse)
END_INTERFACE_LIST

HTTPTCPResponse::HTTPTCPResponse()
    : m_pOwner(NULL)
{
}

void 
HTTPTCPResponse::InitObject(CHTTPFileObject* pOwner)
{
    m_pOwner = pOwner;
}

HTTPTCPResponse::~HTTPTCPResponse()
{
    m_pOwner = NULL;
}

/*
 *      IHXTCPResponse methods
 */

STDMETHODIMP HTTPTCPResponse::ConnectDone(HX_RESULT status)
{
    if (m_pOwner)
    {
        m_pOwner->ConnectDone(status);

        if (SUCCEEDED(status))
        {
            if (m_pOwner->m_bSupportsByteRanges)
            {
                // xxxbobclark OK, it's true that we're "lying" by saying
                // that stuff's been read that really we've merely seeked past.
                // But for now there are other places that will get confused if
                // we don't fake it out like this.
                m_pOwner->m_nContentRead = m_pOwner->m_uByteRangeSeekOffset;

                // If we are attempting a byte offset seek,
                // check the server for byte seek support
                if (m_pOwner->m_bByteRangeSeekPending &&
                    m_pOwner->m_uByteRangeSeekOffset)
                {
                    m_pOwner->m_bCheckingWhetherByteRangeWorks = TRUE;
                }
            }
        }
    }

    return HXR_OK;
}


STDMETHODIMP HTTPTCPResponse::ReadDone( HX_RESULT   status,
                                        IHXBuffer* pBuffer)
{
    return m_pOwner ? m_pOwner->ReadDone(status, pBuffer) : HXR_OK;
}

STDMETHODIMP HTTPTCPResponse::WriteReady(HX_RESULT status)
{
    return m_pOwner ? m_pOwner->WriteReady(status) : HXR_OK;
}

STDMETHODIMP HTTPTCPResponse::Closed(HX_RESULT  status)
{
    return m_pOwner ? m_pOwner->Closed(status) : HXR_OK;
}


/**************************************************************
 *
 *  Additional routines
 *
 **************************************************************/

/*
 * This supplies the bulk of processing needed by the _OpenFile method
 */
STDMETHODIMP_(void)
CHTTPFileObject::CacheSupport_InitObject (void)
{
        HX_ASSERT(m_pCacheFile == NULL);
        HX_ASSERT(m_pPreferences);
        
        LOG ("CacheSupport_InitObject()");

        // get preferences for caching
        if (m_pPreferences)
        {
            // Get cache directory, if not set and available
            IHXBuffer*     pBuffer  = NULL;

            // Check to see if the cache is enabled
            m_bCacheEnabled = TRUE;
            if (m_pPreferences->ReadPref("CacheEnabled", pBuffer) == HXR_OK)
            {
                LOG("    Cache enabled entry exists");
                if (atoi((const char*)pBuffer->GetBuffer()))
                {
                    LOG("    Cache enabled");
                    m_bCacheEnabled = TRUE;
                }
                else
                {
                    LOG("    Cache disabled");
                    m_bCacheEnabled = FALSE;
                }
                HX_RELEASE(pBuffer);
            }
	    else if (m_bOnServer)
	    {
		m_bCacheEnabled = FALSE;
	    }

	    // Here we look for version number to activate the cache...
	    if (m_pContext != NULL)
	    {
		IHXProductIdentity *productIdentity = NULL;
		m_pContext->QueryInterface(IID_IHXProductIdentity, (void **)(&productIdentity));
		if (productIdentity != NULL)
		{
		    // Get the version info...
		    UINT32 majorVersion = productIdentity->GetMajorVersion();
		    UINT32 minorVersion = productIdentity->GetMinorVersion();
		    UINT32 releaseNumber = productIdentity->GetReleaseNumber();

		    // XXXXGB Looks like a hack, feels like a hack, smells like a hack, must be a hack...it disables the cache for U2...
		    if ((majorVersion == 6) && (minorVersion == 0) && (releaseNumber == 6)) m_bCacheEnabled = FALSE;
		}
		HX_RELEASE(productIdentity);
	    }
            
            // If the cache is disabled, do not set it up
            if (m_bCacheEnabled == FALSE)
            {
                return;
            }

            if (m_pCacheFile == NULL)
            {
                if (m_pPreferences->ReadPref("CacheFilename", m_pCacheFile) != HXR_OK)
                {
                    LOG("    Cannot get CacheDirectory from registry, using default");
                    if (HXR_OK == m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&m_pCacheFile))
                    {
#ifdef USE_TEMP_CACHE_LOCATION
			SetBufferToCacheFilePath(m_pCacheFile);
#else
                        char  szModule[MAX_CACHE_FILENAME + sizeof DEF_CACHE_DB + 4] = { 0 }; /* Flawfinder: ignore */
#ifdef _MACINTOSH
	                FSSpec fileSpec;
	                GetCurrentAppSpec(&fileSpec);
	                CHXString fullpath;
	                fullpath = fileSpec;
	                SafeStrCpy(szModule, fullpath, sizeof(szModule));
#elif defined(_WIN32) 
                        GetModuleFileName(NULL, OS_STRING2(szModule,MAX_CACHE_FILENAME), MAX_CACHE_FILENAME);
#else
                        *szModule = '\0';
#endif
                        // Find the separator character before the file name
                        INT8* pFilename = ::strrchr(szModule, OS_SEPARATOR_CHAR);

                        // Perhaps they are using the '/' separator
                        if (pFilename == NULL)
                        {
                            pFilename = ::strrchr(szModule, '/');
                        }

                        if (pFilename != NULL) // Found
                        {
                            // module file name starts after the separator charactor
                            *pFilename = '\0';
                        }
                        // Add on the cache database filename portion
                        ::strcat (szModule, OS_SEPARATOR_STRING); /* Flawfinder: ignore */
                        ::strcat (szModule, DEF_CACHE_DB); /* Flawfinder: ignore */
                        m_pCacheFile->Set((UINT8*)szModule, strlen(szModule) + 1);
#endif // !USE_TEMP_CACHE_LOCATION
                    }
                }
                else
                {
#ifndef _MACINTOSH
                      // Make sure that the given filename belongs to a directory
                      struct stat statbuf;
                      // If cache database filename does not refer to a file, drop the filename part
                      if (!stat ((const char*)m_pCacheFile->GetBuffer(), &statbuf)) 
                      {
                          if (!(statbuf.st_mode & S_IFDIR))
                          {
                              LOG ("*** Invalid cache directory specified");
                              m_pCacheFile->Set((UINT8*)"cache_db", strlen("cache_db") + 1);
                          }
                      }
#endif
                }
                LOG("    Cache directory set");
            }

            // Check to see if the this is an Authenticated reponse
            if (m_pPreferences->ReadPref("Authorization", pBuffer) == HXR_OK)
            {
                LOG("    Authorized content not cached");
                m_bCacheEnabled = FALSE;

                HX_RELEASE(pBuffer);
            }

            // Check to see if the max cache size is given
            m_ulCacheMaxSize = DEFAULT_MAX_CACHE_SIZE;
            if (m_pPreferences->ReadPref("CacheMaxSize", pBuffer) == HXR_OK)
            {
                m_ulCacheMaxSize = atoi((const char*)pBuffer->GetBuffer());

                if (m_ulCacheMaxSize < MININUM_MAX_CACHE_SIZE)
                {
                    m_ulCacheMaxSize = MININUM_MAX_CACHE_SIZE;
                }
                HX_RELEASE(pBuffer);
            }
            LOGX((szDbgTemp, "    Maximum cache size is %lu", m_ulCacheMaxSize));

            if (m_pPreferences->ReadPref("CacheDefaultTTL", pBuffer) == HXR_OK)
            {
                g_ulDefTtl = atoi((const char*)pBuffer->GetBuffer());
                HX_RELEASE(pBuffer);
            }
            else if (m_pPreferences->ReadPref("DefaultTTL", pBuffer) == HXR_OK)
            {
                g_ulDefTtl = atoi((const char*)pBuffer->GetBuffer());
                HX_RELEASE(pBuffer);
            }

        else
        {
            LOG("    No default TTL given in registry");
            g_ulDefTtl = CACHE_DEFAULT_TTL_SECS;
        }

        LOGX((szDbgTemp, "    Default TTL is %lu", g_ulDefTtl));

        IHXBuffer*     pCacheCutOffDate    = NULL;

        if (m_pPreferences->ReadPref("CacheCutOffDate", pCacheCutOffDate) == HXR_OK)
        {
            m_ulCutOffDate = atol((const char*)pCacheCutOffDate->GetBuffer());
            LOGX((szDbgTemp, "    CacheCutOffDate = %lu (%ld)",
                  m_ulCutOffDate, time(NULL) - m_ulCutOffDate));

            if (m_ulCutOffDate)         // Make sure date is reasonable
            {
                if (m_ulCutOffDate < (UINT32)time(NULL) - (2 * SECS_IN_YEAR)
                 || m_ulCutOffDate > (UINT32)time(NULL))
                {
                    LOG("    CacheCutOffDate invalid");
                    m_ulCutOffDate = 0;
                }
            }
            HX_RELEASE(pCacheCutOffDate);
        }
    }
}

STDMETHODIMP_(void)
CHTTPFileObject::CacheSupport_OpenFile (void)
{
    if (m_bCacheEnabled && m_pFilename && m_pCacheEntry && !m_bCached)
    {
        // Look up the URL in the cache
        DBT             dbtKey       = { m_pFilename, strlen (m_pFilename) };
        DBT             dbtHeader    = { NULL, 0 };
        DBT             dbtContent   = { NULL, 0 };
        int             rc           = m_pCacheEntry->ReadCache(&dbtKey, &dbtHeader, &dbtContent, m_bMirroredServers);
        PCacheHeader    pCacheHeader = (CacheHeader*) dbtHeader.data;
        IHXBuffer*     pHttpHeaders = NULL;

        LOGX((szDbgTemp, "    rc=%lu, pHeader=0x%08X, pContent=0x%08X", rc, dbtHeader.data, dbtContent.data));
        if ((rc == HXR_OK) && pCacheHeader && pCacheHeader->m_ulExpiryTime < (UINT32)time(NULL))
        {
            LOG ("$$$ Cache hit (but entry has expired)");
            m_pCacheEntry->del(&dbtKey);    // Remove expired entry from cache
        }
        else if ((rc == HXR_OK) && pCacheHeader && pCacheHeader->m_ulCreateTime < m_ulCutOffDate)
        {
            LOG ("$$$ Cache hit (but entry is before cut-off date)");
            m_pCacheEntry->del(&dbtKey);    // Remove ill-treated entry from cache
        }
        else if ((rc == HXR_OK) && dbtHeader.data)
        {
            LOG ("$$$ Cache hit");

            ASSERT (dbtHeader.size >= sizeof(CacheHeader));

            LOGX((szDbgTemp, "    HttpHeaders buffer is length %3d", pCacheHeader->m_ulHttpHeaderSize));
            // extract the HTTP header values from the cache entry
            UINT32 ulRet = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pHttpHeaders);
            pHttpHeaders->SetSize (pCacheHeader->m_ulHttpHeaderSize);
            memcpy (pHttpHeaders->GetBuffer(), /* Flawfinder: ignore */
                    (const char *)dbtHeader.data + sizeof(CacheHeader),
                    pCacheHeader->m_ulHttpHeaderSize);

            IHXValues *pValues = CreateValuesFromBuffer (pHttpHeaders);
            if (HXR_OK != m_pRequest->SetResponseHeaders(pValues))
            {
                LOG ("*** Error setting Response Headers");
            }
	    HX_RELEASE(pHttpHeaders);
            HX_RELEASE(pValues);

            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "WriteChunky at %ld size %ld\n", 0, dbtContent.size);
                fclose(fp);
            }
            #endif
            
            // copy the data from the cache
            m_pChunkyRes->SetData(0, (const char *)dbtContent.data, dbtContent.size);

            m_bCached               = TRUE;
            // These are values expected to be set when data is downloaded
            m_bConnectDone          = TRUE;
            m_bWriteDone            = TRUE;
            m_bReadHeaderDone       = TRUE;
            m_bReadContentsDone     = TRUE;
            m_bKnowContentSize      = TRUE;
            m_nContentSize          = dbtContent.size;
            m_nContentRead          = dbtContent.size;
            m_nHeaderRead           = dbtContent.size;
            m_ulCurrentReadPosition = 0;

            m_strMimeType = (pCacheHeader->m_szMimeType[0]) ? (char*)pCacheHeader->m_szMimeType : "*";

        }
        else
        {
            LOG ("... Cache miss");
        }

	if (dbtHeader.data)
	    free(dbtHeader.data);
	if (dbtContent.data)
	    free(dbtContent.data);
    }
    else
    {
        if (m_pFilename && m_pCacheEntry)
        {
            LOG ("... Already loaded");
        }
        else
        {
            LOG (m_pFilename ? "... No database" : "... No filename");
        }
    }

    if (m_bCached)
    {
        m_bInitPending = FALSE;
    }
}

STDMETHODIMP_(void)
CHTTPFileObject::CacheSupport_ReadDone (void)
{
    /*
     * cache the data
     */
    if (!m_bCacheEnabled)
    {
        // Quietly skip the caching
    }
    else if (m_nContentRead < m_nContentSize)
    {
        // If the size is not right, don't cache
    }
    else if (m_pChunkyRes && !m_pChunkyRes->HasPartialData(m_nContentSize,0))
    {
	// if the chunky res doesn't have the whole resource (yet), don't cache
    }
    else if (m_pCacheEntry == NULL)
    {
        // This should not happen
        LOG (">!  m_pCacheEntry is NULL");
    }
    else if (!m_bKnowContentSize) 
    {
        // If the HTTP header did not contain size info, don't cache
        LOG (">   Unknown Content-Size");
    }
    else if (m_pFilename == NULL)
    {
        // If the URL is null, don't cache
        LOG (">!  No Filename");
    }
    else if (m_bCached == TRUE)
    {
        // This should not happen
        LOG (">   Already cached");
    }
    else if (m_ulExpiryTime <= (UINT32)time(NULL))
    {
        // If already expired, don't cache
    }
    else if (m_nContentRead > MAX_CACHE_SIZE)
    {
        // If too large, don't cache
        LOG (">   Too large to cache");
    }
    else if (m_nContentRead == 0)
    {
        // If too large, don't cache
        LOG (">   Content size is zero, possible redirect");
    }
    else
    {
        // Everthing looks good for caching
        LOG (">   Writing to cache");

        const char* pszMimeType      = m_strMimeType;
        IHXBuffer* pHttpHeader      = NULL;
        IHXValues* pResponseHeaders = NULL;

        if (m_pRequest)
        {
            if (HXR_OK == m_pRequest->GetResponseHeaders(pResponseHeaders))
            {
                pHttpHeader = CreateBufferFromValues(pResponseHeaders);
            }
            else
            {
                LOG ("    SetResponseHeaders(pResponseHeaders) failed");
            }
        }
        else
        {
            LOG ("    m_pRequest is NULL");
        }

        m_pCacheEntry->CleanCache(m_ulCutOffDate);
        m_pCacheEntry->WriteCache (m_nContentRead, m_ulExpiryTime, m_pFilename, pszMimeType, 
                                   pHttpHeader, m_pChunkyRes, m_bMirroredServers);

        HX_RELEASE(pResponseHeaders);
        HX_RELEASE(pHttpHeader);
    }
}

STDMETHODIMP_(void)
CHTTPFileObject::CacheSupport_HandleSuccess(HTTPResponseMessage* pMessage)
{
    MIMEHeaderValue* pHeaderValue    = NULL;
    MIMEHeader*          pHeader         = pMessage->getFirstHeader();
    INT32            lTimeOffset     = 0;

    while (pHeader)
    {
        pHeaderValue = pHeader->getFirstHeaderValue();

        CHXString HeaderString;

        while (pHeaderValue)
        {
            CHXString TempString;

            pHeaderValue->asString(TempString);
            HeaderString += TempString;

            pHeaderValue = pHeader->getNextHeaderValue();
            if (pHeaderValue)
            {
                HeaderString += ", ";
            }
        }
        LOGX((szDbgTemp, "    HTTP Header: %s = '%s'", pHeader->name(), HeaderString));

        if (!strcasecmp(pHeader->name(), "Date"))
        {
            UTCTimeRep* pTimeRepUTCTimeRep = new UTCTimeRep(HeaderString);
            UINT32 ulDateTime = pTimeRepUTCTimeRep->asUTCTimeT();
            lTimeOffset = (INT32)ulDateTime - time(NULL);

            LOGX((szDbgTemp, "    Date value is %lu [%lds/%ldh offset]",
                 ulDateTime, lTimeOffset, (lTimeOffset + (60 * 30))/(60 * 60)));

            delete pTimeRepUTCTimeRep;
        }
        if (!strcasecmp(pHeader->name(), "Expires"))
        {
            UTCTimeRep* pTimeRepUTCTimeRep = new UTCTimeRep(HeaderString);
            m_ulExpiryTime = pTimeRepUTCTimeRep->asUTCTimeT();
            delete pTimeRepUTCTimeRep;
        }
        if (!strcasecmp(pHeader->name(), "Last-Modified"))
        {
            UTCTimeRep* pTimeRepUTCTimeRep = new UTCTimeRep(HeaderString);
            m_ulLastModTime = pTimeRepUTCTimeRep->asUTCTimeT();
            delete pTimeRepUTCTimeRep;
        }
        pHeader = pMessage->getNextHeader();
    }
    if (m_ulLastModTime)
    {
        m_ulLastModTime -= lTimeOffset;
        LOGX((szDbgTemp, "    Last-Modified value is %lu [%lus ago]",
             m_ulLastModTime, time(NULL) - m_ulLastModTime));
    }
    if (m_ulExpiryTime)
    {
        m_ulExpiryTime -= lTimeOffset;
        LOGX((szDbgTemp, "    Expiry value is %lu [%lus hence]",
             m_ulExpiryTime, m_ulExpiryTime - time(NULL)));
    }

    // Find the expiry time by looking at the Time-To-Live value
    CHXString sCacheControl;
    sCacheControl = pMessage->getHeaderValue("Cache-Control");
    if (!sCacheControl.IsEmpty())
    {
        const char *pszCacheControl = NULL;
        UINT32      ulMaxAgeSeconds = 0;

        sCacheControl.MakeLower();
        pszCacheControl = sCacheControl;

        LOGX((szDbgTemp, "    Cache-Control string is '%s'", pszCacheControl));

        if (sscanf(pszCacheControl, "max-age=%lu", &ulMaxAgeSeconds) == 1) 
        {
            LOGX((szDbgTemp, "    MaxAgeSeconds = %lu", ulMaxAgeSeconds));
            m_ulExpiryTime = time(NULL) + ulMaxAgeSeconds;
        }

        // Check for No-Cache entry in Cache-Control
        if (!strncmp(pszCacheControl, "no-cache", 8)
         || !strncmp(pszCacheControl, "no-store", 8) 
         || !strncmp(pszCacheControl, "private", 7) 
         || !strncmp(pszCacheControl, "must-revalidate", 8)) 
        {
            LOG ("    Cacheing disabled for this item");
            m_ulExpiryTime = time(NULL) - 1;
        }
    }
    else if (g_ulDefTtl > 0)
    {
        LOGX((szDbgTemp, "    No expiry time specified, assigning default TTL (%lu sec)", g_ulDefTtl));

        m_ulExpiryTime = time(NULL) + g_ulDefTtl;
    }
}

STDMETHODIMP_(IHXBuffer*)
CHTTPFileObject::CreateBufferFromValues (IHXValues *pHeaderValues)
{    
    IHXBuffer*  pBuffer        = NULL;
    char*        pCurrent       = NULL;
    const char*  pPropName      = NULL;
    IUnknown*    pUnknown   = NULL;
    IHXBuffer*  pPropValue     = NULL;
    
    LOGX ((szDbgTemp, "CreateBufferFromValues (0x%08X)", pHeaderValues));

    HX_RESULT theErr = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pBuffer);
    pBuffer->SetSize(4);  // four signature bytes at beginning   '0x03 0x03 0x03 0x03'
    memset (pBuffer->GetBuffer(), 0x03, 4);

    if (!pHeaderValues)
    {
	HX_ASSERT(0);  // showell
	theErr = HXR_FAIL;
    }

    // for each item in the list, write an entry into the buffer
    if (theErr == HXR_OK)
    {
	// XXX showell - Eventually we want to make callers pass us an
	// IHXKeyValueList, not an IHXValues, because headers should 
	// really only be IHXKeyValueList.  When we make that change, 
	// we will want to use IHXKeyValueList::GetIter and then do
	// iterate with GetPair calls.  But, until then, it's perfectly
	// fine to rely on GetFirstPropertyCString/GetNextPropertyCString,
	// because CKeyValueList supports those methods w/the right behavior.

        theErr = pHeaderValues->GetFirstPropertyCString (pPropName, pPropValue);

//      LOGX ((szDbgTemp, "    GetFirstPropertyCString() returns 0x%08X/0x%08X/Err=0x%08X", pPropName, pPropValue, theErr));

        while(theErr == HXR_OK)
        {
            // copy value pair into aggregate buffer
            UINT32 ulStartLength = pBuffer->GetSize();
            pBuffer->SetSize(ulStartLength + 8 + strlen(pPropName) + pPropValue->GetSize());

            UINT8* pStart =  pBuffer->GetBuffer() + ulStartLength;

            // write key size as octet stream of length four
            UINT32 ulLength = strlen (pPropName);
            *pStart++ = (UINT8) ((ulLength >> 24) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >> 16) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >>  8) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >>  0) & 0x000000ff);
            // write data
            memcpy (pStart, pPropName, ulLength); /* Flawfinder: ignore */
            pStart += ulLength;

            // write data size as octet stream of length four
            ulLength  = pPropValue->GetSize();
            *pStart++ = (UINT8) ((ulLength >> 24) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >> 16) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >>  8) & 0x000000ff);
            *pStart++ = (UINT8) ((ulLength >>  0) & 0x000000ff);
            // write data
            memcpy (pStart, pPropValue->GetBuffer(), ulLength); /* Flawfinder: ignore */
            pStart += ulLength;

            HX_RELEASE(pPropValue);

            theErr = pHeaderValues->GetNextPropertyCString (pPropName, pPropValue);
	}
    }
    else
    {
        LOG ("*** Error creating IHXBuffer for HTTP headers");
    }

    // add {{0, NULL}, {0, NULL}} to terminate list
    pBuffer->SetSize(pBuffer->GetSize() + 16);
    memset (pBuffer->GetBuffer() + pBuffer->GetSize() - 16, 0, 16);

    LOGX ((szDbgTemp, "    HTTP Header buffer is %lu bytes", pBuffer->GetSize()));
    return(pBuffer);
}

STDMETHODIMP_(IHXValues*)
CHTTPFileObject::CreateValuesFromBuffer (IHXBuffer *pBuffer)
{    
    IUnknown*       pUnknown = NULL;
    IHXValues*     pValues = NULL;
    IHXKeyValueList* pNewList = NULL;
    UINT32      ulBufLen = pBuffer->GetSize();
    UINT32      ulIndex = 0;
    UINT8*      pBufData = pBuffer->GetBuffer();

    LOGX ((szDbgTemp, "CreateValuesFromBuffer (Size=%4lu)", ulBufLen));

    if (HXR_OK != m_pCommonClassFactory->CreateInstance(CLSID_IHXKeyValueList, (void**)&pUnknown))
    {
        goto cleanup;
    }

    if (HXR_OK != pUnknown->QueryInterface(IID_IHXKeyValueList, (void**)&pNewList))
    {
	goto cleanup;
    }

    ulIndex = 4;    // Offset of '4' is due to signature bytes
    while (ulIndex < ulBufLen)
    {
        // extract value pair from aggregate buffer
        
        // write size as octet stream of length four
        UINT32 ulPropLength = 0;
        ulPropLength += (pBufData[ulIndex++] << 24) & 0xff000000;
        ulPropLength += (pBufData[ulIndex++] << 16) & 0x00ff0000;
        ulPropLength += (pBufData[ulIndex++] <<  8) & 0x0000ff00;
        ulPropLength += (pBufData[ulIndex++] <<  0) & 0x000000ff;
        // write data
        UINT8* pPropBuf = new UINT8[ulPropLength + 1];
        memcpy (pPropBuf, pBufData + ulIndex, ulPropLength); /* Flawfinder: ignore */
        pPropBuf[ulPropLength] = '\0';

        ulIndex += ulPropLength;

        // write size as octet stream of length four
        UINT32 ulLength = 0;
        ulLength += (pBufData[ulIndex++] << 24) & 0xff000000;
        ulLength += (pBufData[ulIndex++] << 16) & 0x00ff0000;
        ulLength += (pBufData[ulIndex++] <<  8) & 0x0000ff00;
        ulLength += (pBufData[ulIndex++] <<  0) & 0x000000ff;
        // write data
        IHXBuffer* pNewBuffer = new CHXBuffer();
        pNewBuffer->AddRef();
        pNewBuffer->SetSize (ulLength);
        memcpy (pNewBuffer->GetBuffer(), pBufData + ulIndex, ulLength); /* Flawfinder: ignore */

        ulIndex += ulLength;

        if (ulPropLength && ulLength)
        {
	    pNewList->AddKeyValue((const char*)pPropBuf, pNewBuffer);
	}
        else
        {
            LOG ("*** Error creating IHXValues from IHXBuffer");
        }

        HX_VECTOR_DELETE(pPropBuf);
        pNewBuffer->Release();
    }

    if (HXR_OK != pNewList->QueryInterface(IID_IHXValues, (void**)&pValues))
    {
	// XXX showell - Eventually we want to pass around IHXKeyValueLists
	// internally, so we don't have to do this strange stuff w/IHXValues.
	// Until then, we can rely on CKeyValueList implementing IHXValues,
	// so we should never hit this code.
	HX_ASSERT(0);  
        pValues = NULL;
    }

cleanup:

    HX_RELEASE(pNewList);
    HX_RELEASE(pUnknown);

    return(pValues);
}

STDMETHODIMP
CHTTPFileObject::ProcessCacheCompletions (BOOL bRedirected)
{
    if (m_bCached)
    {
	if (m_pMimeMapperResponse && m_bMimeResponsePending == TRUE)
	{
	    const char* pMimeType = NULL;
	    if (!m_strMimeType.IsEmpty())
	    {
		pMimeType = m_strMimeType;
	    }

	    LOGX ((szDbgTemp, "    Idle: Cached MimeType='%s'", NULLOK(m_strMimeType)));
	    m_bMimeResponsePending = FALSE;
	    m_pMimeMapperResponse->MimeTypeFound(HXR_OK, pMimeType);
	}
	else if (bRedirected)
	{
	    // Addref m_pFileResponse here since it may be released before being
	    // assigned in Init(), below.
	    IHXFileResponse* pFileResponse = m_pFileResponse;
	    if (pFileResponse)	pFileResponse->AddRef();

	    // The client hasn't implemented a MimeMapperResponse, but we still
	    // need to make sure that we deal with the cached data.  Call Init()
	    // to do this, which is what is normally done by the MimeTypeFound
	    // callback.
	    Init(HX_FILE_READ, pFileResponse);
	    HX_RELEASE(pFileResponse);
	}
    }
    return HXR_OK;
}


STDMETHODIMP_( void )
CHTTPFileObject::SetDestinationFile( const char *pFilename )
{
    if( pFilename && *pFilename )
    {
	m_bSaveToFile = TRUE;
	m_SaveFileName = pFilename;
	//try deleting existing file with the same name
	ASSERT( !m_SaveFileName.IsEmpty() );
	CHXDirectory Dir;
        Dir.DeleteFile(m_SaveFileName);
    }
    else
	m_bSaveToFile = FALSE;
}

/************************************************************************
*  Method:
*      IHXProxyAutoConfigCallback::GetProxyInfoDone
*  Purpose:
*/
STDMETHODIMP
CHTTPFileObject::GetProxyInfoDone(HX_RESULT status,
				  char*	    pszProxyInfo)
{
    HX_RESULT	rc = HXR_OK;
    PACInfo*	pPACInfo = NULL;

    m_bGetProxyInfoPending = FALSE;

    if (HXR_OK == status && pszProxyInfo)
    {
	::ParsePACInfo(pszProxyInfo, m_pPACInfoList);	
	// at least one PAC entry
	HX_ASSERT(m_pPACInfoList && m_pPACInfoList->GetCount());

	m_PACInfoPosition = m_pPACInfoList->GetHeadPosition();
	pPACInfo = (PACInfo*)m_pPACInfoList->GetNext(m_PACInfoPosition);

	if (pPACInfo && pPACInfo->type != PAC_DIRECT)
	{
	    m_bUseProxy = TRUE;
	    m_nProxyPort = pPACInfo->ulPort;
	    m_strProxyHost = pPACInfo->pszHost;
	}
    }

    rc = _OpenFileExt();

    if (HXR_OK != rc && HXR_OK == m_LastError)
    {
	m_LastError = rc;
    }

    return rc;
}

HX_RESULT
CHTTPFileObject::MangleAllSetCookies(IHXRequest* pRequest)
{
    IHXValues* pResponseHdrs = NULL;
    IHXKeyValueList* pKeyedResHdrs = NULL;
    IUnknown* pUnknown = NULL;
    IUnknown* pNewUnknown = NULL;
    IHXBuffer* pSetCookie = NULL;
    IHXBuffer* pMangledSetCookie = NULL;

    // Get the response headers from the request
    pRequest->GetResponseHeaders(pResponseHdrs);
    if (pResponseHdrs)
    {
 	// Find out if the response headers support IHXKeyValueList
	pResponseHdrs->QueryInterface(IID_IHXKeyValueList, 
	    (void**)&pKeyedResHdrs);

	if (pKeyedResHdrs)
	{
	    IHXKeyValueListIterOneKey* pListIter = NULL;

	    pKeyedResHdrs->GetIterOneKey("Set-Cookie",pListIter);

	    while (pListIter->GetNextString(pSetCookie) == HXR_OK)
	    {
		// If we found a buffer and it's not already mangled...
		if (pSetCookie &&
		    strncmp((char*)pSetCookie->GetBuffer(), "RSG2!", 5))
		{
		    // *** Mangle it *** //
		    MangleSetCookie(pSetCookie, pMangledSetCookie);
		    if (pMangledSetCookie)
		    {
			pListIter->ReplaceCurr(pMangledSetCookie);
		    }
		    HX_RELEASE(pMangledSetCookie);
		}
		HX_RELEASE(pSetCookie);
	    }
	    HX_RELEASE(pListIter);
	}
	else
	{
	    // Retrieve the only "Set-Cookie" value from the response headers
	    pResponseHdrs->GetPropertyCString("Set-Cookie", pSetCookie);

	    // If we found a buffer and it is not already mangled...
	    if (pSetCookie &&
		strncmp((char*)pSetCookie->GetBuffer(), "RSG2!", 5))
	    {
		// *** Mangle it *** //
		MangleSetCookie(pSetCookie, pMangledSetCookie);
		if (pMangledSetCookie)
		{
		    // Replace the original Set-Cookie value
		    pResponseHdrs->SetPropertyCString("Set-Cookie", 
			pMangledSetCookie);
		}
		HX_RELEASE(pMangledSetCookie);
	    }
	    HX_RELEASE(pSetCookie);
	}
	HX_RELEASE(pKeyedResHdrs);
    }
    HX_RELEASE(pResponseHdrs);

    return HXR_OK;
}

HX_RESULT
CHTTPFileObject::MangleSetCookie(IHXBuffer* pInput, 
				 REF(IHXBuffer*) pOutput)
{
    HX_RESULT hResult = HXR_OK;
    char* pDomain = NULL;
    char* pPath = NULL;
    char* pSetCookie = NULL;
    char* pCursor = NULL;
    char* pStart = NULL;
    char* pEnd = NULL;
    CHXURL* pHXURL = NULL;
    IHXValues* pURLProps = NULL;
    IHXBuffer* pBuffer = NULL;
    char* pFinal = NULL;
    UINT32 ulDomainLen = 0;
    UINT32 ulPathLen = 0;
    char* pOutputBuf = NULL;
    char* pOutputStart = NULL;
    BOOL bIsDomain = FALSE;

    pSetCookie = (char*)pInput->GetBuffer();
    
    for (pCursor = pSetCookie; *pCursor; pCursor++)
    {
	if (*pCursor == ';')
	{
	    pCursor++;

	    // Check if the next value is the domain or path
	    if (strncasecmp(pCursor, "domain", 6) == 0)
	    {
		pStart = pCursor + 7;
		pEnd = pStart;

		while (*pEnd && *pEnd != ';')
		{
		    pEnd++;
		}

		if (pEnd > pStart)
		{
		    pDomain = new char[pEnd - pStart + 1];
		    strncpy(pDomain, pStart, pEnd - pStart); /* Flawfinder: ignore */
		    pDomain[pEnd - pStart] = '\0';
		    ulDomainLen = pEnd - pStart;

		    bIsDomain = TRUE;
		}
	    }
	    else if (strncasecmp(pCursor, "path", 4) == 0)
	    {
		pStart = pCursor + 5;
		pEnd = pStart;

		while (*pEnd && *pEnd != ';')
		{
		    pEnd++;
		}

		if (pEnd > pStart)
		{
		    pPath = new char[pEnd - pStart + 1];
		    strncpy(pPath, pStart, pEnd - pStart); /* Flawfinder: ignore */
		    pPath[pEnd - pStart] = '\0';
		    ulPathLen = pEnd - pStart;
		}
	    }

	    pCursor--;
	}

	// If we've found both, we can stop looking
	if (pDomain && pPath)
	{
	    break;
	}
    }

    // If we didn't find a domain or path setting
    if (!pDomain || !pPath)
    {
	// Figure it out from the URL
	pHXURL = new CHXURL(m_pFilename);

	pURLProps = pHXURL->GetProperties();
	if (pURLProps)
	{
	    if (!pDomain)
	    {
		pURLProps->GetPropertyBuffer(PROPERTY_HOST, pBuffer);
		if (pBuffer)
		{
		    StrAllocCopy(pDomain, (char*)pBuffer->GetBuffer());
		    ulDomainLen = pBuffer->GetSize() - 1;
		}
		HX_RELEASE(pBuffer);
	    }

	    if (!pPath)
	    {
		pURLProps->GetPropertyBuffer(PROPERTY_PATH, pBuffer);
		if (pBuffer)
		{
		    StrAllocCopy(pPath, (char*)pBuffer->GetBuffer());
		    ulPathLen = pBuffer->GetSize() - 1;
		}
		HX_RELEASE(pBuffer);
	    }
	}
	HX_RELEASE(pURLProps);
	HX_DELETE(pHXURL);
    }

    if (pDomain && pPath)
    {
	BOOL bPastName = FALSE;

	// Create the final output buffer
	m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, 
	    (void**)&pOutput);

	// Set the maximum buffer size, allowing for the "RSG2!!%lu!", 
	// "RSG2!!" and ";path=/", that we are going to add
	int lenOutBuf = pInput->GetSize() + ulDomainLen + ulPathLen + 21;
	pOutput->SetSize(lenOutBuf);

	pOutputBuf = (char*)pOutput->GetBuffer();
	pOutputStart = pOutputBuf;

	// Step through each character in the Set-Cookie value
	for (pCursor = pSetCookie; *pCursor; pCursor++)
	{
	    if (pCursor == pSetCookie)
	    {
		// Add RSG2!domain!bIsDomain! to the 
		// beginning of the cookie name
		SafeSprintf(pOutputBuf, lenOutBuf, "RSG2!%s!%s!", /* Flawfinder: ignore */
		pDomain, bIsDomain ? "1" : "0");
		pOutputBuf += ulDomainLen + 8;

		*pOutputBuf++ = *pCursor;
	    }
	    else if (!bPastName && *pCursor == '=')
	    {
		*pOutputBuf++ = *pCursor;

		// Add RSG2!path! to the beginning of the cookie value
		SafeSprintf(pOutputBuf, lenOutBuf,  "RSG2!%s!", pPath); /* Flawfinder: ignore */
		pOutputBuf += ulPathLen + 6;

		bPastName = TRUE;
	    }
	    else if (*pCursor == ';' &&
		     (strncasecmp(pCursor + 1, "domain", 6) == 0 ||
		      strncasecmp(pCursor + 1, "path", 4) == 0))
	    {
		// Skip the domain or path
		pCursor++;
		while (*pCursor && *pCursor != ';')
		{
		    pCursor++;
		}
		pCursor--;
	    }
	    else if (*pCursor == ';' &&
		     (strncasecmp(pCursor + 1, "expires", 7) == 0))
	    {
		// Make sure that "expires" is lowercase, to work around a
		// client bug in 7.0 Beta1 and earlier players that will
		// ignore it if it is not all lowercase - DPS
		strncpy(pOutputBuf, ";expires", 8); /* Flawfinder: ignore */
		pOutputBuf += 8;
		pCursor += 7;
	    }
	    else
	    {
		*pOutputBuf++ = *pCursor;
	    }
	}

	// Add a path of "/", so that this cookie will always get sent
	// back to this server
	strncpy(pOutputBuf, ";path=/", 7); /* Flawfinder: ignore */
	pOutputBuf += 7;

	*pOutputBuf++ = '\0';

	// Copy the mangled name and value onto the end of the
	// mangled cookies value, so that it will be sent with
	// the next URL request if we are immediately redirected
	StoreMangledCookie(pOutputStart);

	// Set the output buffer size
	pOutput->SetSize(pOutputBuf - pOutputStart);
    }
    else
    {
	hResult = HXR_FAIL;
	pOutput = NULL;
    }

    HX_VECTOR_DELETE(pDomain);
    HX_VECTOR_DELETE(pPath);

    return hResult;
}

HX_RESULT
CHTTPFileObject::StoreMangledCookie(char* pCookie)
{
    char* pStart = NULL;
    char* pEnd = NULL;
    UINT32 ulLen = 0;
    UINT32 ulOrigSize = 0;
    UINT32 ulNewSize = 0;
    IHXBuffer* pMangledCookies = NULL;
    char* pOldBuffer = NULL;
    char* pBuffer = NULL;

    // Create a new cookie buffer
    m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, 
	(void**)&pMangledCookies);
    if (!pMangledCookies)
    {
	return HXR_FAIL;
    }

    pStart = pCookie;
    pEnd = pStart;
    while (*pEnd && *pEnd != ';')
    {
	pEnd++;
    }
    ulLen = pEnd - pStart;

    if (m_pMangledCookies)
    {
	ulOrigSize = m_pMangledCookies->GetSize();
	pOldBuffer = (char*)m_pMangledCookies->GetBuffer();
    }

    // Set the new buffer size
    ulNewSize = ulOrigSize + ulLen + 1;
    pMangledCookies->SetSize(ulNewSize);
    pBuffer = (char*)pMangledCookies->GetBuffer();

    if (ulOrigSize)
    {
	// Add it to the end of the existing cookies
	strncpy(pBuffer, pOldBuffer, ulOrigSize - 1); /* Flawfinder: ignore */
	pBuffer[ulOrigSize - 1] = ';';
	strncpy(pBuffer + ulOrigSize, pCookie, ulLen); /* Flawfinder: ignore */
    }
    else
    {
	// No cookies exist, so just add it
	strncpy(pBuffer, pCookie, ulLen); /* Flawfinder: ignore */
    }
    pBuffer[ulOrigSize + ulLen] = '\0';

    // Replace the existing value
    HX_RELEASE(m_pMangledCookies);
    m_pMangledCookies = pMangledCookies;

    return HXR_OK;
}

HX_RESULT
CHTTPFileObject::UnmangleAllCookies(IHXRequest* pRequest)
{
    HX_RESULT hResult = HXR_OK;
    const char* pURL = NULL;
    IHXValues* pRequestHdrs = NULL;
    IHXBuffer* pCookie = NULL;
    char* pInput = NULL;
    char* pTemp = NULL;
    UINT32 ulTempPos = 0;
    IHXBuffer* pFinalBuf = NULL;
    char* pFinal = NULL;
    UINT32 ulFinalPos = 0;
    BOOL bValidCookie = FALSE;
    UINT32 i = 0;
    IHXBuffer* pURLHost = NULL;
    IHXBuffer* pURLPath = NULL;
    IHXBuffer* pDomain = NULL;
    IHXBuffer* pPath = NULL;
    BOOL bIsDomain = FALSE;

    // Get the requested URL
    hResult = pRequest->GetURL(pURL);
    if (FAILED(hResult) || !pURL)
    {
	HX_ASSERT(0);
	return HXR_UNEXPECTED;
    }

    // Separate the URL into a domain and path
    hResult = GetHostAndPath(pRequest, pURLHost, pURLPath);
    if (FAILED(hResult))
    {
	return HXR_FAIL;
    }

    // Get the Cookie header if it exists
    pRequest->GetRequestHeaders(pRequestHdrs);
    if (pRequestHdrs)
    {
	pRequestHdrs->GetPropertyCString("Cookie", pCookie);
	if (pCookie && pCookie->GetSize())
	{
	    // Create a temporary buffer for use in unmangling each cookie,
	    // and another buffer for the final unmangled output
	    pTemp = new char[pCookie->GetSize()];

	    // Create the final output buffer
	    pFinalBuf = new CHXBuffer();
	    pFinalBuf->AddRef();

	    pFinalBuf->SetSize(pCookie->GetSize());
	    pFinal = (char*)pFinalBuf->GetBuffer();

	    // Iterate through each cookie in the buffer
	    pInput = (char*)pCookie->GetBuffer();

	    for (UINT32 i = 0; i < pCookie->GetSize(); i++)
	    {
		if (pInput[i] != ';' && pInput[i] != '\0')
		{
		    pTemp[ulTempPos++] = pInput[i];
		}
		else
		{
		    // We've got a complete cookie
		    pTemp[ulTempPos] = '\0';

		    // Find out if the cookie is mangled
		    if (!strncmp(pTemp, "RSG2!", 5))
		    {
			// If so, unmangle it
			hResult = UnmangleCookie(pTemp, ulTempPos, 
			    pDomain, bIsDomain, pPath);
			if (HXR_OK == hResult && pDomain && pPath)
			{
			    // Compare the domain and path against those 
			    // of the requested URL, to see if this cookie
			    // should be sent on to the http server
			    hResult = CompareDomains(pURLHost, 
				pDomain, bIsDomain);
			    if (SUCCEEDED(hResult))
			    {
				hResult = ComparePaths(pURLPath, pPath);
				if (SUCCEEDED(hResult))
				{
				    // This cookie does apply to the URL
				    // we are about to request
				    bValidCookie = TRUE;
				}
			    }
			}
			HX_RELEASE(pDomain);
			HX_RELEASE(pPath);
		    }
		    else
		    {
			bValidCookie = TRUE;
		    }

		    if (bValidCookie)
		    {
			if (ulFinalPos)
			{
			    pFinal[ulFinalPos++] = ';';
			}

			// Copy this cookie to the final output buffer
			for (UINT32 j = 0; pTemp[j]; j++)
			{
			    pFinal[ulFinalPos++] = pTemp[j];
			}
		    }

		    HX_RELEASE(pDomain);
		    HX_RELEASE(pPath);

		    ulTempPos = 0;
		    bValidCookie = FALSE;
		}
	    }
	    pFinal[ulFinalPos++] = '\0';
	    pFinalBuf->SetSize(ulFinalPos);

	    // Add it to the request headers to replace the earlier one
	    pRequestHdrs->SetPropertyCString("Cookie", pFinalBuf);
	    HX_RELEASE(pFinalBuf);

	    HX_VECTOR_DELETE(pTemp);
	}
	HX_RELEASE(pCookie);
    }
    HX_RELEASE(pRequestHdrs);
    HX_RELEASE(pURLHost);
    HX_RELEASE(pURLPath);

    return HXR_OK;
}

HX_RESULT
CHTTPFileObject::UnmangleCookie(char* pCookie, 
				UINT32 ulCookieLen,
				REF(IHXBuffer*) pDomain, 
				REF(BOOL) bIsDomain,
				REF(IHXBuffer*) pPath)
{
    HX_RESULT hResult = HXR_OK;
    BOOL bValid = FALSE;
    char* pInput = NULL;
    char* pOutput = NULL;
    char* pData = NULL;
    UINT32 ulDataPos = 0;

    pDomain = NULL;
    pPath = NULL;

    // Check if the name is mangled
    if (!strncmp(pCookie, "RSG2!", 5))
    {
	pOutput = pCookie;
	pInput = pCookie + 5;

	if (*pInput != '!')
	{
	    // Copy the real domain into a new buffer
	    pDomain = new CHXBuffer();
	    pDomain->AddRef();

	    pDomain->SetSize(ulCookieLen);
	    pData = (char*)pDomain->GetBuffer();

	    for ( ; *pInput && *pInput != '!'; pInput++)
	    {
		pData[ulDataPos++] = *pInput;
	    }
	    pData[ulDataPos++] = '\0';

	    // Finalize the domain buffer
	    pDomain->SetSize(ulDataPos);

	    if (*pInput && ulDataPos > 1)
	    {
		// Skip the ending '!'
		pInput++;

		// Find out if the domain string we just 
		// retrieved was a domain or a host
		if (*pInput && *(pInput + 1) && *(pInput + 2))
		{
		    if (*pInput == '0')
		    {
			bIsDomain = FALSE;
		    }
		    else 
		    {
			bIsDomain = TRUE;
		    }
                
		    // Skip the digit and ending '!'
		    pInput += 2;

		    // Copy the cookie name into the output buffer
		    for ( ; *pInput && *pInput != '='; pInput++)
		    {
			*pOutput++ = *pInput;
		    }

		    *pOutput++ = '=';

		    if (*pInput)
		    {
			// Skip the ending '='
			pInput++;

			// Make sure the value is mangled
			if (!strncmp(pInput, "RSG2!", 5))
			{
			    pInput += 5;

			    // Copy the real path into a new buffer
			    pPath = new CHXBuffer();
			    pPath->AddRef();

			    pPath->SetSize(ulCookieLen);
			    pData = (char*)pPath->GetBuffer();

			    ulDataPos = 0;
			    for ( ; *pInput && *pInput != '!'; pInput++)
			    {
				pData[ulDataPos++] = *pInput;
			    }
			    pData[ulDataPos++] = '\0';

			    // Finalize the path buffer
			    pPath->SetSize(ulDataPos);

			    if (*pInput && ulDataPos > 1)
			    {
				// Skip the ending '!'
				pInput++;

				// Copy the cookie value into the output buffer
				for ( ; *pInput; pInput++)
				{
				    *pOutput++ = *pInput;
				}

				// NULL terminate the final output buffer
				*pOutput++ = '\0';

				// If we made it this far, we must have a
				// valid cookie, domain, and path
				bValid = TRUE;
			    }
			}
		    }
		}
	    }
	}
    }

    if (!bValid)
    {
	HX_RELEASE(pDomain);
	HX_RELEASE(pPath);
    }

    return (bValid ? HXR_OK : HXR_FAIL);
}

// 
// Method: CompareDomains
//
// Description: Compares the test domain against the URL domain in accordance
//     with the HTTP Cookie specification. If the two match, returns HXR_OK.
//
HX_RESULT
CHTTPFileObject::CompareDomains(IHXBuffer* pURLHost, 
				IHXBuffer* pTestDomain, 
				BOOL bIsDomain)
{
    HX_RESULT hResult = HXR_OK;

    if (bIsDomain)
    {
	CHXString cDomainCopy((const char*)pTestDomain->GetBuffer());

	CHXString cHostCopy((const char*)pURLHost->GetBuffer());

	// Now we compare the domain (from the cookie itself) with
	// the rightmost characters of the host. For instance,
	// a domain of ".bar.com" would match with a host (passed in)
	// of "foo.bar.com", "www.bar.com", etc. but would NOT match
	// a host of "bar.com".
	CHXString cHostRight = cHostCopy.Right(cDomainCopy.GetLength());

	if (cHostRight != cDomainCopy)
	{
	    // No match
	    hResult = HXR_FAIL;
	}
    }
    else if (strcasecmp((const char*)pTestDomain->GetBuffer(), 
			(const char*)pURLHost->GetBuffer()))
    {
	// Hostname matchup failed
	hResult = HXR_FAIL;
    }

    return hResult;
}

// 
// Method: ComparePaths
//
// Description: Compares the test path against the URL path in accordance
//     with the HTTP Cookie specification. If the two match, returns HXR_OK.
//
HX_RESULT
CHTTPFileObject::ComparePaths(IHXBuffer* pURLPath,
			      IHXBuffer* pTestPath)
{
    // Shorter strings always come last so there can be no ambiquity
    if (pTestPath && 
	!strncmp((const char*)pURLPath->GetBuffer(), 
		 (const char*)pTestPath->GetBuffer(),
		 pTestPath->GetSize() - 1))
    {
	return HXR_OK;
    }

    return HXR_FAIL;
}

// 
// Method: GetHostAndPath
//
// Description: Parses the URL in the given request object into a domain/host
//     and path, in accordance with the HTTP Cookie specification.
//
HX_RESULT 
CHTTPFileObject::GetHostAndPath(IHXRequest* pRequest,
				REF(IHXBuffer*) pHostStr,
				REF(IHXBuffer*) pPathStr)
{
    HX_RESULT hResult = HXR_OK;
    const char* pURL = NULL;

    hResult = pRequest->GetURL(pURL);
    if (SUCCEEDED(hResult) && pURL)
    {
        CHXURL cURL(pURL);
        hResult = cURL.GetLastError();
        if (SUCCEEDED(hResult))
        {
            IHXBuffer* pHost = NULL;

	    IHXValues* pHeader = cURL.GetProperties();
	    if (pHeader)
	    {
                hResult = pHeader->GetPropertyBuffer(PROPERTY_HOST, pHost);
                if (SUCCEEDED(hResult))
		{
                    IHXBuffer* pFullPath = NULL;

                    hResult = pHeader->GetPropertyBuffer(PROPERTY_FULLPATH, pFullPath);

                    if (SUCCEEDED(hResult))
		    {
                        HX_RELEASE(pHostStr);
                        pHostStr = pHost;
                        pHostStr->AddRef();

                        HX_RELEASE(pPathStr);
                        pPathStr = pFullPath;
                        pPathStr->AddRef();
		    }
                    HX_RELEASE(pFullPath);
		}
                HX_RELEASE(pHost);
	    }
	    else
	    {
		hResult = HXR_FAIL;
	    }
	    HX_RELEASE(pHeader);
        }
    }
    else
    {
        hResult = HXR_INVALID_PARAMETER;
    }

    return hResult;
}

HX_RESULT
CHTTPFileObject::DePerplexBuffer(IHXBuffer* pInput, 
				 REF(IHXBuffer*) pOutput)
{
    HX_RESULT hResult = HXR_OK;
    CHXPerplex perplex;

    hResult = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, 
	(void**)&pOutput);
    if (SUCCEEDED(hResult))
    {
	hResult = perplex.DePerplex(pInput, pOutput);
    }

    return hResult;
}

void
CHTTPFileObject::ReportConnectionFailure()
{
    if (m_pErrorMessages && m_pFilename)
    {
	char* pTemp = NULL;
	
	int lenTemp = strlen(m_pFilename) + 256;
	pTemp = new char[lenTemp];
	SafeSprintf(pTemp, lenTemp,/* Flawfinder: ignore */
	    "HTTPFSys: Failed to connect to the server at "
	    "the following URL: %s.", 
	    m_pFilename);

	m_pErrorMessages->Report(HXLOG_ERR, 
                                 HXR_FAIL, 
                                 HXR_OK, 
                                 pTemp,
                                 NULL);
	HX_VECTOR_DELETE(pTemp);
    }
}

void
CHTTPFileObject::ReportConnectionTimeout()
{
    if (m_pErrorMessages && m_pFilename)
    {
	char* pTemp = NULL;
	int lenTemp = strlen(m_pFilename) + 256;
	pTemp = new char[lenTemp];
	SafeSprintf(pTemp, lenTemp,/* Flawfinder: ignore */
	    "HTTPFSys: Failed to retrieve the following "
	    "URL because the web server could not be contacted "
	    "within %lu seconds: %s.", 
	    m_nConnTimeout / 1000, m_pFilename);

	m_pErrorMessages->Report(HXLOG_ERR, 
				 HXR_NET_CONNECT, 
				 HXR_OK, 
				 pTemp,
				 NULL);
	HX_VECTOR_DELETE(pTemp);
    }
}

void
CHTTPFileObject::ReportServerTimeout()
{
    if (m_pErrorMessages && m_pFilename)
    {
	if (m_bOnServer)
	{
	    char* pTemp = NULL;
		int lenTemp = strlen(m_pFilename) + 256;
	    pTemp = new char[lenTemp];
	    SafeSprintf(pTemp, lenTemp,/* Flawfinder: ignore */
		"HTTPFSys: Failed to retrieve the following "
		"URL because the web server stopped sending data "
		"for more than %lu seconds: %s.", 
		m_nServerTimeout / 1000, m_pFilename);

	    m_pErrorMessages->Report(HXLOG_ERR, 
				     HXR_SERVER_TIMEOUT, 
				     HXR_OK, 
				     pTemp,
				     NULL);
	    HX_VECTOR_DELETE(pTemp);
	}
	else
	{
	    // The client will know how to present this to the user
            m_pErrorMessages->Report(HXLOG_ERR, 
                                     HXR_SERVER_TIMEOUT, 
                                     HXR_OK, 
                                     NULL,
                                     NULL);
	}
    }
}

void
CHTTPFileObject::ReportDocumentMissing()
{
    if (m_pErrorMessages && m_pFilename)
    {
	char* pTemp = NULL;
	int lenTemp = strlen(m_pFilename) + 256;
	pTemp = new char[lenTemp];
	SafeSprintf(pTemp, lenTemp,/* Flawfinder: ignore */
	    "HTTPFSys: Web server reported 'File Not Found' "
	    "for the following URL: %s.", 
	    m_pFilename);

	m_pErrorMessages->Report(HXLOG_ERR, 
                                 HXR_DOC_MISSING, 
                                 HXR_OK, 
                                 pTemp,
                                 NULL);
	HX_VECTOR_DELETE(pTemp);
    }
}

void
CHTTPFileObject::ReportGeneralFailure()
{
    if (m_pErrorMessages && m_pFilename)
    {
	char* pTemp = NULL;
	int lenTemp = strlen(m_pFilename) + 256;
	pTemp = new char[lenTemp];
	SafeSprintf(pTemp, lenTemp,/* Flawfinder: ignore */
	    "HTTPFSys: An error occurred while attempting "
	    "to retrieve the following URL: %s.", 
	    m_pFilename);

	m_pErrorMessages->Report(HXLOG_ERR, 
                                 HXR_FAIL, 
                                 HXR_OK, 
                                 pTemp,
                                 NULL);
	HX_VECTOR_DELETE(pTemp);
    }
}

HX_RESULT
CHTTPFileObject::DecodeChunkedEncoding(HTTPChunkedEncoding*&	pChunkedEncoding, 
				       const char*		pChunk, 
				       int			l)
{
    HX_RESULT	rc = HXR_OK;
    char*	errstr = NULL;

    while (l > 0) 
    {
	if (CE_HEADER_READY == pChunkedEncoding->state) 
	{
	    // parse the chunk head
            if( pChunkedEncoding->buf )
            {
                pChunkedEncoding->size = strtol(pChunkedEncoding->buf, &errstr, 16);    
            }
            else
            {
                rc = HXR_UNEXPECTED;
                break;
            }
            
	    if (pChunkedEncoding->size > 0)
	    {
		if (pChunkedEncoding->size >= MAX_CHUNK_SIZE)
		{
		    HX_VECTOR_DELETE(pChunkedEncoding->buf);

		    pChunkedEncoding->buf = new char[pChunkedEncoding->size + 1];
                    if( pChunkedEncoding->buf )
                    {
                        memset(pChunkedEncoding->buf, 0, pChunkedEncoding->size + 1);
                    }
                    else
                    {
                        rc = HXR_OUTOFMEMORY;
                        break;
                    }
		}
		else
		{
		    memset(pChunkedEncoding->buf, 0, pChunkedEncoding->size + 1);
		}
		 
		pChunkedEncoding->read = 0;
		pChunkedEncoding->state = CE_BODY_PENDING;
	    }
	    else if (pChunkedEncoding->size == 0)
	    {		
		pChunkedEncoding->lastchunk = TRUE;
		pChunkedEncoding->read = 0;
		pChunkedEncoding->state = CE_BODY_PENDING;
	    }
	    else
	    {
		rc = HXR_FAILED;
		break;
	    }
	} 
	else if (CE_BODY_READY == pChunkedEncoding->state) 
	{
	    // parse the chunk body
	    if (pChunkedEncoding->lastchunk) 
	    {
		HX_VECTOR_DELETE(pChunkedEncoding->buf);
		HX_DELETE(pChunkedEncoding);
		m_bReadContentsDone = TRUE;
		break;
	    }

            #ifdef CREATE_LOG_DUMP
            FILE *fp = fopen(LOG_DUMP_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "WriteChunky at %ld size %ld\n", m_nContentRead, pChunkedEncoding->size);
                fclose(fp);
            }
            #endif
	    
            m_pChunkyRes->SetData(m_nContentRead, pChunkedEncoding->buf, pChunkedEncoding->size);
	    m_nContentRead += pChunkedEncoding->size;

	    memset(pChunkedEncoding->buf, 0, MAX_CHUNK_SIZE);
	    pChunkedEncoding->read = 0;
	    pChunkedEncoding->state = CE_HEADER_PENDING;
	} 
	else if (*pChunk == CR && *(pChunk+1) == LF) 
	{
            // first CRLF marks the chunk header
            if (CE_HEADER_PENDING == pChunkedEncoding->state)
            {
                pChunkedEncoding->state = CE_HEADER_READY;
            }
            // ignore the CRLF within the chunk body while we are
            // still reading it
            else if (pChunkedEncoding->read < pChunkedEncoding->size)
            {
                pChunkedEncoding->buf[pChunkedEncoding->read++] = *pChunk;
            }
            // last CRLF marks the end of chunk
            else if (CE_BODY_PENDING == pChunkedEncoding->state)
            {
                HX_ASSERT(pChunkedEncoding->read == pChunkedEncoding->size);

                // Remote HTTP server is sending more data than specified by 
                // chunk-size. This may be an attack on the server, so don't 
                // accept data sent in this fashion.

                // This should NEVER happen. This condition should be caught
                // earlier, but just in case fail gracefully anyway.
                if (pChunkedEncoding->read > pChunkedEncoding->size)
                {
                    rc = HXR_UNEXPECTED;
                    break;
                }

                pChunkedEncoding->state = CE_BODY_READY;
            }
        }
        else
        {
            // chunk-size exceeded MAX_CHUNK_SIZE in length.
            // A hex-number >=1024 digits is very suspicious.

            if ((CE_HEADER_PENDING == pChunkedEncoding->state)
            &&  (pChunkedEncoding->read >= MAX_CHUNK_SIZE))
            {
                rc = HXR_UNEXPECTED;
                break;
            }

            // Remote HTTP server is sending more data than specified by 
            // chunk-size. This may be an attack on the server, so don't 
            // accept data sent in this fashion.

            else if ((CE_BODY_PENDING == pChunkedEncoding->state)
                 &&  (pChunkedEncoding->read >= pChunkedEncoding->size))
            {
                HX_ASSERT(pChunkedEncoding->size > 0);

                // If read is ever > size, we're in trouble--
                // the buffer has already been overrun.
                HX_ASSERT(pChunkedEncoding->read == pChunkedEncoding->size);

                rc = HXR_UNEXPECTED;
                break;
            }

            HX_ASSERT((CE_HEADER_PENDING == pChunkedEncoding->state) || (CE_BODY_PENDING == pChunkedEncoding->state));

            pChunkedEncoding->buf[pChunkedEncoding->read++] = *pChunk;
        }
        pChunk++, l--;
    }

    return rc;
}

