/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include "stdafx.h"

#include "helper.h"
#include "il_strm.h"
#include "display.h"
#include "prefapi.h" //CRN_MIME
#include "oleregis.h"

//  List of all helpers in our helper app struct.
//  Static must be declared here.
CPtrList CHelperApp::m_cplHelpers;

//
// Parse a list of extensions for file types
//
// Extensions will come in as a list .sg,.earh,.rts .ear
//
// Parse this list, white space is used to delimit between entries.  The
//  number of entries is returned in num_exts and the individual extensions
//  are returned in list
//
// I think I've fixed it so that we are no longer dropping extensions 
//  for .mp2 files.  chouck 29-Dec-94
//


void fe_ParseExtList(const char * ext_string, int *num_exts, char **list, int maxExts) 
{
	char * start, *cur, *newext;
	int iLen, idx;
	BOOL bInExt = FALSE;
	char * copy = strdup(ext_string);  // make copy to muck with
	int iExtCnt = 0;
	
	start = cur = copy;
	iLen = strlen(copy);

	// traverse string	
	for (idx =0; idx < iLen && iExtCnt < maxExts; idx++) {
		if (bInExt) {
			// We are currently reading an extension
			// If we have a character add it to the current extension

			if (!isalnum(*cur))	{

				// Found a delimiter character, end the extension

				bInExt = FALSE;
				*cur = 0; // mark end
				list[iExtCnt] = strdup(newext);
				iExtCnt++;				

			}

		} else {

			// We are not currently in an extension, search for next alpha numeric character

			if (isalnum(*cur)) {
				// found one! -- mark start of new extenison -- set flag
				newext = cur;
				bInExt = TRUE;

			}

		}

		cur++;
	}
	// save last one
	if (bInExt) {
		list[iExtCnt] = strdup(newext);
		iExtCnt++;				
	}
	*num_exts = iExtCnt;
	
	//	Be sure to free your copy to muck with
	XP_FREE(copy);
}

// Returns TRUE if lpszExt is in the list of comma delimited extensions
// (this is the Netscape format used to write the suffixes associated a
// particular MIME type). lpszExt should not have a lead '.'
static BOOL
ExtensionIsInList(LPCSTR lpszExtList, LPCSTR lpszExt)
{
	LPSTR	extlist[50];
	int 	nExtensions = 0;
	BOOL	bResult = FALSE;
	
    ASSERT(lpszExt && (*lpszExt != '.'));
	fe_ParseExtList(lpszExtList, &nExtensions, extlist, 50);

	// Look for our extension
	for (int i = 0; i < nExtensions; i++) {
		if (strcmpi(extlist[i], lpszExt) == 0) {
			bResult = TRUE;
			break;
		}
	}

	// Free the extensions
	for (i = 0; i < nExtensions; i++)
		XP_FREE(extlist[i]);

	return bResult;
}

// Returns TRUE if lpszExt is in the list of extensions for this
// type. lpszExt should not have a lead '.' (none of the extensions
// in pcdata have a leading '.')
static BOOL
ExtensionIsInList(NET_cdataStruct *pcdata, LPCSTR lpszExt)
{
    ASSERT(lpszExt && (*lpszExt != '.'));
	for (int i = 0; i < pcdata->num_exts; i++)	{
		if (stricmp(lpszExt, pcdata->exts[i]) == 0)	{
			return TRUE;
		}
	}

	return FALSE;
}

// Creates a front-end data structure (CHelperApp object) if there isn't already one
// Looks in the Viewers section to see how the MIME type should be configured
void fe_AddTypeToList(NET_cdataStruct *cd_item) 
{
    CHelperApp *app = NULL;

    if(cd_item->ci.fe_data) {
        app = (CHelperApp *)cd_item->ci.fe_data;
    }
    else    {
		// Create a front-end data structure
        app = new CHelperApp();
    }
	
	app->bChanged = FALSE;
	app->bNewType = FALSE;
	app->bChangedExts = FALSE;
	
	CString csCmd = theApp.GetProfileString("Viewers", cd_item->ci.type);

	// Handle internally by default
	if (csCmd.IsEmpty()) {
		if ( (!strcmp(cd_item->ci.type, TEXT_HTML))  || 
		     (!strcmp(cd_item->ci.type, TEXT_PLAIN)) ||
			 (!strcmp(cd_item->ci.type, IMAGE_GIF))  || 
			 (!strcmp(cd_item->ci.type, IMAGE_XBM))  || 
			 (!strcmp(cd_item->ci.type, IMAGE_JPG)) ||
			// (!strcmp(cd_item->ci.type, APPLICATION_BINHEX)) ||
			 (!strcmp(cd_item->ci.type, "application/x-ns-proxy-autoconfig"))) {
			app->how_handle = HANDLE_VIA_NETSCAPE;
		}

        if(!stricmp(cd_item->ci.type, APPLICATION_OCTET_STREAM)) {
                app->how_handle = HANDLE_SAVE;
        }
	}
	else {
		int iPercent = csCmd.ReverseFind('%');  // strip %ls from config
		if (iPercent > 0)
			csCmd = csCmd.Left(iPercent-1);

		if (csCmd == MIME_SAVE) 
			app->how_handle = HANDLE_SAVE;
		else if (csCmd == MIME_INTERNALLY)
			app->how_handle = HANDLE_VIA_NETSCAPE;
		else if (csCmd == MIME_PROMPT)
			app->how_handle = HANDLE_UNKNOWN;	
    else if (csCmd == MIME_OLE)
			app->how_handle = HANDLE_BY_OLE;
    else if (csCmd == MIME_SHELLEXECUTE)
			app->how_handle = HANDLE_SHELLEXECUTE;
//~~~
    else if (csCmd == MIME_PLUGIN)
			app->how_handle = HANDLE_VIA_PLUGIN;
    else if (csCmd == MIME_PLUGINAPPLET)
			app->how_handle = HANDLE_VIA_PLUGINAPPLET;
		else {
			char	szFile[_MAX_FNAME];
			
			// This is the default case.
			app->csCmd = csCmd;
			app->how_handle = HANDLE_EXTERNAL;

			// If there's no description then the helper app UI won't display the item
			// Use the base filename without extension
			szFile[0] = '\0';
			_splitpath((LPCSTR)csCmd, NULL, NULL, szFile, NULL);
			if (szFile[0] == '\0') {
				// Just use the MIME type for the description
				cd_item->ci.desc = XP_STRDUP(cd_item->ci.type);

			} else {
                char *pStringFile = szLoadString(IDS_STRING_FILE);
                int iLen = lstrlen(szFile) + lstrlen(pStringFile) + 2;

				cd_item->ci.desc = (char *)XP_ALLOC(iLen);
                if(cd_item->ci.desc)    {
    				szFile[0] = toupper(szFile[0]);
                    lstrcpy(cd_item->ci.desc, szFile);
                    lstrcat(cd_item->ci.desc, " ");
                    lstrcat(cd_item->ci.desc, pStringFile);
                }
			}
		}

		if (app->how_handle == HANDLE_SHELLEXECUTE || app->how_handle == HANDLE_BY_OLE) {
			// We need to have a description or the helper app UI won't display the
			// item. If it's set as shell execute or launch as OLE that means we're getting
            // it from the registry, and it better have a description...
            ASSERT(cd_item->ci.desc);
        }
	}

	app->cd_item = cd_item;
	cd_item->ci.fe_data = app;  // store the helper app object
	theApp.m_HelperListByType.SetAt(cd_item->ci.type,app);  // store in global list
}

void fe_SetExtensionList(NET_cdataStruct *cd_item) 
{
	if (!cd_item)
		return;

	char * extlist[50];
	int iNumExt =0;  // number from ini file
	int idx;

	CString csExtListFromINI = theApp.GetProfileString("Suffixes",cd_item->ci.type);
	if (!csExtListFromINI.IsEmpty()) {
		// add extension out of ini file
		fe_ParseExtList((const char *) csExtListFromINI, &iNumExt, extlist, 50);
	}

	if (iNumExt > 0) {
		// got extension from INI file...overwrite netlib stuff
		for (idx = 0; idx < cd_item->num_exts ; idx++) {
			if (cd_item->exts[idx]) {
				XP_FREE(cd_item->exts[idx]);
				cd_item->exts[idx] = NULL;
			}
		}
		// store our new stuff
		if (cd_item->exts) XP_FREE(cd_item->exts);
		cd_item->exts = (char **)XP_ALLOC(iNumExt * sizeof(char *));
		cd_item->num_exts = iNumExt;
		for (idx = 0; idx < cd_item->num_exts ; idx++)
			cd_item->exts[idx] = extlist[idx];
	}
}

//  Retrieve the default entry of the key.
BOOL GetKeyDefault(const char *pKey, char *pBuf, DWORD *pdwBuf)   {
    BOOL bRetval = FALSE;

    if(pKey && *pKey)   {
        HKEY hOpen = NULL;
        LONG lCheck = RegOpenKeyEx(HKEY_CLASSES_ROOT, pKey, 0, KEY_QUERY_VALUE, &hOpen);
        if(ERROR_SUCCESS == lCheck) {
            DWORD dwType = REG_SZ;
            LONG lResult = RegQueryValueEx(hOpen, NULL, NULL, &dwType, (LPBYTE)pBuf, pdwBuf);
            if(ERROR_SUCCESS == lResult && *pdwBuf > 1 && REG_SZ == dwType) {
                bRetval = TRUE;
            }
            RegCloseKey(hOpen);
            hOpen = NULL;
        }
    }

    return bRetval;
}

//  Retrieve the content type entry of the key.
BOOL GetKeyMime(const char *pKey, char *pBuf, DWORD *pdwBuf)   {
    BOOL bRetval = FALSE;

    //  Only extensions do we care.
    if(pKey && '.' == *pKey)   {
        HKEY hOpen = NULL;
        LONG lCheck = RegOpenKeyEx(HKEY_CLASSES_ROOT, pKey, 0, KEY_QUERY_VALUE, &hOpen);
        if(ERROR_SUCCESS == lCheck) {
            DWORD dwType = REG_SZ;
            LONG lResult = RegQueryValueEx(hOpen, "Content Type", NULL, &dwType, (LPBYTE)pBuf, pdwBuf);
            if(ERROR_SUCCESS == lResult && *pdwBuf > 1 && REG_SZ == dwType) {
                bRetval = TRUE;
            }
            RegCloseKey(hOpen);
            hOpen = NULL;
        }
    }

    return bRetval;
}

// Retrieve the class name for the given extension
BOOL
GetExtClassName(LPCSTR lpszExtension, CString &strClass)
{
    char    szKey[_MAX_EXT + 1] = { '.', '\0' };
    char    szClass[128];
	DWORD	lcb = sizeof(szClass);

	// Look up the file association key which maps a file extension
	// to a file class
    lstrcat(szKey, lpszExtension);

    BOOL bResult = GetKeyDefault(szKey, szClass, &lcb);

    if(FALSE == bResult || lcb <= 1)
		return FALSE;

    strClass = szClass;
    return TRUE;
}

//  Return value must be freed by caller.
char *InventMime(const char *pClassName)
{
    LPSTR   lpszMimeType = NULL;

    // Only do this is there's a class associated with the extension
    if (pClassName && *pClassName)  {
        lpszMimeType = (LPSTR)XP_ALLOC(lstrlen(SZ_WINASSOC) + lstrlen(pClassName) + 1);
        if(lpszMimeType)    {
            lstrcpy(lpszMimeType, SZ_WINASSOC);
            lstrcat(lpszMimeType, pClassName);
        }
    }

    return lpszMimeType;
}

//  Return value must be freed by caller.
char *InventDescription(const char *pExtension, const char *pClassName)
{
    if (!pExtension)
        return NULL;

    // Get the class name
    if (pClassName && *pClassName)  {
        LONG    cbData;
        LONG    lResult;

#ifdef _WIN32
        // See how much space we need
        cbData = 0;
        lResult = RegQueryValue(HKEY_CLASSES_ROOT, pClassName, NULL, &cbData);
        if (lResult == ERROR_SUCCESS && cbData > 1) {
            LPSTR   lpszDescription = (LPSTR)XP_ALLOC(cbData);

            if (!lpszDescription)
                return NULL;

            // Get the string
            VERIFY(RegQueryValue(HKEY_CLASSES_ROOT, pClassName, lpszDescription, &cbData) == ERROR_SUCCESS);
            return lpszDescription;
        }
#else
		// Win 3.1 RegQueryValue() doesn't support asking for the size of the data
		char	szDescription[128];

		// Get the string
		cbData = sizeof(szDescription);
		lResult = RegQueryValue(HKEY_CLASSES_ROOT, pClassName, szDescription, &cbData);
		return lResult == ERROR_SUCCESS ? XP_STRDUP(szDescription) : NULL;
#endif
    }

    return NULL;
}

// Create a front-end data structure if necessary, and set how_handle as
// HANDLE_SHELLEXECUTE if there's a shell\open command for the file extension.
// It will also set the description if there isn't already one
void ShellHelper(NET_cdataStruct *pNet, const char *pExtension, const char *pClassName)
{
    if(pNet != NULL && pNet->ci.fe_data == NULL)    {
        //  Create front end counter-part.
        CHelperApp *pApp = new CHelperApp();
        if(pApp)    {
	        pApp->cd_item = pNet;
	        pNet->ci.fe_data = (void *)pApp;

            pApp->bChanged = FALSE;
            pApp->bNewType = FALSE;
            pApp->bChangedExts = FALSE;

            // Don't set how_handle to HANDLE_SHELLEXECUTE unless there's a file
            // type class associated with the extension which has a shell\open
			// command
			//
			// Assume that we can't shell execute it
			pApp->how_handle = HANDLE_UNKNOWN;
            pApp->strFileClass = "";

            if (pClassName && *pClassName) {
                pApp->strFileClass = pClassName;

				// XXX - We really should handle verbs other than Open. FindExecutable()
				// and ShellExecute() don't either, but ShellExecuteEx() does...
                char aExe[_MAX_PATH];
                aExe[0] = '\0';
				if(FEU_FindExecutable(pExtension, aExe, FALSE, TRUE))   {
					pApp->how_handle = HANDLE_SHELLEXECUTE;
					pApp->csCmd = MIME_SHELLEXECUTE;
				}
                else if(aExe[0])    {
                    //  We reach this only if the extension itself is executable.
                    //  Those that are themselves shellexecutable must save or
                    //      we risk security.
                    pApp->how_handle = HANDLE_SAVE;
                }
            }

            //  application/octet-stream is always save.
            if(!stricmp(pNet->ci.type, APPLICATION_OCTET_STREAM)) {
                    pApp->how_handle = HANDLE_SAVE;
            }

	        theApp.m_HelperListByType.SetAt(pNet->ci.type, pApp);
        }

        //  Give it a description if not already set.
        if(pExtension != NULL && pNet->ci.desc == NULL)   {
            pNet->ci.desc = InventDescription(pExtension, pClassName);
        }
    }
}

// Given a file extension and its associated MIME type (may be NULL),
// make sure there's a corresponding entry in the netlib list of
// NET_cdataStruct structures
//
// There must be an extension, and the extension must not begin with
// a leading '.'
NET_cdataStruct *
ProcessFileExtension(const char *pExtension, const char *pClassName, const char *ccpMimeType)
{
	NET_cdataStruct *pExistingItem = NULL;

    //  Must have an extension to be considered, must not begin with a period.
    if(pExtension == NULL)  {
        return NULL;
    }
    else if(*pExtension == '.')  {
        ASSERT(0);
        return NULL;
    }

    // See if the extension is already in the netlib list. If it is AND it has a MIME
	// type then call ShellHelper() which will set how_handle to HANDLE_SHELLEXECUTE
    // if there is a shell\open command for the extension
    XP_List *pTypesList = cinfo_MasterListPointer();
    NET_cdataStruct *pListEntry = NULL;
    while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))	{
        if(pListEntry->ci.type != NULL || pListEntry->ci.encoding != NULL)	{
            for(int iFind = 0; iFind < pListEntry->num_exts; iFind++)	{
                if(stricmp(pExtension, pListEntry->exts[iFind]) == 0)	{
                    // This call will create a front-end data structure if
                    // necessary, and set how to handle as HANDLE_SHELLEXECUTE if
                    // there's a shell\open command for the file extension. It will
                    // also set the description if there isn't already one
                    if(pListEntry->ci.type)
						ShellHelper(pListEntry, pExtension, pClassName);

					// Remember this item
					pExistingItem = pListEntry;
                }
            }
        }
    }

	if(pExistingItem && pExistingItem->ci.encoding)
	{
		/* don't overwrite existing encodings with nonsense types */
		return NULL;
	}

    //  If no mime type was passed in, invent one.
    const char *pMimeType = ccpMimeType;
    BOOL bFreeMimeType = FALSE;
    if(pMimeType == NULL)   {
        pMimeType = (const char *)InventMime(pClassName);
        if(pMimeType)   {
            bFreeMimeType = TRUE;
        }
        else    {
            return NULL;
        }
    }
#ifdef DEBUG
    else    {
        //  Dont like empty mime types.
        ASSERT(*pMimeType);
    }
#endif

    // Ignore extensions in the registry that have content type "application/x-msdownload"
    if (bFreeMimeType && stricmp(pMimeType, "application/x-msdownload") == 0) {
        XP_FREE((void *)pMimeType);
        pMimeType = XP_STRDUP(APPLICATION_OCTET_STREAM);
    }
    
    //  If mime type already exists in the list, must add extention to it.
    pTypesList = cinfo_MasterListPointer();
    pListEntry = NULL;
    BOOL bExisted = FALSE;
    while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))	{
        if(pListEntry->ci.type != NULL)	{
            if(stricmp(pListEntry->ci.type, pMimeType) == 0)	{
                bExisted = TRUE;

				// Check if the extension is already in the list
				if (!ExtensionIsInList(pListEntry, pExtension)) {
					char **ppTest = (char **)XP_REALLOC((void *)(pListEntry->exts), sizeof(char *) * (pListEntry->num_exts + 1));
					if(ppTest)  {
						pListEntry->exts = ppTest;
						pListEntry->exts[pListEntry->num_exts] = XP_STRDUP(pExtension);
						if(pListEntry->exts[pListEntry->num_exts])    {
							pListEntry->num_exts++;
	
							// This call will create a front-end data structure if
							// necessary, and set how to handle as HANDLE_SHELLEXECUTE if
							// there's a shell\open command for the file extension. It will
							// also set the description if there isn't already one
							ShellHelper(pListEntry, pExtension, pClassName);
						}
					}
                }
            }
        }
    }
    if(bExisted)    {
        if(bFreeMimeType)   {
            XP_FREE((void *)pMimeType);
            pMimeType = NULL;
        }
        return pListEntry;
    }

    // There's no entry in the netlib list for this MIME type so create a new entry. Don't
	// do this if there's an existing entry in the netlib list with the same extension and
	// we made up the MIME type (it's a fake generated MIME type)
	if (pExistingItem && strncmp(pMimeType, SZ_WINASSOC, strlen(SZ_WINASSOC)) == 0)
	{
   		if(bFreeMimeType)   {
       		XP_FREE((void *)pMimeType);
   		}
		return pExistingItem;
	}

    NET_cdataStruct *pNew = NET_cdataCreate();
    if(pNew)    {
        pNew->ci.type = XP_STRDUP(pMimeType);
        if(pNew->ci.type)   {
            pNew->num_exts = 1;
            pNew->exts = (char **)XP_ALLOC(sizeof(char *));
            if(pNew->exts)  {
                pNew->exts[0] = XP_STRDUP(pExtension);
                if(pNew->exts[0])   {
                    NET_cdataAdd(pNew);
                }
                else    {
                    NET_cdataFree(pNew);
                    pNew = NULL;
                }
            }
            else    {
                NET_cdataFree(pNew);
                pNew = NULL;
            }
        }
        else    {
            NET_cdataFree(pNew);
            pNew = NULL;
        }

        if(pNew)    {
            // This call will create a front-end data structure if
            // necessary, and set how to handle as HANDLE_SHELLEXECUTE if
            // there's a shell\open command for the file extension. It will
            // also set the description if there isn't already one
            ShellHelper(pNew, pExtension, pClassName);
        }
    }

    if(bFreeMimeType)   {
        XP_FREE((void *)pMimeType);
        pMimeType = NULL;
    }

	return pNew;
}

#ifndef _WIN32
// This routines looks at every file type association in the WIN.INI file.
// For each file extenion it calls ProcessFileExtension()
void winini_GenericFileTypes()
{
    const char *pSection = "Extensions";
    int iAllocSize = 1024 * 16;
    char *pBuffer = (char *)XP_ALLOC(iAllocSize);
    if(pBuffer) {
        memset(pBuffer, 0, iAllocSize);
        if(0 != GetProfileString(pSection, NULL, "", pBuffer, iAllocSize))  {
            char *pTraverse = pBuffer;
            char aExt[_MAX_PATH];
            memset(aExt, 0, sizeof(aExt));
            while(*pTraverse != '\0')   {
                strcpy(aExt, pTraverse);
                pTraverse += strlen(aExt) + 1;
                ProcessFileExtension(aExt, NULL);
            }
        }

        XP_FREE((void *)pBuffer);
        pBuffer = NULL;
    }
}
#endif

// See if there are any user defined MIME types that need to be added to the
// netlib list. These are stored in the Viewers section. Add them first so
// they're handled like the types in mktypes.h
static void
fe_UserDefinedFileTypes()
{
	for (int idx=0 ; ; idx++) {	
		char 	szBuf[20];
		
		sprintf(szBuf, "TYPE%d", idx);
		CString csType(theApp.GetProfileString("Viewers", szBuf));
		
		// XXX - we expect the list of user defined items to be contiguously numbered
		// starting at 0, and so we stop when we get back an empty value. We should
		// really handle the case where there are holes in the numbering, e.g. an
		// uninstaller removed one of the items...
		if (csType.IsEmpty())
			break;
     	
		// See whether the MIME type is already in the netlib list
		XP_List	   		*pTypesList = cinfo_MasterListPointer();
		NET_cdataStruct *pcdata;
		while ((pcdata = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))	{
			if (pcdata->ci.type != NULL && (stricmp(pcdata->ci.type, (LPCSTR)csType) == 0))
				break;
		}

		if (!pcdata) {
			// Create a new netlib type
			pcdata = NET_cdataCreate();
			pcdata->ci.type = XP_STRDUP((LPCSTR)csType);

			// Add it to the netlib list
			NET_cdataAdd(pcdata);

			// Note: don't look in the Viewers section to see how the MIME type should be
			// configured. We'll do that last after checking the registry
		}

		if (pcdata) {
			// Look in the Suffixes section to get the list of extensions associated with
			// this MIME type
			fe_SetExtensionList(pcdata);
		}
	}
	
	theApp.m_iNumTypesInINIFile = idx;
}

//Begin CRN_MIME
NET_cdataStruct *
FileExtensionInNetlibList(const char *pExtension, const char *ccpMimeType)
{
	NET_cdataStruct *pExistingItem = NULL;

    //  Must have an extension to be considered, must not begin with a period.
    if(pExtension == NULL)  {
        return NULL;
    }
    else if(*pExtension == '.')  {
        ASSERT(0);
        return NULL;
    }

    // See if the extension is already in the netlib list. If it is AND it has a MIME
	// type then return the NET_cdataStruct pointer
    XP_List *pTypesList = cinfo_MasterListPointer();
    NET_cdataStruct *pListEntry = NULL;
    while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))	{
        if(pListEntry->ci.type != NULL)	{
            for(int iFind = 0; iFind < pListEntry->num_exts; iFind++)	{
                if(stricmp(pExtension, pListEntry->exts[iFind]) == 0)	{
					// Remember this item
					pExistingItem = pListEntry;
                }
            }
        }
    }

	return pExistingItem;
}

const char *GetMimeCmd(int load_action)
{
	switch(load_action) 
	{
		case HANDLE_SAVE: 
			return MIME_SAVE;

		case HANDLE_SHELLEXECUTE: 
			return MIME_SHELLEXECUTE;

		case HANDLE_UNKNOWN:
			return MIME_SHELLEXECUTE;	//????? What should this be set to ???
//~~~
		case HANDLE_VIA_PLUGIN:
			return MIME_PLUGIN;
		case HANDLE_VIA_PLUGINAPPLET:
			return MIME_PLUGINAPPLET;

		default:
			return MIME_SHELLEXECUTE;	//????? What should this be set to ???			
	}	
}

int GetPrefLoadAction(char * pPrefix)
{
	//Get load_action, if any
	int32 load_action;
	char szPref[512];

	wsprintf(szPref, "%s.load_action", pPrefix);
	int iRet = PREF_GetIntPref(szPref, &load_action);
	if(iRet == PREF_NOERROR)
	{
		switch(load_action) 
		{
			case 1: //Save
				iRet = HANDLE_SAVE;
				break;
			case 2: //Launch 
				iRet = HANDLE_SHELLEXECUTE;
				break;
			case 3://Unknown
				iRet = HANDLE_UNKNOWN;
				break;
			case 4://Plug-In
				iRet = HANDLE_VIA_PLUGIN;
				break;
			default:
				iRet = HANDLE_SHELLEXECUTE;
				break;
		}
	}	
	else
		iRet = HANDLE_SHELLEXECUTE;

	return iRet;
}

void fe_ChangeDescription(NET_cdataStruct *pcdata, LPCSTR lpszExt, LPCSTR lpszDescription)
{
	// See if the Description has changed
	if (strcmpi(pcdata->ci.desc, lpszDescription) != 0) {

       CString strClass;

		// Get the class name
		if (GetExtClassName(lpszExt, strClass) && !strClass.IsEmpty()) {
			// Set the description
			RegSetValue(HKEY_CLASSES_ROOT, strClass, REG_SZ, lpszDescription, lstrlen(lpszDescription));

			// Now go ahead and change the description in the netlib data structure
			StrAllocCopy(pcdata->ci.desc, lpszDescription);
		}
	}
}


void fe_UpdateMControlMimeTypes(void) 
{
	CString str;
	char * child_list;
	int bOK = PREF_NOERROR;
	char * type = NULL;
	char * ext = NULL;
	char * desc = NULL;
	char * open_cmd = NULL;


	if ( PREF_CreateChildList("mime", &child_list) == 0 )
	{	
		char szPref[512];
		int index = 0;
		char *child = NULL;
		while (child = PREF_NextChild(child_list, &index)) 
		{
			TRACE(child);TRACE("\r\n");	
			
			wsprintf(szPref, "%s.mimetype", child);
			bOK = PREF_CopyCharPref(szPref, &type);
			if (bOK == PREF_NOERROR) 
			{
				wsprintf(szPref, "%s.extension", child);
				bOK = PREF_CopyCharPref(szPref, &ext);
				if(bOK == PREF_NOERROR)
				{
					wsprintf(szPref, "%s.description", child);
					bOK = PREF_CopyCharPref(szPref, &desc);
					if(bOK == PREF_NOERROR)
					{
						wsprintf(szPref, "%s.win_appname", child);
						bOK = PREF_CopyCharPref(szPref, &open_cmd);
					}
				}
			}
			//Register new type only if all the fields are specified.
			if(type && type[0] && ext && ext[0] && desc && desc[0] && open_cmd && open_cmd[0])
			{
				//char szJunk[256];
				//wsprintf(szJunk, "%s - %s - %s - %s\r\n", (type && type[0]) ? type : "", (ext && ext[0]) ? ext : "", (desc && desc[0]) ? desc : "", (open_cmd && open_cmd[0]) ? open_cmd : "");
				//TRACE(szJunk);
				
				NET_cdataStruct *pcdata= NULL;

				int load_action = GetPrefLoadAction(child);

				//Check and see if we already have this file extension with the mime type
				pcdata = FileExtensionInNetlibList(ext, type);
				if(pcdata == NULL)
				{
					pcdata = fe_NewFileType(desc, ext, type, open_cmd);
					CHelperApp *pApp = (CHelperApp *)pcdata->ci.fe_data;
					if(pApp)
					{
						pApp->how_handle = load_action;
						pApp->csCmd = GetMimeCmd(load_action);
						pApp->csMimePrefPrefix = child;
					}
				}
				else
				{
					CHelperApp *pApp = (CHelperApp *)pcdata->ci.fe_data;
					if(pApp)
					{
						pApp->csMimePrefPrefix = child;
					}

					fe_ChangeFileType(pcdata, type, load_action, open_cmd);
					fe_ChangeDescription(pcdata, ext, desc);
				}
			}

			if(type && type[0])
			{
				XP_FREE(type);
				type = NULL;
			}
			if(ext && ext[0])
			{
				XP_FREE(ext);
				ext = NULL;
			}
			if(desc && desc[0])
			{
				XP_FREE(desc);
				desc = NULL;
			}
			if(open_cmd && open_cmd[0])
			{
				XP_FREE(open_cmd);
				open_cmd = NULL;
			}
		}
		XP_FREE(child_list);
	}

}
//End CRN_MIME

void fe_CleanupFileFormatTypes(void) 
{
	NET_cdataStruct *cd_item;
    XP_List   * infolist = cinfo_MasterListPointer();;  // Get beginning of the list
	CHelperApp * app;
		    
	while ((cd_item = (NET_cdataStruct *)XP_ListNextObject(infolist))) {  // iterate through the list  
		if (cd_item->ci.type) {  // if it is a mime type  
			app = (CHelperApp *)cd_item->ci.fe_data;
			
			if (app) {
				if (app->bChanged) 				
					theApp.WriteProfileString("Viewers",cd_item->ci.type,app->csCmd);
				delete app;
			}				
		}
	}
}
//
// Add a user defined mime type to our list of helper applications
//
CHelperApp * fe_AddNewFileFormatType(const char *mime_type,const char *subtype) 
{
	NET_cdataStruct * cd_item;
    XP_List   		* infolist = cinfo_MasterListPointer();;  // Get beginning of the list
	CString 		  csMime;

	if (!infolist) return NULL;
	cd_item = (NET_cdataStruct *)XP_ALLOC(sizeof(NET_cdataStruct));
	memset(cd_item,0,sizeof(NET_cdataStruct));
	csMime += mime_type;
	csMime += '/';
	csMime += subtype;
	cd_item->ci.type = XP_STRDUP((const char *)csMime);

	CHelperApp * app = new CHelperApp();
	app->bChanged = TRUE;
	app->bNewType = TRUE;
	app->bChangedExts = FALSE;	
	app->how_handle = HANDLE_UNKNOWN;
	app->cd_item = cd_item;
	app->csCmd = "";
	cd_item->ci.fe_data = app;  // store the helper app object

    // store in FE's list of types
	theApp.m_HelperListByType.SetAt(cd_item->ci.type, app);

    // tell the netlib about it
	XP_ListAddObjectToEnd(infolist,cd_item); 
	return app;
}

// Sets the shell open command string value for the given file class
void
SetShellOpenCommand(LPCSTR lpszFileClass, LPCSTR lpszCmdString)
{
	char	szKey[_MAX_PATH];
	HKEY	hKey;
	LONG	lResult;

	// Build the subkey string
	wsprintf(szKey, "%s\\shell\\open\\command", lpszFileClass);

	// Update the shell\open\command for the file type class
	lResult = RegCreateKey(HKEY_CLASSES_ROOT, szKey, &hKey);

	if (lResult == ERROR_SUCCESS) {
		RegSetValue(hKey, NULL, REG_SZ, lpszCmdString, lstrlen(lpszCmdString));
		RegCloseKey(hKey);
	}
}

#ifdef _WIN32
// We expect the extension to begin with '.'
static void
AddToMIMEDatabase(LPCSTR lpszType, LPCSTR lpszExt)
{
	char	szKey[_MAX_PATH];
	HKEY	hKey;

	PR_snprintf(szKey, sizeof(szKey), "MIME\\Database\\Content Type\\%s", lpszType);

	// What we're really doing is setting the default extension for the
	// content type
	//
	// Since we don't have a UI for specifying the default extension when creating
	// a new file type, only add this is there's nothing already there
	if (RegCreateKey(HKEY_CLASSES_ROOT, szKey, &hKey) == ERROR_SUCCESS) {
		DWORD	cbData;
		LONG	lResult;

		// See if there's anything there
		cbData = 0;
		lResult = RegQueryValueEx(hKey, "Extension", NULL, NULL, NULL, &cbData);

		if (lResult != ERROR_SUCCESS || cbData <= 1)
			RegSetValueEx(hKey, "Extension", 0, REG_SZ, (CONST BYTE *)lpszExt, lstrlen(lpszExt) + 1);

		// Close the key
		RegCloseKey(hKey);
	}
}
#endif

// Adds a new content type to the list of Netscape specific types, and adds lpszExtension
// to the list of extensions for the MIME type. lpszExtenion should have a leading '.'
static void
AddNetscapeMimeType(LPCSTR lpszMimeType, LPCSTR lpszExtension)
{
    char szBuf[20];
    
    // Add this to the list of user-defined MIME types
    sprintf(szBuf, "TYPE%d", theApp.m_iNumTypesInINIFile++);
    theApp.WriteProfileString("Viewers", szBuf, lpszMimeType);

	// Get the list of extensions for the MIME type
	CString strExtList(theApp.GetProfileString("Suffixes", lpszMimeType));

    ASSERT(lpszExtension && (*lpszExtension == '.'));

	strExtList.TrimLeft();
	strExtList.TrimRight();
	if (strExtList.IsEmpty()) {
		theApp.WriteProfileString("Suffixes", lpszMimeType, lpszExtension);

	} else if (!ExtensionIsInList(strExtList, lpszExtension + 1)) {
		// Add the extension to the end of the list
		strExtList += ',';
		strExtList += lpszExtension;
		theApp.WriteProfileString("Suffixes", lpszMimeType, (LPCSTR)strExtList);
	}
}

// Routine to add a new file type. Note that this routine is also called
// when the user is adding an additional MIME type to an existing file
// extension
NET_cdataStruct *
fe_NewFileType(LPCSTR lpszDescription,
			   LPCSTR lpszExtension,
			   LPCSTR lpszMimeType,
			   LPCSTR lpszOpenCmd)
{
	char	szExtKey[_MAX_PATH];
	CString	strFileClass;
	HKEY	hKey;
#ifdef _WIN32
    BOOL    bAlreadyHasMimeType;
#endif

    // Parse out list of extensions.
    LPSTR exts[50];
    int   i, numExts;
    fe_ParseExtList( lpszExtension, &numExts, exts, 50 );

    // Process each extension...
    for( i = 0; i < numExts; i++ ) {

	// Create a key for the file type extension. There may already be a key for
	// this extension; that's okay this will open it
	wsprintf(szExtKey, ".%s", exts[i]);
	if (RegCreateKey(HKEY_CLASSES_ROOT, szExtKey, &hKey) != ERROR_SUCCESS)
        return NULL;

	// Set the file type class associated with the extension. Note that we ONLY
	// do this if there is no file type class. If there's already a file type class
	// we assume that we're just adding another MIME type for this file type
	if (!GetExtClassName(lpszExtension, strFileClass)) {
		// Create a file type class
		strFileClass = exts[i];
		strFileClass += "file";
	
		// Set the file type class
		RegSetValue(hKey, NULL, REG_SZ, (LPCSTR)strFileClass, strFileClass.GetLength());
	}

#ifdef _WIN32
	// Add the Content Type for the extension. Only do this if there isn't already a
	// content type specified for the extension. Note: unfortunately Microsoft only
	// allows a single content type for a particular extension. If there's already a
	// content type that's okay, we'll add the additional content type to the MIME database
	LONG	lResult;
	DWORD	cbData;

	ASSERT(lpszMimeType);
	cbData = 0;
	lResult = RegQueryValueEx(hKey, "Content Type", NULL, NULL, NULL, &cbData);
    bAlreadyHasMimeType = (lResult == ERROR_SUCCESS && cbData > 1);

	if (!bAlreadyHasMimeType)
		RegSetValueEx(hKey, "Content Type", 0, REG_SZ, (CONST BYTE *)lpszMimeType, lstrlen(lpszMimeType) + 1);
#endif
	RegCloseKey(hKey);

	// Create a key for the file type class. If there's already a key this will open
	// it
	RegCreateKey(HKEY_CLASSES_ROOT, (LPCSTR)strFileClass, &hKey);

	// Set the description
	RegSetValue(hKey, NULL, REG_SZ, lpszDescription, lstrlen(lpszDescription));
	RegCloseKey(hKey);

	// Add the shell Open command
	SetShellOpenCommand((LPCSTR)strFileClass, lpszOpenCmd);

	// Update the MIME database information
#ifdef _WIN32
	AddToMIMEDatabase(lpszMimeType, szExtKey);
	
	// If there's already a MIME type and we're adding an additional type, then
	// add it to the Netscape specific information in the Suffixes section
	if (bAlreadyHasMimeType)
		AddNetscapeMimeType(lpszMimeType, szExtKey);
#else
	AddNetscapeMimeType(lpszMimeType, szExtKey);
#endif
    } // end for loop for each extension

	// Create the NET_cdataStruct structure and associated CHelperApp object
    NET_cdataStruct *pcdata = NET_cdataCreate();

    if (pcdata) {
        pcdata->ci.desc = XP_STRDUP(lpszDescription);
        pcdata->ci.type = XP_STRDUP(lpszMimeType);
        pcdata->num_exts = numExts;
        pcdata->exts = (char **)XP_ALLOC(numExts*sizeof(char *));
        if (pcdata->exts) {
            for( i = 0; i < numExts; i++ ) {
                pcdata->exts[i] = XP_STRDUP(exts[i]);
            }
        }

        // Add it to the netlib list
        NET_cdataAdd(pcdata);

        // Create the front-end data structure
        CHelperApp *pApp = new CHelperApp;

        if (pApp) {
			// Initialize the object
            pApp->bChanged = FALSE;
            pApp->bNewType = FALSE;
            pApp->bChangedExts = FALSE;
            pApp->how_handle = HANDLE_SHELLEXECUTE;
            pApp->csCmd = MIME_SHELLEXECUTE;
			pApp->strFileClass = strFileClass;

			// Front-end and netlib data structures need to point to each other
	        pApp->cd_item = pcdata;
	        pcdata->ci.fe_data = (void *)pApp;

			// Store it in the global list ordered by MIME type
	        theApp.m_HelperListByType.SetAt(pcdata->ci.type, pApp);
        }
    }

	return pcdata;
}

#ifdef _WIN32
// Routine to recursively delete a key with subkeys. Needed under Win NT only
LONG
RegDeleteKeyNT(HKEY hStartKey, LPCSTR lpszKeyName)
{
	HKEY    hKey;
	LONG	lResult;

	// Do not allow NULL or empty key name
	if (!lpszKeyName || *lpszKeyName == '\0')
		return ERROR_BADKEY;

	lResult = RegOpenKeyEx(hStartKey, lpszKeyName, 0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey);
	if (lResult != ERROR_SUCCESS)
		return lResult;

	 while (lResult == ERROR_SUCCESS) {
		char		szName[256];
		DWORD		cbName = sizeof(szName);
		FILETIME	ftLastWriteTime;

		// Recursively delete each of the subkeys
		lResult = RegEnumKeyEx(hKey, 0, szName, &cbName, NULL, NULL, NULL, &ftLastWriteTime);

		if (lResult == ERROR_SUCCESS)
			lResult = RegDeleteKeyNT(hKey, szName);
	 }

	 RegCloseKey(hKey);
	 return RegDeleteKey(hStartKey, lpszKeyName);
} 
#endif

// Returns the count of the number of NET_cdataStruct structures that have
// lpszFileClass as their file type class
static int
OccurencesOfFileClass(LPCSTR lpszFileClass)
{
    XP_List 		*pTypesList = cinfo_MasterListPointer();
    NET_cdataStruct *pcdata = NULL;
	int				 nResult = 0;

    while ((pcdata = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))	{
		if (pcdata->ci.fe_data) {
			CHelperApp *pHelperApp = (CHelperApp *)pcdata->ci.fe_data;

			if (pHelperApp->strFileClass == lpszFileClass)
				nResult++;
		}
    }

	return nResult;
}

// Removes a file type
BOOL
fe_RemoveFileType(NET_cdataStruct *pcdata)
{
	CHelperApp	*pHelperApp = (CHelperApp *)pcdata->ci.fe_data;
	
	if (pHelperApp && (pHelperApp->how_handle == HANDLE_SHELLEXECUTE || pHelperApp->how_handle == HANDLE_BY_OLE)) {
		// Remove the registry keys
		for (int i = 0; i < pcdata->num_exts; i++) {
			CString	strClass;
	
			// Get the file type class
			if (GetExtClassName(pcdata->exts[i], strClass)) {
				// Remove the subkey for the file type class. Don't delete the file type class
				// unless this is the only netlib entry that has this file type class. The
				// reason for this is that we arrange items by MIME type and not by file type class
				// like Windows does. The user is asking to remove a particular MIME type and
				// it's associated extensions; only remove the file type class if this is the only
				// item with that file type class
				ASSERT(pHelperApp->strFileClass == strClass);
				if (OccurencesOfFileClass((LPCSTR)strClass) <= 1) {
	#ifdef _WIN32
					RegDeleteKeyNT(HKEY_CLASSES_ROOT, (LPCSTR)strClass);
	#else
					RegDeleteKey(HKEY_CLASSES_ROOT, (LPCSTR)strClass);
	#endif
				}
			}
	
			// Remove the key for the file type extension
			char	szBuf[_MAX_PATH];
	
			PR_snprintf(szBuf, sizeof(szBuf), ".%s", pcdata->exts[i]);
	#ifdef _WIN32
			RegDeleteKeyNT(HKEY_CLASSES_ROOT, szBuf);
	#else
			RegDeleteKey(HKEY_CLASSES_ROOT, szBuf);
	#endif
	
	#ifdef _WIN32
			// Clean up the MIME database. Only remove this key if the default
			// extension matches this extension
			if (pcdata->ci.type) {
				HKEY	hKey;
				LONG	lResult;
	
				PR_snprintf(szBuf, sizeof(szBuf), "MIME\\Database\\Content Type\\%s",
					pcdata->ci.type);
	
				if (RegOpenKeyEx(HKEY_CLASSES_ROOT, szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {
					BOOL	bDeleteKey = FALSE;
					DWORD	cbData;
					char	szDefaultExt[_MAX_EXT];
	
					// Get the extension associated with this MIME type
					cbData = sizeof(szDefaultExt);
					lResult = RegQueryValueEx(hKey, "Extension", NULL, NULL, (LPBYTE)szDefaultExt, &cbData);
					if (lResult == ERROR_SUCCESS && cbData > 2) {
						// Only delete the key if the extension matches one of the extensions in our list
						bDeleteKey = ExtensionIsInList(pcdata, &szDefaultExt[1]);
					}
	
					RegCloseKey(hKey);
	
					if (bDeleteKey)
						RegDeleteKeyNT(HKEY_CLASSES_ROOT, szBuf);
				}
			}
	#endif
		}
	}

	// Cleanup up any Netscape specific associations
	if (pcdata->ci.type) {
		theApp.WriteProfileString("Suffixes", pcdata->ci.type, NULL);
		theApp.WriteProfileString("Viewers", pcdata->ci.type, NULL);
	}
	
	// Clean up the FE data structures
	if (pHelperApp) {
		// Remove the helper app from the global list
		if (pcdata->ci.type)
			theApp.m_HelperListByType.RemoveKey(pcdata->ci.type);

		delete pHelperApp;
		pcdata->ci.fe_data = NULL;
	}

	// Remove the item from the cdata list. This frees it as well
	NET_cdataRemove(pcdata);
	return TRUE;
}

static void
ChangeMIMEType(NET_cdataStruct *pcdata, LPCSTR lpszMimeType, int nHowToHandle)
{
	// We need to check both the Netscape specific information, and we also
	// need to check the registry. First check the Netscape specific information
    CString strValue;
	
    // First thing to handle is the Viewers section.  See if there's an application
    // associated with this MIME type
	strValue = theApp.GetProfileString("Viewers", pcdata->ci.type, NULL);
	if (!strValue.IsEmpty()) {
		// Rename the key by removing the old key and creating a new key with the same value
		theApp.WriteProfileString("Viewers", pcdata->ci.type, NULL);
		theApp.WriteProfileString("Viewers", lpszMimeType, (LPCSTR)strValue);
	}

	// Check if there is a "TYPE%d" value associated with this MIME type
	for (int i = 0; i < theApp.m_iNumTypesInINIFile; i++) {
		char szBuf[20];

		sprintf(szBuf, "TYPE%d" ,i);
		strValue = theApp.GetProfileString("Viewers", szBuf, NULL);
		if (strValue.CompareNoCase(pcdata->ci.type) == 0) {
			// Rename the key by removing the old key and creating a new key with the same value
			theApp.WriteProfileString("Viewers", szBuf, NULL);
			theApp.WriteProfileString("Viewers", szBuf, lpszMimeType);
			break;
		}
	}
	
    // See if there are file extensions specified for the type
	strValue = theApp.GetProfileString("Suffixes", pcdata->ci.type, NULL);
	if (!strValue.IsEmpty()) {
		// Rename the key by removing the old key and creating a new key with the same value
		theApp.WriteProfileString("Suffixes", pcdata->ci.type, NULL);
		theApp.WriteProfileString("Suffixes", lpszMimeType, (LPCSTR)strValue);
	}

#ifdef _WIN32
	char	szKey[_MAX_PATH];
	HKEY	hKey;
	LONG	lResult;

	// Check the registry. We need to check the MIME database, and the Content Type associated
	// with each of the file extensions
	PR_snprintf(szKey, sizeof(szKey), "MIME\\Database\\Content Type\\%s", pcdata->ci.type);

	// Get the extension that's associated with this MIME type
	if (RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {
		BOOL	bDeleteKey = FALSE;
		DWORD	cbData;
		char	szDefaultExt[_MAX_EXT];

		// Get the key value
		cbData = sizeof(szDefaultExt);
		lResult = RegQueryValueEx(hKey, "Extension", NULL, NULL, (LPBYTE)szDefaultExt, &cbData);
		RegCloseKey(hKey);
		
		if (lResult == ERROR_SUCCESS && cbData > 1) {
			// Only rename the key if the extension matches one of the extensions in our list
			if (ExtensionIsInList(pcdata, &szDefaultExt[1])) {
				// Rename the key by deleting it and creating a new key
				RegDeleteKey(HKEY_CLASSES_ROOT, szKey);
				AddToMIMEDatabase(lpszMimeType, szDefaultExt);
			}
		}
	
	} else {
		char	szDefaultExt[_MAX_EXT];
		
		// There's no existing MIME type entry so go ahead and create a new key. Use
		// the first extension in the list
		wsprintf(szDefaultExt, ".%s", pcdata->exts[0]);
		AddToMIMEDatabase(lpszMimeType, szDefaultExt);
	}
	
	// Rename the Content Type values associated with the extensions
	for (i = 0; i < pcdata->num_exts; i++) {
		char	szKey[_MAX_EXT];
		HKEY	hKey;
		LONG	lResult;

		// Build the file association key. It must begin with a '.'
		wsprintf(szKey, ".%s", pcdata->exts[i]);

		// Open the key for the file extension
		lResult = RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hKey);
		if (lResult == ERROR_SUCCESS) {
			char	szContentType[256];
			DWORD	cbData;
			DWORD	dwType;

			// Only change the MIME type for the extension if the MIME type that's currently
			// there matches
			cbData = sizeof(szContentType);
			lResult = RegQueryValueEx(hKey, "Content Type", NULL, &dwType, (LPBYTE)szContentType, &cbData);
			if (lResult == ERROR_SUCCESS && cbData > 1) {
				if (lstrcmpi(szContentType, pcdata->ci.type) == 0) {
					// Change the MIME type
					RegSetValueEx(hKey, "Content Type", 0, REG_SZ, (CONST BYTE *)lpszMimeType, lstrlen(lpszMimeType) + 1);
				}

			} else {
				// There is no existing MIME type, so go ahead and add one
				RegSetValueEx(hKey, "Content Type", 0, REG_SZ, (CONST BYTE *)lpszMimeType, lstrlen(lpszMimeType) + 1);
			}

			RegCloseKey(hKey);
		}
	}
#endif

    // Update the FE list ordered by MIME type
    theApp.m_HelperListByType.RemoveKey(pcdata->ci.type);
	theApp.m_HelperListByType.SetAt(lpszMimeType, (CHelperApp *)pcdata->ci.fe_data);
	
    // Now go ahead and change the MIME type in the netlib data structure
	StrAllocCopy(pcdata->ci.type, lpszMimeType);
}

BOOL
fe_ChangeFileType(NET_cdataStruct *pcdata, LPCSTR lpszMimeType, int nHowToHandle, LPCSTR lpszOpenCmd)
{
	CHelperApp *pHelperApp = (CHelperApp *)pcdata->ci.fe_data;
    CString     strFileClass;

    // See if the MIME type changed
	if (strcmpi(pcdata->ci.type, lpszMimeType) != 0) {
		ChangeMIMEType(pcdata, lpszMimeType, nHowToHandle);
	}

    // See if how we handle it changed
    if (pHelperApp->how_handle != nHowToHandle) {
        pHelperApp->how_handle = nHowToHandle;

        if (pcdata->ci.type) {
            // Almost everything is stored in the registry now except for
            // the special types that Netscape can handle internally
            if (strcmp(pcdata->ci.type, IMAGE_GIF) == 0) {
                if (pHelperApp->how_handle == HANDLE_VIA_NETSCAPE)
                    NET_RegisterContentTypeConverter(IMAGE_GIF, FO_PRESENT, NULL, IL_ViewStream);
                else {
					if (pHelperApp->how_handle == HANDLE_SHELLEXECUTE)
						pHelperApp->how_handle = HANDLE_EXTERNAL;  // save in Netscape specific information
					NET_RegisterContentTypeConverter(IMAGE_GIF, FO_PRESENT, NULL , external_viewer_disk_stream);
				}
			}

			if (strcmp(pcdata->ci.type, IMAGE_JPG) == 0) {
				if (pHelperApp->how_handle == HANDLE_VIA_NETSCAPE)
					NET_RegisterContentTypeConverter(IMAGE_JPG, FO_PRESENT, NULL, IL_ViewStream);
				else {
					if (pHelperApp->how_handle == HANDLE_SHELLEXECUTE)
						pHelperApp->how_handle = HANDLE_EXTERNAL;  // save in Netscape specific information
					NET_RegisterContentTypeConverter(IMAGE_JPG, FO_PRESENT, NULL , external_viewer_disk_stream);
				}
			}
        
			if (strcmp(pcdata->ci.type, IMAGE_XBM) == 0) {
				if (pHelperApp->how_handle == HANDLE_VIA_NETSCAPE)
					NET_RegisterContentTypeConverter(IMAGE_XBM, FO_PRESENT, NULL, IL_ViewStream);
				else {
					if (pHelperApp->how_handle == HANDLE_SHELLEXECUTE)
						pHelperApp->how_handle = HANDLE_EXTERNAL;  // save in Netscape specific information
					NET_RegisterContentTypeConverter(IMAGE_XBM, FO_PRESENT, NULL , external_viewer_disk_stream);
				}
			}
		}

		// Make sure we don't leave old Netscape specific information lying around. Note that
		// for types that Netscape can handle internally it's assumed we're handling it unless
		// it's marked as HANDLE_EXTERNAL in the Netscape specific information
		if (pHelperApp->how_handle == HANDLE_EXTERNAL || pHelperApp->how_handle == HANDLE_SAVE
//~~~      
        || pHelperApp->how_handle == HANDLE_VIA_PLUGIN || pHelperApp->how_handle == HANDLE_VIA_PLUGINAPPLET) {
			// This gets saved in fe_CleanupFileFormatTypes()
			if (pHelperApp->how_handle == HANDLE_SAVE)
				pHelperApp->csCmd = MIME_SAVE;
			if (pHelperApp->how_handle == HANDLE_VIA_PLUGIN)
				pHelperApp->csCmd = MIME_PLUGIN;
			if (pHelperApp->how_handle == HANDLE_VIA_PLUGINAPPLET)
				pHelperApp->csCmd = MIME_PLUGINAPPLET;

      pHelperApp->bChanged = TRUE;
		
		} else {
			// Remove any existing entry for this MIME type. If the MIME type isn't listed in the
			// Viewers section, then we use the default behavior which is HANDLE_SHELLEXECUTE
            theApp.WriteProfileString("Viewers", pcdata->ci.type, NULL);
		}
	}

	// See if the Open command has changed
	switch (pHelperApp->how_handle) {
		case HANDLE_EXTERNAL:
			if (pHelperApp->csCmd.Compare(lpszOpenCmd) != 0) {
				pHelperApp->csCmd = lpszOpenCmd;

				// Update the Netscape specific association
				if (pcdata->ci.type)
					theApp.WriteProfileString("Viewers", pcdata->ci.type, lpszOpenCmd);
			}
			break;

		case HANDLE_SHELLEXECUTE:
			if (GetExtClassName(pcdata->exts[0], strFileClass) && !strFileClass.IsEmpty())
				SetShellOpenCommand(strFileClass, lpszOpenCmd);
			break;

		case HANDLE_BY_OLE:
			fe_SetHandleByOLE(pcdata->ci.type, pHelperApp, TRUE);
			break;
		default:
			break;
	}
    return TRUE;
}

// exts - Array of Pointers of extension.
// numofExt - number of extension in this array.
// return - TRUE if these extension can be handle by OLE, otherwise FALSE.
BOOL fe_CanHandleByOLE(char** exts, short numOfExt)
{
	char aExtension[MAX_PATH + 1];
	BOOL canHandleByOLE = FALSE;
    HKEY    hkSubCLSID ;
    HKEY    hTempKey;
    LONG    cb;
    TCHAR	szName[MAX_PATH + 1];
    TCHAR	 szBuf[MAX_PATH + 1];
    TCHAR	 szTempCLSID[MAX_PATH + 1];

    cb = sizeof(szName);
	
	for (int i = 0; i < numOfExt; i++) {
		strcpy(aExtension, exts[i]);  // reset for next extension.
        if ((RegQueryValue( HKEY_CLASSES_ROOT, (LPTSTR)aExtension, szName, &cb) == ERROR_SUCCESS) &&
            (RegOpenKey( HKEY_CLASSES_ROOT, szName, &hkSubCLSID ) == ERROR_SUCCESS)) {
		    cb = sizeof(szName);
			// get the clsid key for this extension.
            if (RegQueryValue(hkSubCLSID, "CLSID", szTempCLSID, &cb) == ERROR_SUCCESS) {
                wsprintf( szBuf, _T("CLSID\\%s\\Insertable"), (LPTSTR)szTempCLSID) ;
                if (RegOpenKey( HKEY_CLASSES_ROOT, szBuf, &hTempKey ) == ERROR_SUCCESS)	{
					canHandleByOLE = TRUE;
                    RegCloseKey( hTempKey ) ;
                }
            }
            RegCloseKey( hkSubCLSID ) ;
        }
	}
	return canHandleByOLE;
}


BOOL fe_SetHandleByOLE(char* mimeType, CHelperApp* app, BOOL handleByOLE)
{
	if (handleByOLE) {
		app->how_handle = HANDLE_BY_OLE;
		app->csCmd = MIME_OLE;
		return theApp.WriteProfileString("Viewers", mimeType, MIME_OLE);
	}
	else {
		app->how_handle = HANDLE_SHELLEXECUTE;
		app->csCmd = MIME_SHELLEXECUTE;
		return theApp.WriteProfileString("Viewers", mimeType, NULL);
	}
}

BOOL fe_IsHandleByOLE(char* mimeType)
{
	CString csCmd = theApp.GetProfileString("Viewers", mimeType);  
	if (csCmd.Compare( MIME_OLE))
		return FALSE;
	else return TRUE;
}

#ifdef XP_WIN32
//This function is taken from the PE file Profile.cpp

BOOL  CopyRegKeys(HKEY  hKeyOldName,
				   HKEY  hKeyNewName,
				   DWORD subkeys,
				   DWORD maxSubKeyLen,
				   DWORD maxClassLen,
				   DWORD values,
				   DWORD maxValueNameLen,
				   DWORD maxValueLen,
				   const char *OldPath,
				   const char *NewPath)
{

	BOOL Err = FALSE;
	DWORD index;


	// first loop through and copies all the value keys

	if (values > 0) {

		DWORD valueNameSize = maxValueNameLen + 1;
		char *valueName = (char *)malloc(sizeof(char) * valueNameSize);
		DWORD dataSize = maxValueLen + 1;
		unsigned char *data = (unsigned char *)malloc(sizeof(char) * dataSize);
		DWORD type;

		if ((valueName) && (data)) {

			for (index=0; index<values; index++) {

				// gets each value name and its data
				if (ERROR_SUCCESS == RegEnumValue(hKeyOldName, index, valueName, &valueNameSize, NULL, &type, data, &dataSize)) {
				
					// create value in our new profile key
					if (ERROR_SUCCESS != RegSetValueEx(hKeyNewName, valueName, 0, type, data, dataSize)) {
						// unknown err occured... what do we do?
						Err = TRUE;
						return FALSE;
					}
				}

				valueNameSize = maxValueNameLen + 1;
				dataSize = maxValueLen + 1;
			}

			free(valueName);
			free(data);
		}
	}


	// next, we need to creates subkey folders

	if (subkeys > 0) {

		char OldSubkeyPath[260];
		char NewSubkeyPath[260];
		HKEY hkeyOldSubkey;
		HKEY hkeyNewSubkey;


		for (index=0; index<subkeys; index++) {

			DWORD subkeyNameSize = maxSubKeyLen + 1;
			char *subkeyName = (char *)malloc(sizeof(char) * subkeyNameSize);

			if (subkeyName) {

				// gets each subkey name
				if (ERROR_SUCCESS == RegEnumKey(hKeyOldName, index, subkeyName, subkeyNameSize)) {
				
					// create subkey in our new profile keypath
					if (ERROR_SUCCESS != RegCreateKey(hKeyNewName, subkeyName, &hkeyNewSubkey))  {
						Err = TRUE;
						return FALSE;
					}

					// now the subkey is created, we need to get hkey value for oldpath and 
					// then copy everything in the keys again

					// construct key path
					strcpy((char *)OldSubkeyPath, OldPath);
					strcat((char *)OldSubkeyPath, "\\");
					strcat((char *)OldSubkeyPath, subkeyName);

					strcpy((char *)NewSubkeyPath, NewPath);
					strcat((char *)NewSubkeyPath, "\\");
					strcat((char *)NewSubkeyPath, subkeyName);

					free(subkeyName);


					// now... try to start the copying process for our subkey here
					// first, gets the hkey for the old subkey profile path
					if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, (char *)OldSubkeyPath, NULL, KEY_ALL_ACCESS, &hkeyOldSubkey)) {
			
						DWORD SubKeys;
						DWORD MaxSubKeyLen;
						DWORD MaxClassLen;
						DWORD Values;
						DWORD MaxValueNameLen;
						DWORD MaxValueLen;
						DWORD SecurityDescriptor;
						FILETIME LastWriteTime;

						// get some information about this old profile subkey 
						if (ERROR_SUCCESS == RegQueryInfoKey(hkeyOldSubkey, NULL, NULL, NULL, &SubKeys, &MaxSubKeyLen, &MaxClassLen, &Values, &MaxValueNameLen, &MaxValueLen, &SecurityDescriptor, &LastWriteTime)) {

							// copy the values & key stuff

							Err = CopyRegKeys(hkeyOldSubkey, hkeyNewSubkey, SubKeys, MaxSubKeyLen, MaxClassLen, Values, MaxValueNameLen, MaxValueLen, (char *)OldSubkeyPath, (char *)NewSubkeyPath);

							if (!Err)
								return FALSE;

						}

						RegCloseKey(hkeyOldSubkey);
					}
				}
			}
		}
	}

	return TRUE;
}

#endif

//  There are a lot of initializations having to do with helper
//      applications and external protocol handlers that we
//      put off until the last second for startup performance
//      time.
//  Most involve the netlib cdata lists.


//  Some thread info.
//  Array cannot be accessed unless the thread has exited.
HANDLE hRegThread = NULL;
DWORD dwRegThreadID = 0;
DWORD dwArraySize = 0;
#define _REGNAME 0
#define _REGCLASS 1
#define _REGMIME 2
#define _REGMAX 3
char *(*ppaRegArray)[_REGMAX] = NULL;

//  This routines looks at every file type association in the registry.
//      For each file extension it calls ProcessFileExtension()
//      For each protocol, it calls ProcessShellProtocol();
void registry_Loop()
{
    if(hRegThread)  {
        //  We must wait for the registry thread to complete.
        WaitForSingleObject(hRegThread, INFINITE);
        hRegThread = NULL;
        dwRegThreadID = 0;

        if(ppaRegArray) {
            DWORD dwExtKey = 0;
            do  {
                if(ppaRegArray[dwExtKey][_REGNAME])    {
                    if('.' == ppaRegArray[dwExtKey][_REGNAME][0])    {
                        ProcessFileExtension(&ppaRegArray[dwExtKey][_REGNAME][1], ppaRegArray[dwExtKey][_REGCLASS], ppaRegArray[dwExtKey][_REGMIME]);
                    }
                    else    {
                        //ProcessShellProtocol(ppaRegArray[dwExtKey][_REGNAME], ppaRegArray[dwExtKey][_REGCLASS]);
                    }
                    delete ppaRegArray[dwExtKey][_REGNAME];
                    ppaRegArray[dwExtKey][_REGNAME] = NULL;
                    if(ppaRegArray[dwExtKey][_REGCLASS])    {
                        delete ppaRegArray[dwExtKey][_REGCLASS];
                        ppaRegArray[dwExtKey][_REGCLASS] = NULL;
                    }
                    if(ppaRegArray[dwExtKey][_REGMIME])    {
                        delete ppaRegArray[dwExtKey][_REGMIME];
                        ppaRegArray[dwExtKey][_REGMIME] = NULL;
                    }
                }
                dwExtKey++;
            }
            while(dwExtKey < dwArraySize);
            delete [] ppaRegArray;
            ppaRegArray = NULL;
            dwArraySize = 0;
        }
    }
}

//  Little understood legacy code.
void fe_LegacyNetlibInit(void)  {
    NET_cdataStruct *cd_item = NULL;
    XP_List *infolist = cinfo_MasterListPointer();

    // Use the Netscape specific information. This means looking
    // at the Viewers and Suffixes sections
    // Do this for every entry in the netlib list
    while((cd_item = (NET_cdataStruct *)XP_ListNextObject(infolist))) {  // iterate through the list  
        if(cd_item->ci.type) {  // if it is a mime type
            // Look in the Viewers section to see how the MIME type should be configured.
            // This allows us to override anything we found in the registry, e.g. user wants to
            // Save to disk or open as an OLE server
            fe_AddTypeToList(cd_item);

            // Look in the Suffixes section to get the list of extensions associated with
            // this MIME type
            fe_SetExtensionList(cd_item);
        }
    }
}

void fe_InitFileFormatTypes(void) {
	//  See if there are any user defined MIME types stored in the
    //      viewers section. Add them first so this way when we
    //      look in the registry we'll find shell execute handlers.
	fe_UserDefinedFileTypes();

    //  Registry holds a lot of info.
    registry_Loop();

    //  Legacy init, not well understood.
    fe_LegacyNetlibInit();
}

void fe_MimeProtocolHelperInit(void)	{
    static BOOL bMPHI = TRUE;
    if(bMPHI)   {
        bMPHI = FALSE;

        //  Empty the message queue before we hog the CPU.
        MSG msg;
        while(::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))   {
            BOOL bPumpVal = theApp.NSPumpMessage();
            //  shouldn't be WM_QUIT here, but would like to know.
            ASSERT(bPumpVal);
        }

        //  This sets up helper applications from the prefs and
        //      from the system registry.
        fe_InitFileFormatTypes();

        //  Initialize our OLE streaming viewers.
        COleRegistry::RegisterIniViewers();
        //  Initialize our OLE protocol handlers.
        COleRegistry::RegisterIniProtocolHandlers();
    }
}

DWORD WINAPI regthreadproc(LPVOID *pParam)  {
    DWORD dwRetval = 0;

    //  Figure out the size of the registry.
    LONG lQuery = RegQueryInfoKey(HKEY_CLASSES_ROOT, NULL, NULL, NULL, &dwArraySize, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
    if(ERROR_SUCCESS == lQuery)  {
        //  Allocate enough space for pointers to the info.
        ppaRegArray = new char *[dwArraySize][_REGMAX];
        if(ppaRegArray) {
            memset(ppaRegArray, 0, dwArraySize * _REGMAX * sizeof(char *));

            char aExtension[256] = { '\0' };
            DWORD dwExtension = 0;

            DWORD dwExtKey = 0;
            LONG lCheckEnum = ERROR_SUCCESS;

            //  The loop.
            do  {
                aExtension[0] = '\0';
                dwExtension = sizeof(aExtension);
                lCheckEnum = RegEnumKeyEx(HKEY_CLASSES_ROOT, dwExtKey, aExtension, &dwExtension, NULL, NULL, NULL, NULL);
                if(lCheckEnum == ERROR_SUCCESS) {
                    //  Store the name.
                    ppaRegArray[dwExtKey][_REGNAME] = new char[lstrlen(aExtension) + 1];
                    if(ppaRegArray[dwExtKey][_REGNAME])    {
                        lstrcpy(ppaRegArray[dwExtKey][_REGNAME], aExtension);

                        char aClassName[256] = { '\0' };
                        DWORD dwClassName = sizeof(aClassName);

                        if(GetKeyDefault(aExtension, aClassName, &dwClassName))   {
                            //  Store the class.
                            ppaRegArray[dwExtKey][_REGCLASS] = new char[dwClassName];
                            if(ppaRegArray[dwExtKey][_REGCLASS])    {
                                lstrcpy(ppaRegArray[dwExtKey][_REGCLASS], aClassName);
                            }
                        }

                        char aMime[256] = { '\0' };
                        DWORD dwMime = sizeof(aMime);
                        if(GetKeyMime(aExtension, aMime, &dwMime))   {
                            //  Store it away too.
                            ppaRegArray[dwExtKey][_REGMIME] = new char[dwMime];
                            if(ppaRegArray[dwExtKey][_REGMIME]) {
                                lstrcpy(ppaRegArray[dwExtKey][_REGMIME], aMime);
                            }
                        }
                    }
                }
                dwExtKey++;
            }
            while(lCheckEnum == ERROR_SUCCESS && dwExtKey < dwArraySize);
        }
    }

    return(dwRetval);
}

void fe_StartRegistryLoop(void) {
    hRegThread = CreateThread(NULL, 1024, (LPTHREAD_START_ROUTINE)regthreadproc, NULL, 0, &dwRegThreadID);
}

void fe_EndRegistryLoop(void)   {
    //  If the thread is still active, terminate it.
    //  Forget about cleanup, the app should be exiting and we would
    //      rather not stand in the way.
    DWORD dwExitCode = 0;
    if(hRegThread && GetExitCodeThread(hRegThread, &dwExitCode))  {
        if(STILL_ACTIVE == dwExitCode)  {
            TerminateThread(hRegThread, 1);
        }
    }
}