/*
   Enhanced NCSA Mosaic from Spyglass
   "Guitar"

   Copyright 1994 Spyglass, Inc.
   All Rights Reserved

   Author(s):
   Jim Seidman      jim@spyglass.com
*/

#include "all.h"
#include "history.h"
#include "htmlutil.h"
#include "wc_html.h"
#include "blob.h"
#define TIMER_PULL 1022
#ifdef FEATURE_IAPI
#include "w32dde.h"
#endif
#ifdef FEATURE_INTL
#define IEXPLORE
#include <fechrcnv.h>
#endif

#ifdef FEATURE_TESTHOOK
// defined in dumpanch.c
        extern void TestDumpAnchors(struct _www*);
        extern void TestSignalLoadDone(WORD);
#endif

#ifdef FEATURE_KEEPALIVE
#define KEEPALIVE_TIME 60000
#endif

static void x_DoLoadDocument(struct Mwin *tw, struct DestInfo *pDest,
							 BOOL bRecord, BOOL bPost, BOOL bNoDocCache, 
							 BOOL bNoImageCache, BOOL bAuthFailCacheOK,
							 CONST char * szPostData, CONST char *szReferer,
								BOOL fLoadFromDCacheOK);

/* DDE result information structure passed to IssueURLResult() */

typedef struct dderesultinfo
{
    /* DDE transaction ID */

    LONG lTransID;

    /* window ID */

    LONG lSerialID;

    /* transaction result */

    BOOL bResult;

    /* referent's URL */

    PSTR pszURL;

    /* referent's MIME type */

    HTAtom htaMIMEType;
}
DDERESULTINFO;
DECLARE_STANDARD_TYPES(DDERESULTINFO);

#ifdef DEBUG

PRIVATE_CODE BOOL IsValidPCDDERESULTINFO(PCDDERESULTINFO pcdderi)
{
    /* bResult may be any value. */

    /* BUGBUG: Beef up validation for lTransID and lSerialID. */

    return	(   IS_VALID_READ_PTR(pcdderi, CDDERESULTINFO)
			 && (   !pcdderi->pszURL
				 || IS_VALID_STRING_PTR(pcdderi->pszURL, STR))
			 && (   !pcdderi->htaMIMEType
				 || EVAL(IsValidHTAtom(pcdderi->htaMIMEType))));
}

#endif  /* DEBUG */

/* Ensure that an http URL has a slash after the system name.  This function is suitable
   for "fixing" both URLs and proxy server specifications */
static void x_EnforceHostSlash(char *url)
{
	char * p = url;

	while (*p && *p!=':')
	{
		if (isupper(*p))
			*p = tolower(*p);
		p++;
	}

	if (!strncmp(url, "http://", 7))
	{
		if (!strchr(url + 7, '/'))
			strcat(url, "/");
	}
#ifdef HTTPS_ACCESS_TYPE
	if (!strncmp(url, "https://", 8))
	{
		if (!strchr(url + 8, '/'))
			strcat(url, "/");
	}
#endif

#ifdef SHTTP_ACCESS_TYPE
	if (!strncmp(url, "shttp://", 8))
	{
		if (!strchr(url + 8, '/'))
			strcat(url, "/");
	}
#endif
}


/* Find the element index for a local anchor */
int TW_FindLocalAnchor(struct _www *pdoc, char *name)
{
	int i;
	int nameLen;

	nameLen = strlen(name);

	if (pdoc->elementCount)
	{
		for (i = 0; i >= 0; i = pdoc->aElements[i].next)
		{
			if (pdoc->aElements[i].lFlags & ELEFLAG_NAME)
			{
				if (pdoc->aElements[i].nameLen == nameLen)
				{
					if (0 == strncmp(name, &(pdoc->pool[pdoc->aElements[i].nameOffset]), nameLen))
					{
						break;
					}
				}
			}
		}
	}
	return i;
}

int TW_AddToHistory(struct Mwin *tw, char *url)
{
	char *mycopy;
	char *last;

	XX_DMsg(DBG_HIST, ("Adding to window history: %s\n", url));

	while (tw->history_index--)
	{
		mycopy = HTList_removeLastObject(tw->history);
		GTR_FREE(mycopy);
#ifdef FEATURE_INTL
		HTList_removeLastObject(tw->MimeHistory);
#endif
	}

	last = HTList_objectAt(tw->history, 0);
	if (!last || strcmp(url, last))
	{
		mycopy = GTR_strdup(url);

		HTList_addObject(tw->history, mycopy);
		tw->history_index = HTList_indexOf(tw->history, mycopy);
#ifdef FEATURE_INTL
		HTList_addObject(tw->MimeHistory, (tw->w3doc)? (void *)tw->w3doc->iMimeCharSet: (void *)tw->iMimeCharSet);
#endif
	}
	else
		tw->history_index = 0;

	return 0;
}

/* This is meant to be a convenient interlude function which both reads in the images
   and then reformats the document if necessary.  It doesn't use ppInfo at all. */
int GDOC_LoadImages_Async(struct Mwin *tw, int nState, void **ppInfo)
{
 	struct Params_GDOC_LoadImages *pparams = NULL;

 	if (ppInfo)
 	{
 		pparams = *ppInfo;
 	}

	if (tw == NULL) return STATE_DONE;

	switch (nState)
	{
		case STATE_INIT:
		{
			struct Params_Image_LoadAll *pil;

			pil = GTR_MALLOC(sizeof(*pil));
			pil->tw = tw;
 			if (pparams)
 			{
 				pil->bLocalOnly = pparams->bLocalOnly;
 			}
 			else
 			{
 				pil->bLocalOnly = !gPrefs.bAutoLoadImages;
 			}
#ifdef FEATURE_IMG_THREADS
			pil->bNoImageCache = FALSE;
			pil->decoderObject = NULL;
			pil->parentThread = NULL;
			pil->bJustOne = FALSE;

			//  Image_LoadAll_Async tolerates pil->tw not being GIMGMASTER
#endif
			Async_DoCall(Image_LoadAll_Async, pil);
			return STATE_OTHER;
		}

		case STATE_OTHER:
		case STATE_ABORT:
		{
			if (W3Doc_CheckForImageLoad(tw->w3doc))
			{
				TW_Reformat(tw, NULL);
				/* TODO: Go back to correct place in document */
			}
 			TW_UpdateTBar(tw);
			return STATE_DONE;
		}
	}
	XX_Assert((0), ("Function called with illegal state: %d", nState));
	return STATE_DONE;
}

static void x_HandleCurrentDocument(struct Mwin *tw, struct DestInfo *pDest, BOOL bNoImageCache)
{
	int ndx;

#if defined(FEATURE_IAPI) && defined(WIN32)
	tw->transID = 0;		/* special ID - Requested URL is already loaded in the window */
#endif

#if 0
	/* TODO: Make this case work */
	if (bNoImageCache)
	{
		w3doc->bIsShowPlaceholders = FALSE;
		Image_NukeImages(tw->w3doc, TRUE, /*fNukeDCache=*/TRUE);
		if (!g_bAbort)
			Image_LoadAllImages(tw, !gPrefs.bAutoLoadImages);
	}
#endif

	W3Doc_CheckAnchorVisitations(tw->w3doc, NULL);

/*	TW_InvalidateDocument( tw ); */

	if (pDest->szActualLocal)
	{
		ndx = TW_FindLocalAnchor(tw->w3doc, pDest->szActualLocal);
		if (ndx < 0)
			ndx = 0;
		TW_ScrollToElement(tw, ndx);
	}
	else
	{
		TW_ScrollToElement(tw, tw->w3doc->iFirstVisibleElement);
	}
}

static void RevertToPrevious(struct Mwin *tw, struct _www *w3doc)
{
	W3Doc_ConnectToWindow(w3doc, tw);
	W3Doc_CheckAnchorVisitations(w3doc, NULL);
	
	if (tw->win)
	{
		TW_InvalidateDocument(tw);
		TW_SetWindowName(tw);
		TW_Reformat(tw, NULL);
		TW_ScrollToElement(tw, w3doc->iFirstVisibleElement);
	}
}

static void x_HandleCacheHit(struct Mwin *tw, struct DestInfo *pDest, int doc_index, BOOL bNoImageCache, BOOL bRecord)
{
	struct _www *w3doc;
	int ndx;

	/*
	   Move the w3doc to the end of the cache list
	 */
	Hash_GetIndexedEntry(&tw->doc_cache, doc_index, NULL, NULL, (void **) &w3doc);
	Hash_DeleteIndexedEntry(&tw->doc_cache, doc_index);
	Hash_Add(&tw->doc_cache, pDest->szActualURL, NULL, (void *) w3doc);
	W3Doc_DisconnectFromWindow(tw->w3doc, tw);
	w3doc->bIsShowPlaceholders = FALSE;
	W3Doc_ConnectToWindow(w3doc, tw);
	W3Doc_CheckAnchorVisitations(w3doc, NULL);
	tw->bLoading = FALSE;

	if (bNoImageCache)
	{
		Image_NukeImages(tw->w3doc, TRUE, /*fNukeDCache=*/TRUE);
		FNukeBlobs(tw->w3doc, /*fNukeDCache=*/TRUE);
	}

	if (tw->win)
	{
		TW_SetWindowName(tw);
		TW_Reformat(tw, NULL);
	}

	if (pDest->szActualLocal)
	{
		ndx = TW_FindLocalAnchor(tw->w3doc, pDest->szActualLocal);
		if (ndx < 0)
			ndx = 0;
		TW_ScrollToElement(tw, ndx);
	}
	else
	{
		TW_ScrollToElement(tw, w3doc->iFirstVisibleElement);
	}

	if (bRecord)
	{
		TW_AddToHistory(tw, pDest->szActualURL);
		GHist_Add(pDest->szActualURL, tw->w3doc->title, time(NULL),/*fCreateShortcut=*/TRUE);
		UpdateHistoryMenus(tw);
	}

   /*	TW_InvalidateDocument(tw); */

	{
 		struct Params_GDOC_LoadImages *pparams;

 		pparams = GTR_MALLOC(sizeof(*pparams));
 		if (pparams)
 		{
 			pparams->tw = tw;
 			pparams->bLocalOnly = !gPrefs.bAutoLoadImages;
 			Async_StartThread(GDOC_LoadImages_Async, pparams, tw);
 		}
	}   	
}


// ClientPullTimerProc - timer proc for Client Pull operations.
// After we complete a download, we SetTimer on that Mwin,
// when the time has elapsed we get called to go to the new URL..
//
// hWnd : handle to our current window that is getting Pulled ...
// idEvent: TIMER_PULL, our timer id.
// ....
VOID CALLBACK ClientPullTimerProc(
    HWND  hWnd,	// handle of window for timer messages 
    UINT  uMsg,	// WM_TIMER message
    UINT  idEvent,	// timer identifier
    DWORD  dwTime 	// current system time
   )
{
	struct Mwin * tw = GetPrivateData(hWnd);

	// this is a one-shot timer, so lets kill it
	KillTimer(hWnd, idEvent );

	// make sure we don't party on with destroyed stuff
	if ( tw == NULL || tw->w3doc == NULL || tw->w3doc->pMeta == NULL )
		return;

	tw->w3doc->pMeta->uiTimer = 0;

	// if we have the URL lets go right to it..
	// we may not need to call CreateOrLoad.. perhaps we could call
	// lower in the call stack? perhaps a TW_ func?
	if ( tw->w3doc->pMeta->szURL ) 		
		TW_LoadDocument(tw, tw->w3doc->pMeta->szURL, TW_LD_FL_NO_DOC_CACHE, NULL, NULL);
	else
	{
		// reload by synthing a message WM_COMMAND message...
		// could this be done better?
		if ( tw->win )
		{
			XX_DMsg(DBG_MENU,
					("CC_Forward: forwarding message 0x%x to window 0x%x.\n",
					 RES_MENU_ITEM_RELOAD, tw->win));
			(void) SendMessage(tw->win, WM_COMMAND, (WPARAM) RES_MENU_ITEM_RELOAD, 0L);
		}
	}

}

PRIVATE_CODE void FreeDDEResultInfo(PDDERESULTINFO pdderi)
{
    ASSERT(IS_VALID_STRUCT_PTR(pdderi, CDDERESULTINFO));

    if (pdderi->pszURL)
    {
        FreeMemory(pdderi->pszURL);
        pdderi->pszURL = NULL;
    }

    FreeMemory(pdderi);
    pdderi = NULL;

    return;
}

/*
** IssueURLResult()
**
** 
**
** Arguments:
**
** Returns:
**
** Side Effects:  Frees pv as PDDERESULTINFO.
*/
PRIVATE_CODE void IssueURLResult(PVOID pv)
{
    PDDERESULTINFO pdderi;

    ASSERT(IS_VALID_STRUCT_PTR(pv, CDDERESULTINFO));

    pdderi = pv;

    if (pdderi->lTransID)
        DDE_Issue_Result(pdderi->lTransID, pdderi->lSerialID, pdderi->bResult);

    DDE_Issue_URLEcho(pdderi->lSerialID, pdderi->pszURL, pdderi->htaMIMEType);

    FreeDDEResultInfo(pdderi);
    pdderi = NULL;



    return;
}

PRIVATE_CODE BOOL IssueURLResult_Async(PMWIN pmwin, BOOL bURLResult)
{
    BOOL bResult = FALSE;
    PSTR pszURLCopy = NULL;
    PDDERESULTINFO pdderi = NULL;
    struct Params_mdft *pmdft = NULL;

    /* bURLResult may be any value. */
    ASSERT(IS_VALID_STRUCT_PTR(pmwin, CMWIN));

    if (! pmwin->w3doc ||
        ! pmwin->w3doc->szActualURL ||
        StringCopy(pmwin->w3doc->szActualURL, &pszURLCopy))
    {
        if (AllocateMemory(sizeof(*pdderi), &pdderi))
        {
            if (AllocateMemory(sizeof(*pmdft), &pmdft))
            {
                /* Fill in DDERESULTINFO. */

                pdderi->lTransID = pmwin->transID;
                pdderi->lSerialID = pmwin->serialID;
                pdderi->bResult = bURLResult;
                pdderi->pszURL = pszURLCopy;
                pdderi->htaMIMEType = pmwin->mimeType;
				

                /* Fill in Params_mdft. */

                ZeroMemory(pmdft, sizeof(*pmdft));

                pmdft->tw = pmwin;
                pmdft->fn = &IssueURLResult;
                pmdft->args = pdderi;
				pmdft->fDontDisable = TRUE;

                /* IssueURLResult() frees pdderi. */

                Async_DoCall(MDFT_RunModalDialog_Async, pmdft);

                bResult = TRUE;
            }
        }
    }

    /* Free allocated objects on failure. */

    if (! bResult)
    {
        if (pszURLCopy)
        {
            FreeMemory(pszURLCopy);
            pszURLCopy = NULL;
        }

        if (pdderi)
        {
            FreeMemory(pdderi);
            pdderi = NULL;
        }

        if (pmdft)
        {
            FreeMemory(pmdft);
            pmdft = NULL;
        }
    }

    return(bResult);
}


struct Params_HandleLoadDocument {
	/* To be filled in by caller */
	struct DestInfo *pDest;
	BOOL			bRecord;
	BOOL			bPost;
	BOOL			bNoDocCache;
	BOOL			bNoImageCache;
	char *			szPostData;		/* This is GTR_FREE'd by this function! */
	CONST char *	szReferer;

	/* Used internally by routine */
	HTRequest *			request;
	struct _www	*		prev_w3doc;
	int					status;
#ifdef FEATURE_IMG_THREADS
	struct Mwin			*twMaster;
#endif
	BOOL			fLoadFromDCacheOK;	//OK to load from dcache,
										//no need to check header response
										//for Last-Modified tag
};

#ifdef FEATURE_KEEPALIVE
static int cbDownLoads = 0;
static UINT uiKATimer = 0;

// KeepAliveTimerProc - timer proc for Keep Alive reaping operations.
// After we complete all downloads, we SetTimer, when the time has 
// elapsed we attempt to close keep alive sockets.  if we are now
// in the midst of another download, we let the last x_HandleLoadDocument_Async
// instance do it for us.
//
// ....
VOID CALLBACK KeepAliveTimerProc(
    HWND  hWnd,	// handle of window for timer messages 
    UINT  uMsg,	// WM_TIMER message
    UINT  idEvent,	// timer identifier
    DWORD  dwTime 	// current system time
   )
{

	// this is a one-shot timer, so lets kill it
	KillTimer(NULL, uiKATimer );
	// force all free sockets closed
	if (cbDownLoads == 0) Net_CloseUnusedKeepAlive(TRUE);
}
#endif


#define STATE_HLD_TRIEDLOAD	(STATE_OTHER)
#define STATE_HLD_GOTIMAGES	(STATE_OTHER+1)

/* Handle loading an (ostensibly) new document.  This could result in an HTTP redirection to a new
   document which is actually present in the cache, in which case we'll call x_DoLoadDocument()
   recursively. */
static int x_HandleLoadDocument_Async(struct Mwin *tw, int nState, void **ppInfo)
{
	struct Params_HandleLoadDocument *pParams;
	char buf[MAX_URL_STRING + 32 + 1];
	char *p;
	static BOOL success;

#ifdef FEATURE_IMG_THREADS
	struct Params_Image_LoadAll *pil = NULL;
#endif

	pParams = *ppInfo;

	switch (nState)
	{
		case STATE_INIT:
#ifdef FEATURE_KEEPALIVE
			cbDownLoads++;
#endif
			if (tw == NULL) goto done;
#ifdef USE_MEMMANAGE
			GTR_RestoreCushion();
#endif
#ifdef FEATURE_IMG_THREADS
			pParams->twMaster = NewMwin(GIMGMASTER);
			pil = GTR_MALLOC(sizeof(*pil));
			if (pParams->twMaster == NULL || pil == NULL)
			{
				if (pParams->twMaster) Plan_close(pParams->twMaster);
				if (pil) GTR_FREE(pil);
				ERR_SimpleError(tw, errLowMemory, RES_STRING_LOADDOC1);
				goto done;
			}
#endif
			GTR_formatmsg(RES_STRING_LOADDOC2,buf,sizeof(buf));
			// save a pointer where URL will be, to allow humanizing in place
			p = buf + strlen( buf );
 			GTR_strncat(buf, pParams->pDest->szActualURL, MAX_URL_STRING);
			make_URL_HumanReadable( p, NULL, FALSE );
			WAIT_Push(tw, waitPartialInteract, buf);  // was: waitNoInteract
			WAIT_SetStatusBarIcon( tw, SBI_FindingIcon );

			tw->bLoading = TRUE;

#ifdef FEATURE_IMG_THREADS 
			//  Launch Image_LoadAll_Async in the blocked state for TW_PARSEBLOCKED
			//  When LOADALL completes it clears TW_LOADALLDONE
			pil->bJustOne = FALSE;
			pil->tw = pParams->twMaster;
			pil->bLocalOnly = !gPrefs.bAutoLoadImages;
			pil->bNoImageCache = pParams->bNoImageCache ;
			TW_SETBLOCKED(pParams->twMaster,TW_PARSEBLOCKED);
			TW_SETFLAG(tw,TW_LOADALLACTIVE);
			pil->tw->twParent = tw;
			pil->parentThread = Async_GetCurrentThread();
			Async_BlockThread(Async_StartThread(Image_LoadAll_Async,pil,pParams->twMaster));
#endif
			/*
				we must check here because we overloaded the bPost field to contain
				information as to whether any data had been sent from a form
			*/
			if (IS_FLAG_SET(pParams->bPost, TW_LD_FL_POST))
			{
				pParams->request = tw->post_request;

				XX_Assert((pParams->szPostData),
						  ("x_HandleLoadDocument: bPost set when szPostData NULL."));

				pParams->request->szPostData = pParams->szPostData;

				XX_Assert((strcmp(HTAtom_name(pParams->request->content_type),
								  "application/x-www-form-urlencoded")==0),
						  ("x_handleLoadDocument: request content-type not as expected [%s].",
						   HTAtom_name(pParams->request->content_type)));
			}
			else
 			{
				pParams->request = tw->request;

 				/*
 					We need to initialize a few things here to make sure we're not
 					getting values left over from previous uses of this request struct.
 					TODO We should not be re-using these request structs.  We should
 					allocate a new one for each request.
 				*/
 				pParams->request->content_encoding = 0;          
 				pParams->request->content_length = 0;
 				pParams->request->content_type = 0;
 				pParams->request->content_language = 0;
 				pParams->request->callback = NULL;
 				pParams->request->iFlags = 0;
 				pParams->request->output_stream = NULL;
 				pParams->request->szLocalFileName = NULL;
#ifdef FEATURE_IAPI
 				pParams->request->savefile = NULL;
#endif
				pParams->request->dctLastModified.dwDCacheTime1 =
				pParams->request->dctLastModified.dwDCacheTime2 = 0;
			}

			/*
				we overloaded the bPost field
			*/
			if (IS_FLAG_SET(pParams->bPost, TW_LD_FL_SENDING_FROM_FORM)) 
				pParams->request->iFlags |= HTREQ_SENDING_FROM_FORM;

			if (IS_FLAG_SET(pParams->bPost, TW_LD_FL_REALLY_SENDING_FROM_FORM)) 
				pParams->request->iFlags |= HTREQ_REALLY_SENDING_FROM_FORM;				

			pParams->request->pMeta = NULL;
			pParams->request->destination = pParams->pDest;
			pParams->request->referer = pParams->szReferer;
			if ( tw->w3doc && tw->w3doc->pMeta && tw->w3doc->pMeta->bInherit )
			{
				// we've got a redirection, that needs to be born again
				// in a new life .. i mean a new w3doc.
				//
				// to get it over the great barrier to the new world
				// we slide it into a request struct..
				// it shouldn't get touched there ????
				pParams->request->pMeta = tw->w3doc->pMeta;
				// make sure it doesn't come back to haunt us after this one..
				tw->w3doc->pMeta->bInherit = FALSE;
				tw->w3doc->pMeta = NULL;
			}


 			pParams->prev_w3doc = tw->w3doc;
			pParams->request->fNotFromCache = pParams->bNoDocCache;

			// indicate to the loader that this is a page that
			// is being downloaded!
			pParams->request->iFlags |= HTREQ_HTML_PAGE_DOWNLOAD;

			if (pParams->bRecord)
			{
				pParams->request->iFlags |= HTREQ_RECORD;
			}
			else
			{
				pParams->request->iFlags &= (~HTREQ_RECORD);
			}
 			pParams->request->bReload = pParams->bNoDocCache;


#ifdef FEATURE_SPM
#ifdef DISABLED_BY_JIM
			tw->HACK_security_redirect = NULL;
#endif
#endif
			/* Call load routines asynchronously. */
			{
				struct Params_LoadAsync *pLoadParams;

				pLoadParams = GTR_MALLOC(sizeof(*pLoadParams));
				pLoadParams->request = pParams->request;
				pLoadParams->pStatus = &pParams->status;
				pLoadParams->fLoadFromDCacheOK = pParams->fLoadFromDCacheOK;
				Async_DoCall(HTLoadSpecial_Async, pLoadParams);
			}
			return STATE_HLD_TRIEDLOAD;

		case STATE_HLD_TRIEDLOAD:
 			if (pParams->request->szLocalFileName)
 			{
 				GTR_FREE(pParams->request->szLocalFileName);
 				pParams->request->szLocalFileName = NULL;
 			}

			pParams->request->referer = NULL;
			pParams->request->iFlags &= (~HTREQ_RECORD);

			/* We don't want to do a WAIT_Pop() here because that would bring us back to
			   the base state, resetting the globe position. */
			WAIT_Update(tw,	waitSameInteract,"");

			if (pParams->szPostData)
			{
				GTR_FREE(pParams->szPostData);
				pParams->szPostData = NULL;
			}

			if ( tw && tw->w3doc )
			{
				// hack for WellsFargo, they depend on a weird Netscape
				// feature where class 500 errors always reload even
				// when in memory cache

				if ( pParams->request->iFlags & HTREQ_NO_MEM_CACHE_ON_PAGE )
					tw->w3doc->flags |= W3DOC_FLAG_NO_MEM_CACHE_ON_PAGE;
			}

			if ( pParams->request->pMeta && tw->w3doc )
			{
				// watch out..
				// what if we already grabed a W3 for this 
				// doc.?
				//
				// OK snatch the Meta struc from the HTTP Request Header..
				// Assuming we found a "Refresh: " in the header.
				//
				if ( !tw->w3doc->pMeta || pParams->request->pMeta->bInherit )
				{	
					
					// if we hit a redirected, Refresh Tag we need to propagage
					// his idea upward.. an idea that no w3doc should be born
					// without the inalienable right to inherit its refresh tag.

					tw->w3doc->pMeta = pParams->request->pMeta;
					pParams->request->pMeta = NULL;
				}
			}

				
#ifdef FEATURE_SPM
#ifdef DISABLED_BY_JIM
			if (tw->HACK_security_redirect)
			{
				tw->bLoading = FALSE;
				if (pParams->status == HT_LOADED)
				{
					tw->w3doc->my_anchor = (HTParentAnchor *) tw->HACK_security_redirect;
					pParams->anc = (HTAnchor *) tw->w3doc->my_anchor;
					pParams->adult = (HTParentAnchor *) pParams->anc;
				}
			}
			else
#endif
#endif /* FEATURE_SPM */
			if (   pParams->status == HT_REDIRECTION_ON_FLY
				|| pParams->status == HT_REDIRECTION_DCACHE
				|| pParams->status == HT_REDIRECTION_DCACHE_TIMEOUT)
			{
				BOOL fLoadFromDCacheOK = FALSE;

#ifdef FEATURE_IMG_THREADS
				Async_TerminateByWindow(pParams->twMaster);
#endif
				if (pParams->status == HT_REDIRECTION_ON_FLY)
				{
					/* If we got here, our destination has already been updated to
					   reflect the redirection. */

					/* This isn't useful in the history, but it allows us to properly
					   change the link color on redirected links */
					GHist_Add(pParams->pDest->szRequestedURL, "Document moved", time(NULL),/*fCreateShortcut=*/TRUE);
				}
				else
				{
					fLoadFromDCacheOK = TRUE;
#ifdef XX_DEBUG
					XX_Assert(	 pParams->status == HT_REDIRECTION_DCACHE
							  || pParams->status == HT_REDIRECTION_DCACHE_TIMEOUT, (""));
#ifdef NEVER
					if (pParams->status ==  HT_REDIRECTION_DCACHE_TIMEOUT)
					{
						PSTR psz;
						XX_Assert(psz = PszGetDCachePath(pParams->pDest->szActualURL, NULL, NULL), (""));
						if (psz)
							GTR_FREE(psz);
					}
#endif	/* NEVER */
#endif
				}


				/* Call recursively so that we can check the image cache, etc.  Note that we
				   never set bPost after a redirection. */
				/* TODO: Should we reset bNoDocCache if bPost is true, so that a form request
				   can lead us to a cached document? */
				/* NOTE: since we force bPost false, we don't send szPostData to the call. */

				x_DoLoadDocument(tw, pParams->pDest, pParams->bRecord, FALSE,
								 pParams->bNoDocCache, pParams->bNoImageCache,
								 FALSE, NULL, pParams->szReferer,
								 fLoadFromDCacheOK);

				/* tw->bLoading won't get reset if we wind up with a cache hit. */

				tw->bLoading = FALSE;
#ifdef FEATURE_IMG_THREADS
				Image_UnblockMaster(tw);
#endif
				WAIT_Pop(tw);
				/* Note that this is the one instance in this function where
				   we return without destroying the destination.  This is
				   because we passed it to x_DoLoadDocument, so that function
				   will free it. */
				success = TRUE;
				goto finish_up;
			}
			else if (pParams->status == HT_LOADED)
			{
				if (tw->w3doc && (pParams->prev_w3doc != tw->w3doc))
				{

#ifndef FEATURE_IMG_THREADS
					if (pParams->bNoImageCache)
					{
						Image_NukeImages(tw->w3doc, FALSE, /*fNukeDCache=*/TRUE);
						FNukeBlobs(tw->w3doc, /*fNukeDCache=*/TRUE);
					}
#endif
					FORM_ShowAllChildWindows(tw->w3doc, SW_SHOW);
				}

				tw->bLoading = FALSE;

				if (tw->win && tw->w3doc && (pParams->prev_w3doc != tw->w3doc))
				{
					TW_SetWindowName(tw);
#ifdef FEATURE_INTL
					if (IsFECodePage(GETMIMECP(tw->w3doc)))
                                            TW_ForceReformat(tw);
                                        else
#endif
					TW_Reformat(tw, NULL);

#ifdef FEATURE_TESTHOOK
        TestDumpAnchors(tw->w3doc);
#endif

#ifdef FEATURE_IMG_THREADS
					Image_UnblockMaster(tw);					
					if (TW_GETFLAG(tw,TW_LOADALLACTIVE))
					{
    					Async_BlockThread(Async_GetCurrentThread());
						TW_SETBLOCKED(tw,TW_LOADALLBLOCKED);
					}
#else
					/* Load in images for this document */
					{
						struct Params_Image_LoadAll *pil;
						pil = GTR_MALLOC(sizeof(*pil));
						pil->tw = tw;
						pil->bLocalOnly = !gPrefs.bAutoLoadImages;
						Async_DoCall(Image_LoadAll_Async, pil);
					}
#endif

					return STATE_HLD_GOTIMAGES;
				}
				else
				{
					/* Even though the load succeeded we didn't get a new document.
					   That means we're done. */
					WAIT_Pop(tw);
#ifdef FEATURE_IMG_THREADS
					Async_TerminateByWindow(pParams->twMaster);
#endif
					Dest_DestroyDest(pParams->pDest);
					success = FALSE;
					
					goto finish_up;
				}
			}
			else
			{
				/*
					!bOK : the load failed
				*/
#if defined(FEATURE_IAPI) && defined(WIN32)
				/* If the request is from an IAPI application, do not show the error box */

				if (tw->transID == 0)
#endif
				{
					TBar_LoadFailed( tw, pParams->pDest->szRequestedURL );
 					/*
 						We deliberately do not display the errDocLoadFailed if the interlude dialog
 						was cancelled.
 					*/
 					if (!(pParams->request->iFlags & HTREQ_USERCANCEL))
 					{
 						ERR_ReportError(tw, errDocLoadFailed, pParams->pDest->szRequestedURL, NULL);
 					}
 				}

				tw->bLoading = FALSE;
#ifdef FEATURE_IMG_THREADS
				Async_TerminateByWindow(pParams->twMaster);
#endif
				TW_InvalidateDocument(tw);

				WAIT_Pop(tw);
				Dest_DestroyDest(pParams->pDest);
				success = FALSE;

				#ifdef HTTPS_ACCESS_TYPE
					/*
					  We cancelled this load.  So reinstall flags from previous page.  We do this
					  since we may not have completed the last load.
					*/
					tw->dwSslPageFlagsWorking &= SSL_PAGE_LAST_SECURE_PROTOCOL;
					if (tw->w3doc && IsURLSecure(tw->w3doc->szActualURL)){
						tw->dwSslPageFlagsWorking |= SSL_PAGE_CURRENT_SECURE_PROTOCOL;
					}
				#endif


				goto finish_up;
			}
			XX_Assert((0), ("Fell through case improperly!"));

		case STATE_HLD_GOTIMAGES:
			if (W3Doc_CheckForImageLoad(tw->w3doc))
			{
				TW_Reformat(tw, NULL);
				/* TODO: Go back to correct place in document */
			}
			WAIT_Pop(tw);
			Dest_DestroyDest(pParams->pDest);
			success = TRUE;
					
			goto finish_up;

		case STATE_ABORT:
			if (tw == NULL) goto done;
 			if (tw->w3doc)
 			{
 				if (W3Doc_CheckForImageLoad(tw->w3doc))
 				{
 					TW_Reformat(tw, NULL);
 					/* TODO: Go back to correct place in document ? */
 				}
 				FORM_ShowAllChildWindows(tw->w3doc, SW_SHOW);
 			}
			WAIT_Pop(tw);
#ifdef FEATURE_IMG_THREADS 
 			TW_CLEARBLOCKED(tw,TW_LOADALLBLOCKED);
			Async_TerminateByWindow(pParams->twMaster);
#endif
			if (pParams->szPostData)
			{
				GTR_FREE(pParams->szPostData);
			}
			Dest_DestroyDest(pParams->pDest);
			tw->bLoading = FALSE;
			success = TRUE;
			goto finish_up;

finish_up:

#ifdef FEATURE_IMG_THREADS
			TW_CLEARFLAG(tw,TW_LOADALLACTIVE);
#endif
			if ( (success) && nState != STATE_ABORT && !TW_GETFLAG(tw,TW_LOADALLBLOCKED))
			{
				// check to see if we have a meta - refresh tag
				// BUGBUG if we add meta tags in the future
				// that have nothing to do with refresh this could
				// be a problem... for now we only have one type.

				if (tw->w3doc && tw->w3doc->pMeta && !tw->w3doc->pMeta->bInherit)
				{
					if ( tw->w3doc->pMeta->uiTimer )					
						KillTimer(tw->win, tw->w3doc->pMeta->uiTimer);

					tw->w3doc->pMeta->uiTimer = 
						 SetTimer(tw->win, TIMER_PULL, tw->w3doc->pMeta->iDelay*1000,
						 (TIMERPROC) ClientPullTimerProc );
				}
			}

			
			
			if ( success )
			{
				if ( nState != STATE_ABORT )	// was this load a real success?
				{
					TBar_LoadSucceeded( tw );
				}
				SelectFirstControl(tw);
			}
#ifdef FEATURE_TESTHOOK
                        TestSignalLoadDone(success);
#endif

			
#ifdef HTTPS_ACCESS_TYPE
			/*main page is done loading, can play with dwSslPageFlagsWorking here*/
#endif

			TW_UpdateTBar(tw);
#if defined(FEATURE_IAPI) && defined(WIN32)
            /* Ignore return value. */
            IssueURLResult_Async(tw, success);
#endif

			if ((!success) || (tw->w3doc && tw->w3doc->bIsJustMessage))
			{
				if (tw->wintype == GHTML && (tw->w3doc == NULL || tw->w3doc->bIsJustMessage))
				{
					if (pParams->prev_w3doc)
					{
						RevertToPrevious(tw, pParams->prev_w3doc);
					}
					else if (!TW_ExistsModalChild(tw))
					{
						tw->bKillMe = TRUE;
						(void) PostMessage(wg.hWndHidden, WM_DO_KILLME, 0, 0L);
					}
					else if ( tw->w3doc != NULL )
					{
						tw->bKillMe = TRUE;
					}
				}
			} else {
				// we're now finished downloading page.  If there are any
				// fetches to be done, send a message to our window saying
				// we should do the fetch now
				if (tw->iIndexForNextFetch >= 0) {
					SendMessage(tw->hWndFrame,WM_DO_FETCH,0,0);
				}

			}
			goto done;
	}
	XX_Assert((0), ("Function called with illegal state: %d", nState));
done:
#ifdef FEATURE_KEEPALIVE
	if (--cbDownLoads == 0) 
	{
		if (uiKATimer) KillTimer(NULL, uiKATimer);
		uiKATimer = SetTimer(NULL, 0, KEEPALIVE_TIME, (TIMERPROC) KeepAliveTimerProc );
	//	If SetTimer failed, force all free sockets closed
		Net_CloseUnusedKeepAlive(uiKATimer == 0);
	}
#endif
	return STATE_DONE;
}

static int nNumUnsuccesfulTimeouts=0;
#define MAX_NUM_UNSUCCESFUL_TIMEOUTS 20

VOID CALLBACK AutoPlaceHolderTimerHandler(
    HWND hWnd,
    UINT uMsg,
    UINT idEvent,
    DWORD dwTime)
{
	struct Mwin *tw;
	struct _www *w3doc;
	RECT rUpdate;

    tw = GetPrivateData(hWnd);

	if((!tw )|| (tw !=  (struct Mwin *)(idEvent))){
		ASSERT(0);
		goto exitPoint;		// This is an error !
	}

    w3doc = tw->w3doc;
   
   	// Has the HTML text been downloaded -- If not leave and wait for the timer to go off again.
   	if((tw->bLoading) || (w3doc == NULL)){
		nNumUnsuccesfulTimeouts++;
		if(nNumUnsuccesfulTimeouts > MAX_NUM_UNSUCCESFUL_TIMEOUTS)
			goto exitPoint;
		//Otherwise -- Just let the timer go off again 
		return;
	}
    

	// Are there any images left ?
	if (!(w3doc->frame.pLineInfo && w3doc->frame.nLineCount && w3doc->frame.nLastFormattedLine >= 0 &&w3doc->frame.nLastLineButForImg >= 0))
		goto exitPoint;

	if(tw->w3doc->bIsShowPlaceholders) 
		goto exitPoint;
    
	tw->w3doc->bIsShowPlaceholders = TRUE;
	if(w3doc->frame.nLineCount > w3doc->frame.nLastLineButForImg){	    // Paranoia
	    rUpdate.top = w3doc->frame.pLineInfo[w3doc->frame.nLastLineButForImg].nYStart - tw->offt;
	}else{
	    rUpdate.top = 0;
	}
    rUpdate.bottom = w3doc->frame.rWindow.bottom;
    rUpdate.left = w3doc->frame.rWindow.left;
    rUpdate.right = w3doc->frame.rWindow.right;
    InvalidateRect(tw->win, &rUpdate, TRUE);
	(void) UpdateWindow(tw->win);


exitPoint:
	nNumUnsuccesfulTimeouts = 0;
	KillTimer(hWnd, idEvent);
	return;
}

static void x_DoLoadDocument(struct Mwin *tw, struct DestInfo *pDest,
							 BOOL bRecord, BOOL bPost, BOOL bNoDocCache,
							 BOOL bNoImageCache, BOOL bAuthFailCacheOK,
							 CONST char * szPostData, CONST char *szReferer,
							BOOL fLoadFromDCacheOK)
 {
	int ndx;
	struct _www *w3doc;
	BOOL bDestroyDest;
 	char *szMyReferer;
	

	bDestroyDest = TRUE;

 	/*
 		We make our own copy of the referer, since it could be freed indirectly
 		by the cache removal below.
 	*/
 	if (szReferer)
 	{
 		szMyReferer = GTR_strdup(szReferer);
 	}
 	else
 	{
		szMyReferer = NULL;
 	}

	w3doc = NULL;
	ndx = Hash_Find(&tw->doc_cache, pDest->szActualURL, NULL, (void **)&w3doc);
	if (   ndx >= 0
		&& (   bNoDocCache
			|| (w3doc->flags & W3DOC_FLAG_NO_MEM_CACHE_ON_PAGE)
			|| !w3doc->bIsComplete
			|| (w3doc->bAuthFailCache && !bAuthFailCacheOK)
			|| FFreshnessCheckNeeded(pDest->szActualURL)
			|| FExpired(w3doc->dctExpires)
			|| LocalPageLastWriteTimeChanged(tw, w3doc, FALSE) 
		   )
	   )
	{
		/* Note: the only time (for now) that FFreshnessCheckNeeded could
		 * return true is
		 * a) user started out with UpdateFrequency=NEVER
		 * b) Loaded a doc from the dcache and moved to another page
		 * c) Changed UpdateFrequency to ONCE_PER_SESSION
		 * d) Navigated back to this doc.
		 */
		/* Delete this item from the cache */
		if (tw->w3doc == w3doc)
			W3Doc_DisconnectFromWindow(w3doc, tw);

		if (bNoImageCache)
		{
			Image_NukeImages(w3doc, TRUE, /*fNukeDCache=*/TRUE);
			FNukeBlobs(w3doc, /*fNukeDCache=*/TRUE);
		}

		W3Doc_FreeContents(tw, w3doc);
		GTR_FREE(w3doc);

 		/*
 			We need to re-do the search here to make sure that the document is still
 			in the cache.  It is possible that it was removed in the call to
 			W3Doc_DisconnectFromWindow().
 		*/
 		ndx = Hash_Find(&tw->doc_cache, pDest->szActualURL, NULL, (void **)&w3doc);
 		if (ndx >= 0)
 		{
 			Hash_DeleteIndexedEntry(&tw->doc_cache, ndx);
 		}
		w3doc = NULL;
	}

	if (bNoDocCache && gPrefs.bEnableDiskCache)
		FlushDCacheEntry(pDest->szActualURL);

	/* See if this is the currently loaded document */
	if (w3doc)
	{
		if (tw->w3doc == w3doc)
		{
			x_HandleCurrentDocument(tw, pDest, bNoImageCache);
		}
		else
		{
			x_HandleCacheHit(tw, pDest, ndx, bNoImageCache, bRecord);
		}
		if (szPostData)
		{
			GTR_FREE((char *) szPostData);
		}
		SelectFirstControl(tw);
	}
#ifdef FEATURE_IMAGE_VIEWER
#ifndef FEATURE_IMG_INLINE
	else if (!bNoDocCache && (Viewer_ShowCachedFile(pDest->szActualURL)))
	{
		/* We used the existing copy on disk */
		if (szPostData)
		{
			GTR_FREE((char *) szPostData);
		}
	}
#endif
#endif
#ifdef FEATURE_SOUND_PLAYER
	else if (!bNoDocCache && (SoundPlayer_ShowCachedFile(pDest->szActualURL)))
	{
		/* We used the existing copy on disk */
		if (szPostData)
		{
			GTR_FREE((char *) szPostData);
		}
	}
#endif
	else
	{
		/*
		** This is the situation where we actually have to load a document, either because
		** it's not in the cache or because we are forcing a reload.
		*/
		struct Params_HandleLoadDocument *pHLDParams;

		pHLDParams = GTR_MALLOC(sizeof(*pHLDParams));
		pHLDParams->pDest = pDest;
		pHLDParams->bRecord = bRecord;
		/*the bPost field has been overloaded, no longer a bool*/
		pHLDParams->bPost = bPost;
		pHLDParams->bNoDocCache = bNoDocCache;
		pHLDParams->bNoImageCache = bNoImageCache;
		pHLDParams->szPostData = (char *) szPostData;
 		pHLDParams->szReferer = szMyReferer;
 		pHLDParams->fLoadFromDCacheOK = fLoadFromDCacheOK;
 		ASSERT(tw != 0);

		// A way of disabling the timer by issuing a .reg file as a patch
		if((tw) && (gPrefs.nPlaceHolderTimeOut < PLACEHOLDER_TIMEOUT_MAXIMUM)){
			nNumUnsuccesfulTimeouts = 0;
			SetTimer(tw->win, (UINT)(tw), gPrefs.nPlaceHolderTimeOut, AutoPlaceHolderTimerHandler);
		}

		Async_StartThread(x_HandleLoadDocument_Async, pHLDParams, tw);

		/* The load function will destroy the destination for us when it
		   completes. */
		bDestroyDest = FALSE;
	}

	if ( bDestroyDest ) {
		Dest_DestroyDest( pDest );
		TBar_LoadSucceeded( tw );

		// check to see if we have a meta - refresh tag
		// BUGBUG if we add meta tags in the future
		// that have nothing to do with refresh this could
		// be a problem... for now we only have one type.

		if (tw->w3doc && tw->w3doc->pMeta && !tw->w3doc->pMeta->bInherit)
		{
			if ( tw->w3doc->pMeta->uiTimer )
				KillTimer(tw->win, tw->w3doc->pMeta->uiTimer);

			tw->w3doc->pMeta->uiTimer = 
				 SetTimer(tw->win, TIMER_PULL, tw->w3doc->pMeta->iDelay*1000,
				 (TIMERPROC) ClientPullTimerProc );
		}
	}
}



/* This function will free szPostData when done */
int TW_LoadDocument(PMWIN tw, PCSTR url, DWORD dwFlags, PSTR szPostData,
                    PCSTR szReferer)
{
	char buf[MAX_URL_STRING + 32 + 1];
	struct DestInfo *pDest;


	bTBar_URLComboProtected = FALSE;

    ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_TW_LD_FLAGS));

	if (!url || !*url)
	{
		ERR_ReportError(tw, errNoURL, NULL, NULL);
		if (szPostData)
		{
			GTR_FREE(szPostData);
		}		
 		TW_UpdateTBar(tw);
		return -1;
	}
 	XX_Assert((strlen(url) <= MAX_URL_STRING), ("TW_LoadDocument received a URL longer than %d characters.  Actual length was %d\n", MAX_URL_STRING, strlen(url)));

	/* If this is a relative anchor, make a full URL */
	if (*url == '#')
	{
		if (!tw->w3doc)
		{
			/* Shouldn't happen */
			ERR_ReportError(tw, errNoURL, NULL, NULL);
			if (szPostData)
			{
				GTR_FREE(szPostData);
			}
			return -1;
		}

 		GTR_strncpy(buf, tw->w3doc->szActualURL, MAX_URL_STRING);
        strcat(buf, url);
	}
	else  // Put  URL in cannonical form - ie, lower case access and hostname
 	{
		char *pURL = HTParse(url, "", PARSE_ALL);

		if (!pURL) return -1;
 		GTR_strncpy(buf, pURL, MAX_URL_STRING);
		GTR_FREE(pURL);
 	}
	
#ifdef HTTPS_ACCESS_TYPE
	/*base warnings for this page*/
	tw->dwSslPageFlagsWorking &= SSL_PAGE_LAST_SECURE_PROTOCOL;
	if (buf && IsURLSecure(buf)){
		tw->dwSslPageFlagsWorking |= SSL_PAGE_CURRENT_SECURE_PROTOCOL;
	}
	if (NULL == tw->w3doc)
	{
		BOOL at_home;

		/*first page, and subsequent ones of same type should never get warnings*/
		at_home = strcmp( gPrefs.szHomeURL, buf ) == 0;
		if (at_home) tw->dwSslPageFlagsWorking |= SSL_PAGE_LAST_SPECIAL_PAGE;
	}

	tw->nCertWorking = 0;
	tw->pCertWorking = NULL;
#endif

	if ((!tw->bDDECandidate) && tw->bSilent && Async_GetThreadForWindow(tw) != 0)
	{
		if (resourceMessageBox(tw->hWndFrame, RES_STRING_SILENT, RES_STRING_SILENT_TITLE, MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION) != IDYES)
		{
			if (resourceMessageBox(tw->hWndFrame, RES_STRING_OPENNEW, RES_STRING_SILENT_TITLE, MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION) != IDYES)
			{
				return -1;
			}
			else
			{
			    DWORD dwNWDocFlags = 0;
				int content_length_hint;

				content_length_hint = (tw->request ? tw->request->content_length_hint : 0);
		        if (IS_FLAG_SET(dwFlags, TW_LD_FL_NO_DOC_CACHE))
		            SET_FLAG(dwNWDocFlags, GTR_NW_FL_NO_DOC_CACHE);

		        if (IS_FLAG_SET(dwFlags, TW_LD_FL_NO_IMAGE_CACHE))
		            SET_FLAG(dwNWDocFlags, GTR_NW_FL_NO_IMAGE_CACHE);
				return GTR_NewWindow((PSTR)url, szReferer, content_length_hint, 0, dwNWDocFlags, szPostData, NULL);  
			}
		}
	}

	/* Terminate any other threads currently running on this window. */
	Async_TerminateByWindow(tw);

	tw->bSilent = FALSE;
	tw->bDDECandidate = FALSE;
	tw->bKillMe = FALSE;

	/* If the URL is of the form "http://system", append a slash at the end */
	x_EnforceHostSlash(buf);

	pDest = Dest_CreateDest(buf);
	if (pDest)
#ifdef FEATURE_INTL 
// Before load document, we initialize status of FECHRCNV.DLL.
// BUGBUG:I have to revisit this to make sure every case is covered here.
// _BUGBUG Perf: should load fechrcnv.dll on demand. (JCordell)
    {


            if ((tw->request != NULL && tw->request->iMimeCharSet != -1 && aMimeCharSet[tw->request->iMimeCharSet].iChrCnv)
            || (tw != NULL && aMimeCharSet[tw->iMimeCharSet].iChrCnv))
            FCC_Init();
#endif
		x_DoLoadDocument(tw, pDest, IS_FLAG_SET(dwFlags, TW_LD_FL_RECORD),
						/*we overload the bPost field so we can get to the 
						 TW_LD_FL_POST and TW_LD_FL_SENDING_FROM_FORM info
						 down the road
						 */
                         dwFlags&(TW_LD_FL_SENDING_FROM_FORM|TW_LD_FL_POST|TW_LD_FL_REALLY_SENDING_FROM_FORM),//IS_FLAG_SET(dwFlags, TW_LD_FL_POST),
                         IS_FLAG_SET(dwFlags, TW_LD_FL_NO_DOC_CACHE),
                         IS_FLAG_SET(dwFlags, TW_LD_FL_NO_IMAGE_CACHE),
                         IS_FLAG_SET(dwFlags, TW_LD_FL_AUTH_FAIL_CACHE_OK),
                         szPostData, szReferer, /*fLoadFromDCacheOK=*/FALSE);
#ifdef FEATURE_INTL
	}
#endif
	else
	{
		if (szPostData)
			GTR_FREE(szPostData);
	}
	return 0;
}

struct Params_Download {
	char *url;				/* Freed by function */
	char *szReferer;		/* Freed by function */
	HTFormat output_format;	// optional output format to force

	/* Used internally */
	HTRequest *request;
	struct DestInfo *pDest;
	int status;
};
static int x_DownLoad_Async(struct Mwin *tw, int nState, void **ppInfo)
{
	struct Params_Download *pParams;
	char buf[MAX_URL_STRING + 32 + 1];
	struct Params_LoadAsync *pla;
	static BOOL success;

	pParams = *ppInfo;

	switch (nState)
	{
		case STATE_INIT:
			if (tw == NULL) return STATE_DONE;
			pParams->pDest = Dest_CreateDest(pParams->url);
			if (!pParams->pDest)
			{
				GTR_FREE(pParams->url);
				if (pParams->szReferer)
					GTR_FREE(pParams->szReferer);
				success = FALSE;
				goto finish_up;
			}

			/* Set up the request structure */
			pParams->request = HTRequest_new();
			HTFormatInit(pParams->request->conversions);
			// if caller specified an output format to force, use it,
			// otherwise set to WWW_UNKNOWN
			if (pParams->output_format)
				pParams->request->output_format = pParams->output_format;
			else
				pParams->request->output_format = WWW_UNKNOWN;
			pParams->request->referer = pParams->szReferer;
			pParams->request->destination = pParams->pDest;
			pParams->request->iFlags |= HTREQ_BINARY;

			GTR_formatmsg(RES_STRING_LOADDOC3,buf,sizeof(buf));
 			GTR_strncat(buf, pParams->pDest->szActualURL, MAX_URL_STRING);
			WAIT_Push(tw, waitNoInteract, buf);

			pla = GTR_MALLOC(sizeof(*pla));
			pla->request = pParams->request;
			pla->pStatus = &pParams->status;
			pla->fLoadFromDCacheOK = FALSE;
			Async_DoCall(HTLoadDocument_Async, pla);
			return STATE_OTHER;

		case STATE_OTHER:
			WAIT_Pop(tw);
			if (!pParams->status && !(pParams->request->iFlags & HTREQ_USERCANCEL))
			{
				TBar_LoadFailed( tw, pParams->pDest->szRequestedURL );
				ERR_ReportError(tw, errDocLoadFailed, pParams->url, NULL);
			}
			Dest_DestroyDest(pParams->pDest);
			HTRequest_delete(pParams->request);
			GTR_FREE(pParams->url);
			if (pParams->szReferer)
			{
				GTR_FREE(pParams->szReferer);
			}
			success = TRUE;
			goto finish_up;

		case STATE_ABORT:
			if (tw == NULL) return STATE_DONE;
			WAIT_Pop(tw);
			Dest_DestroyDest(pParams->pDest);
			HTRequest_delete(pParams->request);
			GTR_FREE(pParams->url);
			if (pParams->szReferer)
			{
				GTR_FREE(pParams->szReferer);
			}
			success = TRUE;
			goto finish_up;

finish_up:
			TW_UpdateTBar(tw);
#if defined(FEATURE_IAPI) && defined(WIN32)
            /* Ignore return value. */
            IssueURLResult_Async(tw, success);
#endif
			return STATE_DONE;
	}
	XX_Assert((0), ("Function called with illegal state: %d", nState));
	return STATE_DONE;
}

void GTR_DownLoad(struct Mwin *tw, char *url, CONST char *szReferer,
	HTFormat output_format)
{
	char buf[MAX_URL_STRING + 32 + 1];
	struct Params_Download *pdl;

	if (!url || !*url)
	{
		ERR_ReportError(tw, errNoURL, NULL, NULL);
		TW_UpdateTBar(tw);
		return;
	}

	tw->bSilent = TRUE;
	tw->bDDECandidate = FALSE;

 	// Put  URL in cannonical form - ie, lower case access and hostname
 	{
		char *pURL = HTParse(url, "", PARSE_ALL);

		if (!pURL) return;
 		GTR_strncpy(buf, pURL, MAX_URL_STRING);
		GTR_FREE(pURL);
 	}
	/* If the URL is of the form "http://system", append a slash at the end */
	x_EnforceHostSlash(buf);

	pdl = GTR_MALLOC(sizeof(*pdl));

	/* Make copies of the URL and referer that we know won't be freed */
	pdl->url = GTR_strdup(buf);
	if (szReferer)
	{
		pdl->szReferer = GTR_strdup(szReferer);
	}
	else
	{
		pdl->szReferer = NULL;
	}

	pdl->output_format = output_format;

	Async_StartThread(x_DownLoad_Async, pdl, tw);
}