/* -*- 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.
 */

// umimemap.cp
// CMimeMapper class and CMimeList
// Created by atotic, June 6th, 1994
// ===========================================================================

// Front End
#include "umimemap.h"
#include "macutil.h"
#include "resae.h"
#include "uapp.h"
#include "CAppleEventHandler.h"
#include "ulaunch.h"
#include "ufilemgr.h"
#include "uprefd.h"
#include "resgui.h"
#include "BufferStream.h"

// Netscape
#include "client.h"
#include "net.h"
// stdc
#include <String.h>

#include "uerrmgr.h"
#include "prefapi.h"

//----------------------------------------------------------------------
// class CMimeMapper
//----------------------------------------------------------------------

	// ¥¥ Constructors/destructors

// Initialize all the variables to defaults
// Every initializer calls it
void CMimeMapper::InitMapper()
{
	fTemporary = false;			// This is only a temporary mapper
	fRegisteredViewer = false;	// This is a registered viewer, use all the special codes
	fTempAppSig = '????';		// Application
	fTempFileType = '????';		// File signature
	fFromOldPrefs = false;		// Was this mapper from pre-plugin prefs?
	fLatentPlugin = false;		// Was the plug-in disabled because itÕs missing?
	fLoadAction = CMimeMapper::Unknown;
	fBasePref = nil;			// Corresponding XP pref branch name
}


Boolean
CMimeMapper::NetscapeCanHandle(CStr255& mimeType)
{
	return ((mimeType == IMAGE_GIF) ||
			(mimeType == IMAGE_JPG) ||
			(mimeType == IMAGE_XBM) ||
			(mimeType == APPLICATION_BINHEX) ||
			(mimeType == HTML_VIEWER_APPLICATION_MIME_TYPE) ||
			(mimeType == TEXT_PLAIN));
}


// ¥¥ XP Prefs
//
// CMimeMapper is technically now redundant because all the information 
// it contains is also reflected in xp prefs.  Keeping duplicate
// structures is not ideal but it's minimal-impact.
//
// The xp pref routines are:
//  - CreateMapperFor: creates a mapper corresponding to a specific
//    xp mime pref (e.g. mime.image_gif)
//  - ReadMimePref: converts an xp pref to a mapper (called from
//	  CreateMapperFor, and when xp prefs are updated by auto-config).
//	- WriteMimePref: converts a mapper to its xp pref representation
//	  (called when initializing 3.0-format preferences, after editing
//	  a mime type from its pref UI, and when registering plug-ins).

static const char* Pref_MimeType = ".mimetype";
static const char* Pref_AppName = ".mac_appname";
static const char* Pref_AppSig = ".mac_appsig";
static const char* Pref_FileType = ".mac_filetype";
static const char* Pref_Extension = ".extension";
static const char* Pref_PluginName = ".mac_plugin_name";
static const char* Pref_Description = ".description";
static const char* Pref_LoadAction = ".load_action";
static const char* Pref_LatentPlugin = ".latent_plug_in";

// CreateMapperFor converts an xp pref name into a mimetype mapper.
// Finds an existing mapper or creates a new one, and
// populates its fields from user pref values.
CMimeMapper* CMimeMapper::CreateMapperFor( const char* basepref, Boolean newPrefFormat )
{
	CMimeMapper* mapper = CPrefs::sMimeTypes.FindBasePref(basepref);

	if (mapper) {
		mapper->ReadMimePrefs();
		return NULL;	// already exists; caller doesn't use it.
	}
	else {
		mapper = new CMimeMapper(basepref);
		// FromOldPrefs triggers plug-ins to install themselves as preferred viewers
		mapper->fFromOldPrefs = !newPrefFormat;
		return mapper;
	}
}

// CreateMapperForRes converts a 3.0-format mime resource
// into both a mimetype mapper and xp prefs.
CMimeMapper* CMimeMapper::CreateMapperForRes(Handle res)
{
	SInt8 flags = ::HGetState(res);
	::HNoPurge(res);
	
	CMimeMapper* mapper = new CMimeMapper( res );
	::HSetState(res, flags);

	if (mapper->fMimeType == HTML_VIEWER_APPLICATION_MIME_TYPE) {
		mapper->SetLoadAction(CPrefs::sViewSourceInline ? Internal : Launch);
	}

	mapper->WriteMimePrefs(false);			// convert to xp format

	mapper->SetDefaultDescription();

	return mapper;
}


// Constructor to build mapper from xp prefs
CMimeMapper::CMimeMapper( const char* basepref )
{
	InitMapper();
	
	fBasePref = XP_STRDUP(basepref);
	if (strlen(fBasePref) > 200)
		fBasePref[200] = '\0';
	ReadMimePrefs();
	
	SetDefaultDescription();
}

// Copies an xp mimetype pref into the mapper
void CMimeMapper::ReadMimePrefs()
{
	int err;
	int size = 256,
		actualSize;
	char value[256];

	fAppSig = 0;
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_AppSig), value, &size );
	if (PREF_NOERROR == err)
	{
		// values stored as binary (Base64) will always be longer than 4 bytes
		// so if the string is 4 bytes long (or shorter), it's a straight character string
		actualSize = strlen(value);
		if (actualSize <= sizeof(OSType))
			::BlockMoveData(value, (Ptr) &fAppSig, actualSize );
		else
		{
			err = PREF_GetBinaryPref ( CPrefs::Concat( fBasePref, Pref_AppSig ), value, &size);
			if (PREF_NOERROR == err)
				::BlockMoveData( value, (Ptr) &fAppSig, sizeof(OSType) );
		}
	}

	fFileType = 0;
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_FileType), value, &size );
	if (PREF_NOERROR == err)
	{
		// see notes above for fAppSig
		actualSize = strlen(value);
		if (actualSize <= sizeof(OSType))
			::BlockMoveData( value, (Ptr) &fFileType, actualSize );
		else
		{
			err = PREF_GetBinaryPref ( CPrefs::Concat( fBasePref, Pref_FileType ), value, &size);
			if (PREF_NOERROR == err)
				::BlockMoveData( value, (Ptr) &fFileType, sizeof(OSType) );
		}
	}
	
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_AppName), value, &size );
	if (PREF_NOERROR == err)
		fAppName = value;
	
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_MimeType), value, &size );
	if (PREF_NOERROR == err)
		fMimeType = value;
	
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_Extension), value, &size );
	if (PREF_NOERROR == err)
		fExtensions = value;
	
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_PluginName), value, &size );
	if (PREF_NOERROR == err)
		fPluginName = value;
	
	err = PREF_GetCharPref( CPrefs::Concat(fBasePref, Pref_Description), value, &size );
	if (PREF_NOERROR == err)
		fDescription = value;
	
	int32 intvalue;
	err = PREF_GetIntPref( CPrefs::Concat(fBasePref, Pref_LoadAction), &intvalue );
	if (PREF_NOERROR == err)
		fLoadAction = (LoadAction) intvalue;
	
	XP_Bool boolvalue;
	err = PREF_GetBoolPref( CPrefs::Concat(fBasePref, Pref_LatentPlugin), &boolvalue );
	if (PREF_NOERROR == err)
		fLatentPlugin = (Boolean) boolvalue;
}

// -- For 3.0 format compatability --
// Initializes off a resource handle of one of the mime types. 
// See pref.r for resource definition
// The memory layout of the resource is very important.
#define NAME_LENGTH_OFFSET 9
CMimeMapper::CMimeMapper( Handle resRecord )
{
	ThrowIfNil_(resRecord);
	Size size = ::GetHandleSize(resRecord);
	InitMapper();

	char 	temp[256];
	Ptr		resPtr = *resRecord;
	Byte	lengthByte = 0;
	long	offset = 0;
	
	ThrowIf_(size < 9);
	::BlockMoveData( resPtr, (Ptr)&fAppSig, 4 );
	::BlockMoveData( resPtr + 4, (Ptr)&fFileType, 4 );
	uint32 loadAction = *((unsigned char*) (resPtr + 8));
	fLoadAction = (LoadAction) loadAction;

	offset = NAME_LENGTH_OFFSET;
	ThrowIf_(offset > size);
	// ¥ read the application name
	lengthByte = resPtr[ offset ];
	ThrowIf_(lengthByte > 255);
	::BlockMoveData( &resPtr[ offset + 1 ], (Ptr)temp, lengthByte );

	temp[ lengthByte ] = 0;

	fAppName = temp;

	// ¥ read in the MIME type
	offset += ( lengthByte + 1 );
	ThrowIf_(offset > size);
	lengthByte = resPtr[ offset ];
	::BlockMoveData( &resPtr[ offset + 1 ], temp, lengthByte );

	temp[ lengthByte ] = 0;

	fMimeType = temp;

	// ¥ read in the extensions string
	offset += ( lengthByte + 1 );
	ThrowIf_(offset > size);
	lengthByte = resPtr[ offset ];
	ThrowIf_(lengthByte > 255);
	::BlockMove ( &resPtr[ offset + 1 ], temp, lengthByte );
	
	temp[ lengthByte ] = 0;
	
	fExtensions = temp;
		
	// ¥ read in the plug-in name string
	// Since the plug-in name doesn't exist in 2.0-vintage prefs resources, make
	// sure to check that there is still data left in the handle before proceeding.
	offset += ( lengthByte + 1 );
	if (offset < size)
	{
		lengthByte = resPtr[ offset ];
		ThrowIf_(lengthByte > 255);
		::BlockMove ( &resPtr[ offset + 1 ], temp, lengthByte );
		temp[ lengthByte ] = 0;
		fPluginName = temp;
		
		// Now read the description string (it too could be non-existant)
		offset += ( lengthByte + 1 );
		if (offset < size)
		{
			lengthByte = resPtr[ offset ];
			ThrowIf_(lengthByte > 255);
			::BlockMove ( &resPtr[ offset + 1 ], temp, lengthByte );
			temp[ lengthByte ] = 0;
			fDescription = temp;
			
			// Last, read the "latent plug-in" flag (if it exists)
			offset += ( lengthByte + 1 );
			if (offset < size)
			{
				Boolean latentPlugin = *((unsigned char*) (resPtr + offset));
				XP_ASSERT(latentPlugin == 0 || latentPlugin == 1);
				fLatentPlugin = latentPlugin;
			}
		}
	}
	else
	{
		fPluginName = "";
		fFromOldPrefs = true;				// No plug-in info in these prefs
	}
}

//
// For old prefs that don't have a description stored in the handle,
// look up the default description in netlib, defaulting to just the
// MIME type itself if it can't be found.  This description will then
// get written out to the prefs so it can be edited and saved by the user.
//
void CMimeMapper::SetDefaultDescription()
{
	if (fMimeType == HTML_VIEWER_APPLICATION_MIME_TYPE)
		return;

	CStr255 description;
	char* mimetype = (char*) fMimeType;
    NET_cdataStruct* cdata = NULL;
	XP_List* list_ptr = cinfo_MasterListPointer();
	while ((cdata = (NET_cdataStruct*) XP_ListNextObject(list_ptr)) != NULL)
		if (strcasecomp(mimetype, cdata->ci.type) == 0) 
			break;

	if (cdata && cdata->ci.desc)
		description = cdata->ci.desc;
	else
		description = fMimeType;
	
	if (fDescription == CStr255::sEmptyString) {
		fDescription = description;
		CPrefs::SetModified();
	}
	
	PREF_SetDefaultCharPref( CPrefs::Concat(fBasePref, Pref_Description), description );
}


// Duplicate
CMimeMapper::CMimeMapper( const CMimeMapper& clone )
{
	InitMapper();
	fMimeType = clone.fMimeType;
	fAppName = clone.fAppName;
	fAppSig = clone.fAppSig;
	fFileType = clone.fFileType;
	fLoadAction = clone.fLoadAction;
	fExtensions = clone.fExtensions;
	
	fTemporary = clone.fTemporary;		
	fRegisteredViewer = clone.fRegisteredViewer;
	fTempAppSig = clone.fTempAppSig;			// Application
	fTempFileType = clone.fTempFileType;		// File signature
	
	fPluginName = clone.fPluginName;
	fFromOldPrefs = clone.fFromOldPrefs;
	fDescription = clone.fDescription;
	fLatentPlugin = clone.fLatentPlugin;
	
	fBasePref = clone.fBasePref ? XP_STRDUP(clone.fBasePref) : nil;
}

// Copies values from the mapper to xp user pref tree.
void CMimeMapper::WriteMimePrefs( Boolean )
{
	if (fBasePref == nil) {
		// Convert mime type to a pref-name string with 
		// non-alpha chars replaced with underscores
		char typecopy[256];
		strcpy(typecopy, "mime.");
		int start = strlen(typecopy);
		strncat(typecopy, (char*) fMimeType, 200);
		for (int i = start; i < strlen(typecopy); i++)
			if (!isalnum(typecopy[i]))
				typecopy[i] = '_';
		
		// Mimetype may be blank, so pick an arbitrary name
		if (fMimeType.Length() == 0)
			strcat(typecopy, "unknown_type_999");
		
		fBasePref = XP_STRDUP(typecopy);
	}
	
	char appsig[sizeof(OSType)+1], filetype[sizeof(OSType)+1];
	appsig[sizeof(OSType)] = '\0';
	::BlockMoveData( (Ptr) &fAppSig, appsig, sizeof(OSType) );
	filetype[sizeof(OSType)] = '\0';
	::BlockMoveData( (Ptr) &fFileType, filetype, sizeof(OSType) );
	
	PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_Extension), fExtensions );
	if (fDescription.Length() > 0)
		PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_Description), fDescription );
	PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_MimeType), fMimeType );
	PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_AppName), fAppName );
	PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_PluginName), fPluginName );
	// store appsig and filetype as 4-byte character strings, if possible.
	// otherwise, store as binary.
	if (PrintableChars( &appsig, sizeof(OSType)))
		PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_AppSig), appsig );
	else
		PREF_SetBinaryPref( CPrefs::Concat(fBasePref, Pref_AppSig), appsig, sizeof(OSType));
	if (PrintableChars( &filetype, sizeof(OSType)))
		PREF_SetCharPref( CPrefs::Concat(fBasePref, Pref_FileType), filetype );		
	else
		PREF_SetBinaryPref( CPrefs::Concat(fBasePref, Pref_FileType), filetype, sizeof(OSType));
	PREF_SetIntPref( CPrefs::Concat(fBasePref, Pref_LoadAction), fLoadAction );
	PREF_SetBoolPref( CPrefs::Concat(fBasePref, Pref_LatentPlugin), (XP_Bool) fLatentPlugin );
}

CMimeMapper::CMimeMapper(
	LoadAction loadAction,
	const CStr255&	mimeType,
	const CStr255&	appName,
	const CStr255&	extensions,
	OSType			appSignature,
	OSType			fileType )
{
	fLoadAction = loadAction;
	fMimeType = mimeType;
	fAppName = appName;
	fAppSig = appSignature;
	fFileType = fileType;
	fExtensions = extensions;
	
	fTemporary = false;
	fRegisteredViewer = false;
	fPluginName = "";
	fDescription = "";
	fLatentPlugin = false;
	fBasePref = nil;
}

// Disposes of all allocated structures
CMimeMapper::~CMimeMapper()
{
	if (fBasePref)
		free (fBasePref);
}

CMimeMapper & CMimeMapper::operator= (const CMimeMapper& mapper)
{
	fMimeType = mapper.fMimeType;	
	fAppName = mapper.fAppName;
	fAppSig = mapper.fAppSig;
	fFileType = mapper.fFileType;
	fLoadAction = mapper.fLoadAction;
	fPluginName = mapper.fPluginName;
	fFromOldPrefs = mapper.fFromOldPrefs;
	fDescription = mapper.fDescription;
	fLatentPlugin = mapper.fLatentPlugin;
	return *this;
}

void CMimeMapper::SetAppName(const CStr255& newName) 
{
	fAppName = newName;
}

void CMimeMapper::SetMimeType(const CStr255& newType) 
{
	fMimeType = newType;
}

void CMimeMapper::SetAppSig(OSType newSig) 
{
	fAppSig = newSig;
}

void CMimeMapper::SetAppSig(FSSpec& appSpec) 
{
	FInfo finderInfo;
	OSErr err = FSpGetFInfo(&appSpec, &finderInfo );	
	ThrowIfOSErr_(err);
	fAppSig = finderInfo.fdCreator;
}

void CMimeMapper::SetExtensions(const CStr255& newExtensions)
{
	fExtensions = newExtensions;
	SyncNetlib();
}

void CMimeMapper::SetDocType(OSType newType) 
{
	fFileType = newType;
}	

void CMimeMapper::SetLoadAction(LoadAction newAction)
{
	//
	// If the user explicitly changed the load action,
	// cancel the latent plug-in setting so their choice
	// is maintained persistently.
	//
	fLatentPlugin = false;
		
	//
	// If the user explicitly changed the load action,
	// cancel the registration of an external viewer.
	//
	fRegisteredViewer = false;
	
	fLoadAction = newAction;
	SyncNetlib();
}

void CMimeMapper::SetLatentPlugin()
{
	fLatentPlugin = true;
	fLoadAction = CMimeMapper::Unknown;
}


void CMimeMapper::RegisterViewer(OSType tempAppSig, OSType tempFileType)
{
	fTempAppSig = tempAppSig;		// Application
	fTempFileType = tempFileType;		// File signature
	fRegisteredViewer = TRUE;
	CFrontApp::RegisterMimeType(this);
}

void CMimeMapper::UnregisterViewer()
{
	fRegisteredViewer = FALSE;
	CFrontApp::RegisterMimeType(this);
}

// Typical Spy apple event creation routine.
// It creates the event, with two arguments, URL and the MIME type.
// URL type is stored as attribute urlAttribute, MIME type as AE_spy_viewDocFile_mime
OSErr	CMimeMapper::MakeSpyEvent(AppleEvent & event, 
								URL_Struct * request, 
								AEEventID id,
								AEKeyword urlAttribute)
{
	OSErr err;
	Try_
	{
		AEAddressDesc targetApp;
		
		// Specify the application, and create the event
		{
		err = ::AECreateDesc(typeApplSignature, &fTempAppSig, 
								 sizeof(fTempAppSig), &targetApp);
		ThrowIfOSErr_(err);
		OSErr err = ::AECreateAppleEvent(AE_spy_send_suite, id,
									&targetApp,
									kAutoGenerateReturnID,
									kAnyTransactionID,
									&event);
		::AEDisposeDesc(&targetApp);
		}
		// URL
		{
			AEDesc urlDesc;
			err = ::AECreateDesc(typeChar, request->address, strlen(request->address), &urlDesc);
			ThrowIfOSErr_(err);
			
			err = ::AEPutParamDesc(&event, urlAttribute, &urlDesc);
			ThrowIfOSErr_(err);
			::AEDisposeDesc(&urlDesc);
		}
		// MIME type
		if (request->content_type)
		{
			AEDesc mimeDesc;
			err = ::AECreateDesc(typeChar, request->content_type, strlen(request->content_type), &mimeDesc);
			ThrowIfOSErr_(err);
			err = ::AEPutParamDesc(&event, AE_spy_queryViewer_mime, &mimeDesc);			
			::AEDisposeDesc(&mimeDesc);
		}
	}
	Catch_(inErr)
	{	return inErr;
	}
	EndCatch_
	return noErr;
}

/* Returns whether the contents of inBuffer are all visible, printable characters.
   Actually, that's a misnomer ... it returns whether the contents are printable
   on a Macintosh, which we define to be "not control characters."  This is intended
   for determining whether a binary buffer can be stored as a text string, or needs
   to be encoded. */
Boolean CMimeMapper::PrintableChars(const void *inBuffer, Int32 inLength)
{
	unsigned char	*buffer = (unsigned char *) inBuffer,
					*bufferEnd = buffer + inLength;

	while (buffer < bufferEnd)
	{
		if (*buffer <= 0x20)
			break;
		buffer++;
	}
	return buffer == bufferEnd;
}

// if we have a registered viewer, query it for a file spec
OSErr	CMimeMapper::GetFileSpec(URL_Struct * request, 
								FSSpec & destination)
{
	OSErr err = noErr;
	if (!fRegisteredViewer)
		return fnfErr;
	else
	Try_
	{	
		AppleEvent queryViewerEvent;

		err = MakeSpyEvent(queryViewerEvent, request,AE_spy_queryViewer,
						keyDirectObject); // URL is the direct object
		// 	Send the event
		AppleEvent	reply;
		err = ::AESend(&queryViewerEvent,&reply,kAEWaitReply,kAEHighPriority,600,nil, nil);
		ThrowIfOSErr_(err);
		// Handle the reply. We want to get out the transaction ID
		{
			DescType realType;
			Size actualSize;
			if (!MoreExtractFromAEDesc::DisplayErrorReply(reply))
			{
				err = ::AEGetParamPtr(&reply, keyAEResult, typeFSS, 
						 &realType, &destination, sizeof(destination), 
						&actualSize);
				ThrowIfOSErr_(err);
			}
			else
				Throw_(fnfErr);
			::AEDisposeDesc(&reply);
		}
	}
	Catch_(inErr)
	{
		return inErr;
	}	EndCatch_
	return noErr;
}

// If we have a registered viewer, use ViewDocFile
// if anything fails, or we have no viewer, use ordinary launch sequence
OSErr CMimeMapper::LaunchFile(LFileBufferStream * inFile, URL_Struct * request, Int32 windowID)
{
	OSErr err;
	if (fRegisteredViewer)
	{
		Try_
		{
			AppleEvent viewDocEvent;
			// AppleEvent(fileSpec, URL, MIME, windowID)
			// Create the event, with MIME and URL arguments
			err = MakeSpyEvent(viewDocEvent, request,AE_spy_viewDocFile,
							AE_spy_viewDocFile_url); // URL is the direct object
			ThrowIfOSErr_(err);
			// Make the file spec
			FSSpec fileSpec;
			inFile->GetSpecifier(fileSpec);
			err = ::AEPutParamPtr(&viewDocEvent,keyDirectObject,typeFSS,&fileSpec,sizeof(fileSpec));
			ThrowIfOSErr_(err);
			// URL
			if (request->address)
			{
				err = ::AEPutParamPtr(&viewDocEvent,AE_spy_viewDocFile_url, typeChar, request->address,strlen(request->address));
				ThrowIfOSErr_(err);
			}
			// MIME type
			{
				StAEDescriptor mimeDesc((StringPtr)fMimeType);
				err = ::AEPutParamDesc(&viewDocEvent,AE_spy_viewDocFile_mime,&mimeDesc.mDesc);
				ThrowIfOSErr_(err);
			}
			// WindowID
			{
			StAEDescriptor windowDesc(windowID);
			err = ::AEPutParamDesc(&viewDocEvent,AE_spy_viewDocFile_wind, &windowDesc.mDesc);
			ThrowIfOSErr_(err);
			}
			// Send the event
			AppleEvent	reply;
			err = ::AESend(&viewDocEvent,&reply,kAEWaitReply,kAEHighPriority,60,nil, nil);
			if (err != errAETimeout)
				ThrowIfOSErr_(err);
			// If we got an error code back, launch as usual
			if (reply.descriptorType != typeNull)
			{
				DescType realType;
				Size actualSize;
				long	errNumber;
				err = ::AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, 
							&realType, &errNumber, sizeof(errNumber), 
							&actualSize);
				if ((err == noErr) && (errNumber != noErr))
					Throw_(errNumber);
			}
		}
		Catch_(inErr)
		{
			// Launch through ViewDoc failed, use our original creator and launch as usual
			FSSpec fileSpec;
			inFile->GetSpecifier(fileSpec);
			err = CFileMgr::SetFileTypeCreator(fAppSig, fFileType, &fileSpec);
			::LaunchFile(inFile);
		}
		EndCatch_
	}
	else
		::LaunchFile(inFile);
	return noErr;
}


void CMimeMapper::SyncNetlib()
{
	CFrontApp::RegisterMimeType(this);
}

//----------------------------------------------------------------------
// class CMimeList
//----------------------------------------------------------------------

	// ¥¥ Constructors/destructors

CMimeList::CMimeList() : LArray()
{
}

CMimeList::~CMimeList()
{
}

	// ¥¥ÊUtility functions
// Overrode this on the assumption that every time you add a MIME mapper
// to this list, you want to sync up the netlib
ArrayIndexT CMimeList::InsertItemsAt(
	Uint32			inCount,
	ArrayIndexT		inAtIndex,
	const void		*inItem,
	Uint32			inItemSize)

{
	ArrayIndexT result = LArray::InsertItemsAt(inCount,inAtIndex,inItem, inItemSize);
	CMimeMapper ** mapperPtr = (CMimeMapper **)inItem;
	(*mapperPtr)->SyncNetlib();
	return result;
}

// Deletes all the items in the list
void CMimeList::DeleteAll(Boolean)
{
	CMimeMapper*		oldMap;
	for ( short i = 1; i <= mItemCount; i++ )
	{
		FetchItemAt( i, &oldMap );
		delete oldMap;
	}
	RemoveItemsAt( mItemCount,1);
}

// Finds a CMimeMapper with a given XP pref branch name.
CMimeMapper* CMimeList::FindBasePref( const char* basepref )
{
	CMimeMapper* foundMap = NULL;
	for ( Int32 i = mItemCount; i >= 1; i-- )
	{
		CMimeMapper* oldMap;
		FetchItemAt( i, &oldMap );
		if ( oldMap->GetBasePref() != NULL &&
			 XP_STRCMP(oldMap->GetBasePref(), basepref) == 0 )
		{
			foundMap = oldMap;
			break;
		}
	}
	return foundMap;
}


// Finds a CMimeMapper with a given mimeType. NULL if none
// Search is linear. Might want to make it faster
CMimeMapper* CMimeList::FindMimeType( char* mimeType )
{
	if ( mimeType == NULL )
		return NULL;

	CMimeMapper* foundMap = NULL;
	for ( Int32 i = 1; i <= mItemCount; i++ )
	{
		CMimeMapper* oldMap;
		FetchItemAt( i, &oldMap );
		if ( XP_STRCMP(oldMap->GetMimeName(), mimeType) == 0 )
		{
			foundMap = oldMap;
			break;
		}
	}
	return foundMap;
}

// Find a "built-in" mime type, which were separate in 3.0
// but are integrated into the 4.0 mimetype list.
CMimeMapper* CMimeList::FindMimeType(BuiltIn mimeBuiltin)
{
	char* mimetype = nil;
	switch (mimeBuiltin) {
		case HTMLViewer:
			mimetype = HTML_VIEWER_APPLICATION_MIME_TYPE;
			break;
		case Telnet:
			mimetype = TELNET_APPLICATION_MIME_TYPE;
			break;
		case Tn3270:
			mimetype = TN3270_VIEWER_APPLICATION_MIME_TYPE;
			break;
	}
	return FindMimeType(mimetype);
}


// FindMimeType finds a Mime mapper that
// matches this file's type and creator
// Algorithm is inexact
// TEXT files are not typed	because netlib figures out the 
// HTML files, and hqx ones have the extension
// Look for exact match.
// If not found, look for inexact one

// I don't like the above algorithm. There is no way of telling what type of match occured. In
// cases other than exact or FileType  match the results are unsatisfactory. 
// New behavoir is to return NULL and let the caller call CMimeList::FindCreatorFindCreator if
// needed. Provides a way to hand off to IC if needed.
CMimeMapper* CMimeList::FindMimeType(const FSSpec& inFileSpec)
{
	FInfo		fndrInfo;
	
	OSErr err = FSpGetFInfo( &inFileSpec, &fndrInfo );
	if ((err != noErr) || (fndrInfo.fdType == 'TEXT'))
		return NULL;

	CMimeMapper* fileTypeMatch = NULL;

	// Find the cinfo (which gives us the MIME type) for this file name
	NET_cinfo* cinfo = NET_cinfo_find_type((CStr255)inFileSpec.name);

	for (Int32 i = 1; i <= mItemCount; i++)
	{
		CMimeMapper* map;
		FetchItemAt(i, &map);
		
		//
		// If this MIME type is configured for a plug-in, see if it matches
		// the MIME type of the file (based on the file extension -> MIME
		// type mapping).  If not, try for an exact or partial match based
		// on type and/or creator.  bing: If we decide to support Mac file
		// types for plug-ins, we should check the file type here.
		//
		if (map->GetLoadAction() == CMimeMapper::Plugin)
		{
			if (cinfo && (map->GetMimeName() == cinfo->type))
				return map;
		}
		else if ((map->GetAppSig() == fndrInfo.fdCreator) &&
				 (map->GetDocType() == fndrInfo.fdType))
			return map;
		else 
		{
			#if 0
				if (map->GetAppSig() == fndrInfo.fdCreator)
					creatorMatch = map;
			#endif
			if (map->GetDocType() == fndrInfo.fdType)
				fileTypeMatch = map;
		}
	}

	return fileTypeMatch;	
}


// FindCreator finds a CMimeMapper whose application signature
// matches appSig
CMimeMapper* CMimeList::FindCreator(OSType appSig)
{
	CMimeMapper * foundMap = NULL;
	for (Int32 i = 1; i <= mItemCount; i++)
	{
		CMimeMapper* oldMap;
		FetchItemAt(i, &oldMap);
		if (oldMap->GetAppSig() == appSig)
		{
			foundMap = oldMap;
			break;
		}
	}
	return foundMap;
}

// ------------------------- Apple Event handling -------------------------------

// HandleRegisterViewerEvent
// registers a viewer.
// If the MIME type does not exist, we create one on the fly
void CMimeList::HandleRegisterViewerEvent(const AppleEvent	&inAppleEvent,
									AppleEvent			&outAEReply,
									AEDesc				&/*outResult*/,
									long				/*inAENumber*/)
{
	OSType	appSign;	//	app signature
	OSType	fileType = 'TEXT';	//	file type to be created
	volatile char *	mimeType = NULL;	// MIME type
	OSType	realType;
	Size	realSize;
	CMimeMapper * mapper;
	OSErr err;
	Try_ {
		{	// Get the application signature. Required
			err = ::AEGetParamPtr(&inAppleEvent,keyDirectObject,typeApplSignature,
									&realType, &appSign, sizeof(appSign), &realSize);
			ThrowIfOSErr_(err);
		}
		{	// Get the MIME type. Required
			AEDesc mimeDesc;
			err = ::AEGetParamDesc(&inAppleEvent,AE_spy_registerViewer_mime,typeWildCard,&mimeDesc);
			ThrowIfOSErr_(err);		
			MoreExtractFromAEDesc::TheCString(mimeDesc, mimeType);
			::AEDisposeDesc(&mimeDesc);
			if (mimeType == nil)
				ThrowOSErr_(errAEWrongDataType);
		}
	
		mapper = FindMimeType(mimeType);
		if (mapper)
			fileType = mapper->GetDocType();
		{	// Get the file type. If none is specified, it defaults to already registered type, or 'TEXT'
			AEDesc fileTypeDesc;
			err = ::AEGetParamDesc(&inAppleEvent,AE_spy_registerViewer_ftyp,typeWildCard,&fileTypeDesc);
			if (err == noErr)
			{
				Try_	{
					UExtractFromAEDesc::TheType(fileTypeDesc, fileType);
				} Catch_(inErr)
				{} EndCatch_
			}
			::AEDisposeDesc(&fileTypeDesc);
		}
		// We have all the arguments, set up the new mapper if necessary
		if (mapper == NULL)
		{
			mapper = new CMimeMapper(CMimeMapper::Launch, 
								mimeType,
								"-",	// I10L - this string is never seen by a user
								CStr255::sEmptyString,
								appSign,
								fileType);
			ThrowIfNil_(mapper);
			mapper->SetTemporary(TRUE);
			InsertItemsAt( 1, LArray::index_Last, &mapper);
		}
		
		// If the type is being handled by a plug-in, donÕt let the AE override
		Boolean result = false;
		if (mapper->GetLoadAction() != CMimeMapper::Plugin)
		{
			mapper->RegisterViewer(appSign, fileType);
			result = true;
		}
		
		// Reply success
		// Create the reply, it will automatically be stuck into the outgoing AE by PP
		{
			StAEDescriptor booleanDesc(result);
			err = ::AEPutKeyDesc(&outAEReply,keyAEResult,&booleanDesc.mDesc);   /* IM VI chap. 6 pg 91 */
		}
	}
	Catch_(inErr)
	{
		MoreExtractFromAEDesc::MakeErrorReturn(outAEReply,
					(unsigned char *)GetCString(REG_EVENT_ERR_RESID) , inErr);
	}	EndCatch_
	// Dispose
	if (mimeType) XP_FREE(mimeType);	
}

void CMimeList::HandleUnregisterViewerEvent(const AppleEvent	&inAppleEvent,
									AppleEvent			&outAEReply,
									AEDesc				&/*outResult*/,
									long				/*inAENumber*/)
{
	OSType	appSign;	//	app signature
	OSType	fileType = 'TEXT';	//	file type to be created
	volatile char*	mimeType = NULL;	// MIME type

	CMimeMapper * mapper = NULL;
	OSErr err;
	Try_
	{
		{	// Get the application signature. Required
			AEDesc appSignDesc;
			err = ::AEGetParamDesc(&inAppleEvent,keyDirectObject,typeWildCard,&appSignDesc);
			ThrowIfOSErr_(err);
			UExtractFromAEDesc::TheType(appSignDesc, appSign);
			::AEDisposeDesc(&appSignDesc);
		}
		{	// Get the MIME type. Required
			AEDesc mimeDesc;
			err = ::AEGetParamDesc(&inAppleEvent,AE_spy_unregisterViewer_mime,typeWildCard,&mimeDesc);
			ThrowIfOSErr_(err);		
			MoreExtractFromAEDesc::TheCString(mimeDesc, mimeType);
			::AEDisposeDesc(&mimeDesc);
			if (mimeType == nil)
				ThrowOSErr_(errAEWrongDataType);
		}
		
		mapper = FindMimeType(mimeType);
		
		if (mapper == NULL || mapper->IsViewerRegistered() == false)
		{
			//
			// If the type isnÕt found, or wasnÕt registered
			// to an external viewer, return an error.
			//
			MoreExtractFromAEDesc::MakeErrorReturn(outAEReply, 
				(unsigned char *)GetCString(APP_NOT_REG_RESID), errAEDescNotFound);
		}
		else
		{
			//
			// Otherwise unregister the viewer and remove the 
			// temporary mapper if necessary.
			//
			mapper->UnregisterViewer();
			if (mapper->IsTemporary())
			{
				Remove(&mapper);
				delete mapper;
			}
		}
	}
	Catch_(inErr)
	{
		MoreExtractFromAEDesc::MakeErrorReturn(outAEReply, 
				(unsigned char *)GetCString(UNREG_EVENT_ERR_RESID), inErr);
	}
	EndCatch_
	if (mimeType) XP_FREE(mimeType);
}

