/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is the Mozilla browser. * * The Initial Developer of the Original Code is Netscape * Communications, Inc. Portions created by Netscape are * Copyright (C) 1999, Mozilla. All Rights Reserved. * * Contributor(s): * Scott MacGregor * Bill Law */ #include "nsExternalHelperAppService.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsIFile.h" #include "nsIChannel.h" #include "nsIFileChannel.h" #include "nsIDirectoryService.h" #include "nsAppDirectoryServiceDefs.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsMemory.h" #include "nsIStreamListener.h" #include "nsIMIMEService.h" #include "nsILoadGroup.h" #include "nsCURILoader.h" #include "nsIWebProgress.h" #include "nsIWebProgressListener.h" #include "nsReadableUtils.h" // used to manage our in memory data source of helper applications #include "nsRDFCID.h" #include "rdf.h" #include "nsIRDFService.h" #include "nsIRDFRemoteDataSource.h" #include "nsIFileSpec.h" #include "nsHelperAppRDF.h" #include "nsIMIMEInfo.h" #include "nsDirectoryServiceDefs.h" #include "nsIRefreshURI.h" #include "nsIDocumentLoader.h" #include "nsIHelperAppLauncherDialog.h" #include "nsITransport.h" #include "nsIFileTransportService.h" #include "nsCExternalHandlerService.h" // contains contractids for the helper app service #include "nsIIOService.h" #include "nsNetCID.h" #include "nsMimeTypes.h" // used for http content header disposition information. #include "nsIHttpChannel.h" #include "nsIAtom.h" #include "nsIObserverService.h" // so we can be an xpcom shutdown observer #ifdef XP_MAC #include "nsILocalFileMac.h" #include "nsIInternetConfigService.h" #include "nsIAppleFileDecoder.h" #endif // XP_MAC #include "nsIPluginHost.h" #include "nsEscape.h" const char *FORCE_ALWAYS_ASK_PREF = "browser.helperApps.alwaysAsk.force"; static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); static NS_DEFINE_CID(kRDFXMLDataSourceCID, NS_RDFXMLDATASOURCE_CID); static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID); static NS_DEFINE_CID(kPluginManagerCID, NS_PLUGINMANAGER_CID); // forward declaration of a private helper function static PRBool PR_CALLBACK DeleteEntry(nsHashKey *aKey, void *aData, void* closure); // The following static table lists all of the "default" content type mappings we are going to use. // These are only the ones handled internally. These must be listed here so that we can do the // proper file-extension to content-type mapping. These cannot be overridden by user helper app // prefs. static nsDefaultMimeTypeEntry defaultMimeEntries [] = { { TEXT_PLAIN, "txt,text", "Text File", 'TEXT', 'ttxt' }, { TEXT_HTML, "htm,html,shtml,ehtml", "HyperText Markup Language", 'TEXT', 'MOSS' }, { TEXT_RDF, "rdf", "Resource Description Framework", 'TEXT','ttxt' }, { TEXT_XUL, "xul", "XML-Based User Interface Language", 'TEXT', 'ttxt' }, { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language", 'TEXT', 'ttxt' }, { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language", 'TEXT', 'ttxt' }, #ifdef MOZ_SVG { "image/svg+xml", "svg", "Scalable Vector Graphics", 'svg ', 'ttxt' }, #endif { TEXT_CSS, "css", "Style Sheet", 'TEXT', 'ttxt' }, { APPLICATION_JAVASCRIPT, "js", "Javascript Source File", 'TEXT', 'ttxt' }, { MESSAGE_RFC822, "eml", "RFC-822 data", 'TEXT', 'MOSS' }, { IMAGE_GIF, "gif", "GIF Image", 0,0 }, { IMAGE_JPG, "jpeg,jpg", "JPEG Image", 0, 0 }, { IMAGE_PNG, "png", "PNG Image", 0, 0 }, { IMAGE_BMP, "bmp", "BMP Image", 0, 0 }, { IMAGE_ICO, "ico,cur", "ICO Image", 0, 0 }, { IMAGE_MNG, "mng", "MNG Image", 0, 0 }, { IMAGE_JNG, "jng", "JNG Image", 0, 0 }, { APPLICATION_XPINSTALL, "xpi", "XPInstall Install", 'xpi*','MOSS' }, }; // This table lists all of the 'extra" content types that we can deduce from particular // file extensions. These entries also ensure that we provide a good descriptive name // when we encounter files with these content types and/or extensions. These can be // overridden by user helper app prefs. static nsDefaultMimeTypeEntry extraMimeEntries [] = { #if defined(VMS) { APPLICATION_OCTET_STREAM, "exe,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary Executable", 0, 0 }, #elif defined(XP_MAC) // don't define .bin on the mac...use internet config to look that up... { APPLICATION_OCTET_STREAM, "exe", "Binary Executable", 0, 0 }, #else { APPLICATION_OCTET_STREAM, "exe,bin", "Binary Executable", 0, 0 }, #endif { APPLICATION_GZIP2, "gz", "gzip", 0, 0 }, { IMAGE_ART, "art", "ART Image", 0, 0 }, { IMAGE_TIFF, "tiff,tif", "TIFF Image", 0, 0 }, { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File",0, 0 }, { "application/x-arj", "arj", "ARJ file", 0,0 }, }; NS_IMPL_THREADSAFE_ADDREF(nsExternalHelperAppService) NS_IMPL_THREADSAFE_RELEASE(nsExternalHelperAppService) NS_INTERFACE_MAP_BEGIN(nsExternalHelperAppService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIExternalHelperAppService) NS_INTERFACE_MAP_ENTRY(nsIExternalHelperAppService) NS_INTERFACE_MAP_ENTRY(nsPIExternalAppLauncher) NS_INTERFACE_MAP_ENTRY(nsIExternalProtocolService) NS_INTERFACE_MAP_ENTRY(nsIMIMEService) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END_THREADSAFE nsExternalHelperAppService::nsExternalHelperAppService() : mDataSourceInitialized(PR_FALSE) { NS_INIT_ISUPPORTS(); // we need a good guess for a size for our hash table...let's try O(n) where n = # of default // entries we'll be adding to the hash table. Of course, we'll be adding more entries as we // discover those content types at run time... PRInt32 hashTableSize = sizeof(defaultMimeEntries) / sizeof(defaultMimeEntries[0]); mMimeInfoCache = new nsHashtable(hashTableSize); AddDefaultMimeTypesToCache(); /* Add an observer to XPCOM shutdown */ nsresult rv = NS_OK; nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1", &rv); if (obs) rv = obs->AddObserver(NS_STATIC_CAST(nsIObserver*, this), NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); } nsExternalHelperAppService::~nsExternalHelperAppService() { if (mMimeInfoCache) { mMimeInfoCache->Reset(DeleteEntry, nsnull); delete mMimeInfoCache; } } nsresult nsExternalHelperAppService::InitDataSource() { nsresult rv = NS_OK; // don't re-initialize the data source if we've already done so... if (mDataSourceInitialized) return NS_OK; nsCOMPtr rdf = do_GetService(kRDFServiceCID, &rv); NS_ENSURE_SUCCESS(rv, rv); // Get URI of the mimeTypes.rdf data source. Do this the same way it's done in // pref-applications-edit.xul, for example, to ensure we get the same data source! // Note that the way it was done previously (using nsIFileSpec) worked better, but it // produced a different uri (it had "file:///C|" instead of "file:///C:". What can you do? nsCOMPtr mimeTypesFile; rv = NS_GetSpecialDirectory(NS_APP_USER_MIMETYPES_50_FILE, getter_AddRefs(mimeTypesFile)); NS_ENSURE_SUCCESS(rv, rv); // Get file url spec to be used to initialize the DS. nsXPIDLCString urlSpec; rv = NS_GetURLSpecFromFile(mimeTypesFile, getter_Copies(urlSpec)); NS_ENSURE_SUCCESS(rv, rv); // Get the data source; if it is going to be created, then load is synchronous. rv = rdf->GetDataSourceBlocking( urlSpec, getter_AddRefs( mOverRideDataSource ) ); NS_ENSURE_SUCCESS(rv, rv); // initialize our resources if we haven't done so already... if (!kNC_Description) { rdf->GetResource(NC_RDF_DESCRIPTION, getter_AddRefs(kNC_Description)); rdf->GetResource(NC_RDF_VALUE, getter_AddRefs(kNC_Value)); rdf->GetResource(NC_RDF_FILEEXTENSIONS,getter_AddRefs(kNC_FileExtensions)); rdf->GetResource(NC_RDF_PATH, getter_AddRefs(kNC_Path)); rdf->GetResource(NC_RDF_SAVETODISK, getter_AddRefs(kNC_SaveToDisk)); rdf->GetResource(NC_RDF_HANDLEINTERNAL,getter_AddRefs(kNC_HandleInternal)); rdf->GetResource(NC_RDF_ALWAYSASK, getter_AddRefs(kNC_AlwaysAsk)); rdf->GetResource(NC_RDF_PRETTYNAME, getter_AddRefs(kNC_PrettyName)); } mDataSourceInitialized = PR_TRUE; return rv; } /* boolean canHandleContent (in string aMimeContentType); */ NS_IMETHODIMP nsExternalHelperAppService::CanHandleContent(const char *aMimeContentType, nsIURI * aURI, PRBool *_retval) { *_retval = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsExternalHelperAppService::DoContent(const char *aMimeContentType, nsIURI *aURI, nsISupports *aWindowContext, PRBool *aAbortProcess, nsIStreamListener ** aStreamListener) { nsCOMPtr mimeInfo; nsXPIDLCString fileExtension; // (1) Try to find a mime object by looking the mime type GetFromMIMEType(aMimeContentType, getter_AddRefs(mimeInfo)); if (!mimeInfo) { // if we couldn't find one, don't give up yet! Try and see if there is an extension in the // url itself... nsCOMPtr url = do_QueryInterface(aURI); if (url) { url->GetFileExtension(getter_Copies(fileExtension)); GetFromExtension(fileExtension, getter_AddRefs(mimeInfo)); // only over write mimeInfo if we got a non-null mime info object. if (mimeInfo) { // The OS might have thought this extension was a different mime type. // We must reset this to match the actual mime type. Otherwise, we // won't use this MIMEInfo when we see the real mime type next time. mimeInfo->SetMIMEType(aMimeContentType); } } } // (2) if we don't have a match yet, see if this type is in our list of extras. if (!mimeInfo) { GetMIMEInfoForMimeTypeFromExtras(aMimeContentType, getter_AddRefs(mimeInfo)); } // (3) if we STILL don't have a mime object for this content type then give up // and create a new mime info object for it and use it if (!mimeInfo) { mimeInfo = do_CreateInstance(NS_MIMEINFO_CONTRACTID); if (mimeInfo) { // the file extension was conviently already filled in by our call to FindOSMimeInfoForType. mimeInfo->SetFileExtensions(fileExtension); mimeInfo->SetMIMEType(aMimeContentType); // we may need to add a new method to nsIMIMEService so we can add this mime info object to our mime service. } } *aStreamListener = nsnull; if (mimeInfo) { // The primary extension for the mime info may be different from // the URL extension. If the URL extension matches the mime info, // set it as the primary extension. In either case, fileExtension // should be the primary extension once we are doen. if (fileExtension.IsEmpty()) { nsCOMPtr url = do_QueryInterface(aURI); if (url) { url->GetFileExtension(getter_Copies(fileExtension)); } } PRBool matches = PR_FALSE; mimeInfo->ExtensionExists(fileExtension.get(), &matches); if (matches) { mimeInfo->SetPrimaryExtension(fileExtension.get()); } else { mimeInfo->GetPrimaryExtension(getter_Copies(fileExtension)); } // this code is incomplete and just here to get things started.. nsExternalAppHandler * handler = CreateNewExternalHandler(mimeInfo, fileExtension, aWindowContext); handler->QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aStreamListener); } return NS_OK; } NS_IMETHODIMP nsExternalHelperAppService::LaunchAppWithTempFile(nsIMIMEInfo * aMimeInfo, nsIFile * aTempFile) { // this method should only be implemented by each OS specific implementation of this service. return NS_ERROR_NOT_IMPLEMENTED; } nsExternalAppHandler * nsExternalHelperAppService::CreateNewExternalHandler(nsIMIMEInfo * aMIMEInfo, const char * aTempFileExtension, nsISupports * aWindowContext) { nsExternalAppHandler* handler = nsnull; NS_NEWXPCOM(handler, nsExternalAppHandler); // add any XP intialization code for an external handler that we may need here... // right now we don't have any but i bet we will before we are done. handler->Init(aMIMEInfo, aTempFileExtension, aWindowContext); return handler; } nsresult nsExternalHelperAppService::FillTopLevelProperties(const char * aContentType, nsIRDFResource * aContentTypeNodeResource, nsIRDFService * aRDFService, nsIMIMEInfo * aMIMEInfo) { nsresult rv = NS_OK; nsCOMPtr target; nsCOMPtr literal; const PRUnichar * stringValue; rv = InitDataSource(); if (NS_FAILED(rv)) return NS_OK; // set the mime type aMIMEInfo->SetMIMEType(aContentType); // set the pretty name description FillLiteralValueFromTarget(aContentTypeNodeResource,kNC_Description, &stringValue); aMIMEInfo->SetDescription(stringValue); // now iterate over all the file type extensions... nsCOMPtr fileExtensions; mOverRideDataSource->GetTargets(aContentTypeNodeResource, kNC_FileExtensions, PR_TRUE, getter_AddRefs(fileExtensions)); PRBool hasMoreElements = PR_FALSE; nsCAutoString fileExtension; nsCOMPtr element; if (fileExtensions) { fileExtensions->HasMoreElements(&hasMoreElements); while (hasMoreElements) { fileExtensions->GetNext(getter_AddRefs(element)); if (element) { literal = do_QueryInterface(element); if (!literal) return NS_ERROR_FAILURE; literal->GetValueConst(&stringValue); fileExtension.AssignWithConversion(stringValue); if (!fileExtension.IsEmpty()) aMIMEInfo->AppendExtension(fileExtension.get()); } fileExtensions->HasMoreElements(&hasMoreElements); } // while we have more extensions to parse.... } return rv; } nsresult nsExternalHelperAppService::FillLiteralValueFromTarget(nsIRDFResource * aSource, nsIRDFResource * aProperty, const PRUnichar ** aLiteralValue) { nsresult rv = NS_OK; nsCOMPtr literal; nsCOMPtr target; *aLiteralValue = nsnull; rv = InitDataSource(); if (NS_FAILED(rv)) return rv; mOverRideDataSource->GetTarget(aSource, aProperty, PR_TRUE, getter_AddRefs(target)); if (target) { literal = do_QueryInterface(target); if (!literal) return NS_ERROR_FAILURE; literal->GetValueConst(aLiteralValue); } else rv = NS_ERROR_FAILURE; return rv; } nsresult nsExternalHelperAppService::FillContentHandlerProperties(const char * aContentType, nsIRDFResource * aContentTypeNodeResource, nsIRDFService * aRDFService, nsIMIMEInfo * aMIMEInfo) { nsCOMPtr target; nsCOMPtr literal; const PRUnichar * stringValue = nsnull; nsresult rv = NS_OK; rv = InitDataSource(); if (NS_FAILED(rv)) return rv; nsCString contentTypeHandlerNodeName (NC_CONTENT_NODE_HANDLER_PREFIX); contentTypeHandlerNodeName.Append(aContentType); nsCOMPtr contentTypeHandlerNodeResource; aRDFService->GetResource(contentTypeHandlerNodeName.get(), getter_AddRefs(contentTypeHandlerNodeResource)); NS_ENSURE_TRUE(contentTypeHandlerNodeResource, NS_ERROR_FAILURE); // that's not good! we have an error in the rdf file // now process the application handler information aMIMEInfo->SetPreferredAction(nsIMIMEInfo::useHelperApp); // save to disk FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_SaveToDisk, &stringValue); NS_NAMED_LITERAL_STRING( trueString, "true" ); if (stringValue && trueString.Equals(stringValue)) aMIMEInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); // handle internal FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_HandleInternal, &stringValue); if (stringValue && trueString.Equals(stringValue)) aMIMEInfo->SetPreferredAction(nsIMIMEInfo::handleInternally); // always ask --> these fields aren't stored in the data source anymore //FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_AlwaysAsk, &stringValue); //if (trueString.Equals(stringValue)) // aMIMEInfo->SetAlwaysAskBeforeHandling(PR_TRUE); // else // aMIMEInfo->SetAlwaysAskBeforeHandling(PR_FALSE); // now digest the external application information nsCAutoString externalAppNodeName (NC_CONTENT_NODE_EXTERNALAPP_PREFIX); externalAppNodeName.Append(aContentType); nsCOMPtr externalAppNodeResource; aRDFService->GetResource(externalAppNodeName.get(), getter_AddRefs(externalAppNodeResource)); if (externalAppNodeResource) { FillLiteralValueFromTarget(externalAppNodeResource, kNC_PrettyName, &stringValue); if (stringValue) aMIMEInfo->SetApplicationDescription(stringValue); FillLiteralValueFromTarget(externalAppNodeResource, kNC_Path, &stringValue); if (stringValue) { nsCOMPtr application; GetFileTokenForPath(stringValue, getter_AddRefs(application)); if (application) aMIMEInfo->SetPreferredApplicationHandler(application); } } return rv; } nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromDS(const char * aContentType, nsIMIMEInfo ** aMIMEInfo) { nsresult rv = NS_OK; rv = InitDataSource(); if (NS_FAILED(rv)) return rv; // if we have a data source then use the information found in that... // if that fails....then try to the old mime service that i'm going to be // obsoleting soon... if (mOverRideDataSource) { // Get the RDF service. nsCOMPtr rdf = do_GetService(kRDFServiceCID, &rv); NS_ENSURE_SUCCESS(rv, rv); // Build uri for the mimetype resource. nsCString contentTypeNodeName (NC_CONTENT_NODE_PREFIX); nsCAutoString contentType(aContentType); ToLowerCase(contentType); contentTypeNodeName.Append(contentType); // Get the mime type resource. nsCOMPtr contentTypeNodeResource; rv = rdf->GetResource(contentTypeNodeName.get(), getter_AddRefs(contentTypeNodeResource)); NS_ENSURE_SUCCESS(rv, rv); // we need a way to determine if this content type resource is really in the graph or not... // ...Test that there's a #value arc from the mimetype resource to the mimetype literal string. nsCOMPtr mimeLiteral; NS_ConvertUTF8toUCS2 mimeType(contentType); rv = rdf->GetLiteral( mimeType.get(), getter_AddRefs( mimeLiteral ) ); NS_ENSURE_SUCCESS(rv, rv); PRBool exists = PR_FALSE; rv = mOverRideDataSource->HasAssertion(contentTypeNodeResource, kNC_Value, mimeLiteral, PR_TRUE, &exists ); if (NS_SUCCEEDED(rv) && exists) { // create a mime info object and we'll fill it in based on the values from the data source nsCOMPtr mimeInfo (do_CreateInstance(NS_MIMEINFO_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = FillTopLevelProperties(contentType.get(), contentTypeNodeResource, rdf, mimeInfo); NS_ENSURE_SUCCESS(rv, rv); rv = FillContentHandlerProperties(contentType.get(), contentTypeNodeResource, rdf, mimeInfo); *aMIMEInfo = mimeInfo; NS_IF_ADDREF(*aMIMEInfo); } // if we have a node in the graph for this content type else *aMIMEInfo = nsnull; } // if we have a data source else rv = NS_ERROR_FAILURE; return rv; } nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromDS(const char * aFileExtension, nsIMIMEInfo ** aMIMEInfo) { nsresult rv = NS_OK; *aMIMEInfo = nsnull; rv = InitDataSource(); if (NS_FAILED(rv)) return rv; // if we have a data source then use the information found in that if (mOverRideDataSource) { // Get the RDF service. nsCOMPtr rdf = do_GetService(kRDFServiceCID, &rv); NS_ENSURE_SUCCESS(rv, rv); NS_ConvertUTF8toUCS2 extension(aFileExtension); ToLowerCase(extension); nsCOMPtr extensionLiteral; rv = rdf->GetLiteral(extension.get(), getter_AddRefs( extensionLiteral)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr contentTypeNodeResource; rv = mOverRideDataSource->GetSource(kNC_FileExtensions, extensionLiteral, PR_TRUE, getter_AddRefs(contentTypeNodeResource)); nsCAutoString contentTypeStr; if (NS_SUCCEEDED(rv) && contentTypeNodeResource) { const PRUnichar* contentType = nsnull; rv = FillLiteralValueFromTarget(contentTypeNodeResource, kNC_Value, &contentType); if (contentType) contentTypeStr.AssignWithConversion(contentType); if (NS_SUCCEEDED(rv)) { // create a mime info object and we'll fill it in based on the values from the data source nsCOMPtr mimeInfo (do_CreateInstance(NS_MIMEINFO_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = FillTopLevelProperties(contentTypeStr.get(), contentTypeNodeResource, rdf, mimeInfo); NS_ENSURE_SUCCESS(rv, rv); rv = FillContentHandlerProperties(contentTypeStr.get(), contentTypeNodeResource, rdf, mimeInfo); *aMIMEInfo = mimeInfo; NS_IF_ADDREF(*aMIMEInfo); } } // if we have a node in the graph for this extension } // if we have a data source else rv = NS_ERROR_FAILURE; return rv; } ////////////////////////////////////////////////////////////////////////////////////////////////////// // begin external protocol service default implementation... ////////////////////////////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists) { // this method should only be implemented by each OS specific implementation of this service. *aHandlerExists = PR_FALSE; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL) { // this method should only be implemented by each OS specific implementation of this service. return NS_ERROR_NOT_IMPLEMENTED; } ////////////////////////////////////////////////////////////////////////////////////////////////////// // Methods related to deleting temporary files on exit ////////////////////////////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile * aTemporaryFile) { nsresult rv = NS_OK; PRBool isFile = PR_FALSE; nsCOMPtr localFile (do_QueryInterface(aTemporaryFile, &rv)); NS_ENSURE_SUCCESS(rv, rv); // as a safety measure, make sure the nsIFile is really a file and not a directory object. localFile->IsFile(&isFile); if (!isFile) return NS_OK; if (!mTemporaryFilesList) rv = NS_NewISupportsArray(getter_AddRefs(mTemporaryFilesList)); NS_ENSURE_SUCCESS(rv, rv); mTemporaryFilesList->AppendElement(localFile); return NS_OK; } nsresult nsExternalHelperAppService::ExpungeTemporaryFiles() { if (!mTemporaryFilesList) return NS_OK; nsresult rv = NS_OK; PRUint32 numEntries = 0; mTemporaryFilesList->Count(&numEntries); nsCOMPtr element; nsCOMPtr localFile; for (PRUint32 index = 0; index < numEntries; index ++) { element = getter_AddRefs(mTemporaryFilesList->ElementAt(index)); if (element) { localFile = do_QueryInterface(element); if (localFile) localFile->Remove(PR_FALSE); } } mTemporaryFilesList->Clear(); return NS_OK; } /* XPCOM Shutdown observer */ NS_IMETHODIMP nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData ) { // we must be shutting down xpcom so remove our temporary files... ExpungeTemporaryFiles(); nsresult rv = NS_OK; nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1", &rv); if (obs) rv = obs->RemoveObserver(NS_STATIC_CAST(nsIObserver*, this), NS_XPCOM_SHUTDOWN_OBSERVER_ID); return NS_OK; } ////////////////////////////////////////////////////////////////////////////////////////////////////// // begin external app handler implementation ////////////////////////////////////////////////////////////////////////////////////////////////////// NS_IMPL_THREADSAFE_ADDREF(nsExternalAppHandler) NS_IMPL_THREADSAFE_RELEASE(nsExternalAppHandler) NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher) NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_END_THREADSAFE nsExternalAppHandler::nsExternalAppHandler() { NS_INIT_ISUPPORTS(); mCanceled = PR_FALSE; mReceivedDispostionInfo = PR_FALSE; mStopRequestIssued = PR_FALSE; mDataBuffer = (char *) nsMemory::Alloc((sizeof(char) * DATA_BUFFER_SIZE)); mProgressWindowCreated = PR_FALSE; } nsExternalAppHandler::~nsExternalAppHandler() { if (mDataBuffer) nsMemory::Free(mDataBuffer); } NS_IMETHODIMP nsExternalAppHandler::GetInterface(const nsIID & aIID, void * *aInstancePtr) { NS_ENSURE_ARG_POINTER(aInstancePtr); return QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener * aWebProgressListener) { // this call back means we've succesfully brought up the // progress window so set the appropriate flag... mProgressWindowCreated = PR_TRUE; // while we were bringing up the progress dialog, we actually finished processing the // url. If that's the case then mStopRequestIssued will be true. Tell the dialog to go away in that // case and we need to execute the operation since we are actually done. if (mStopRequestIssued && aWebProgressListener) { // simulate a notification saying the document is done. This will turn around and cause our // progress dialog to go away.... aWebProgressListener->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP, NS_OK); return ExecuteDesiredAction(); } // o.t. go ahead and register the progress listener.... if (mLoadCookie) { nsCOMPtr webProgress(do_QueryInterface(mLoadCookie)); if (webProgress) { mWebProgressListener = aWebProgressListener; webProgress->AddProgressListener(mWebProgressListener); } } return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::GetDownloadInfo(nsIURI ** aSourceUrl, PRInt64 * aTimeDownloadStarted, nsIFile ** aTarget) { *aTimeDownloadStarted = mTimeDownloadStarted; if (mFinalFileDestination) { *aTarget = mFinalFileDestination; } else *aTarget = mTempFile; NS_IF_ADDREF(*aTarget); *aSourceUrl = mSourceUrl; NS_IF_ADDREF(*aSourceUrl); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::CloseProgressWindow() { // make our docloader release the progress listener from the progress window... if (mLoadCookie && mWebProgressListener) { nsCOMPtr webProgress(do_QueryInterface(mLoadCookie)); if (webProgress) { webProgress->RemoveProgressListener(mWebProgressListener); } } // release extra state... mWebProgressListener = nsnull; mLoadCookie = nsnull; return NS_OK; } void nsExternalAppHandler::ExtractSuggestedFileNameFromChannel(nsIChannel* aChannel) { // if the channel is an http channel and we have a content disposition header set, // then use the file name suggested there as the preferred file name to SUGGEST to the user. // we shouldn't actually use that without their permission...o.t. just use our temp file // Try to get HTTP channel....if we have a content-disposition header then we can nsCOMPtr httpChannel = do_QueryInterface( aChannel ); if ( httpChannel ) { // Get content-disposition response header and extract a file name if there is one... // content-disposition: has format: disposition-type < ; filename=value > nsXPIDLCString disp; nsresult rv = httpChannel->GetResponseHeader( "content-disposition", getter_Copies( disp ) ); if ( NS_SUCCEEDED( rv ) && disp ) { nsCAutoString dispositionValue; dispositionValue = disp; PRInt32 pos = dispositionValue.Find("filename=", PR_TRUE); if (pos != kNotFound) { // extract everything after the filename= part and treat that as the file name... nsCAutoString dispFileName; dispositionValue.Right(dispFileName, dispositionValue.Length() - (pos + nsCRT::strlen("filename="))); if (!dispFileName.IsEmpty()) // if we got a file name back.. { pos = dispFileName.FindChar(';'); if (pos > 0) dispFileName.Truncate(pos); // According to RFC 2183, filename can be given as filename=value, // where value is token or quoted-string. See bug 66181. dispFileName.StripChar('"'); // ONLY if we got here, will we remember the suggested file name... mSuggestedFileName.AssignWithConversion(dispFileName.get()); } } // if we found a file name in the header disposition field } // we had a disp header } // if we had an http channel } nsresult nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request) { // we are going to run the downloading of the helper app in our own little docloader / load group context. // so go ahead and force the creation of a load group and doc loader for us to use... nsresult rv = NS_OK; nsCOMPtr aChannel = do_QueryInterface(request); NS_ENSURE_TRUE(aChannel, NS_ERROR_FAILURE); nsCOMPtr uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID)); NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE); nsCOMPtr newLoadGroup; nsCOMPtr oldLoadGroup; uriLoader->GetLoadGroupForContext(NS_STATIC_CAST(nsIURIContentListener*, this), getter_AddRefs(newLoadGroup)); aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup)); // we need to store off the original (pre redirect!) channel that initiated the load. We do // this so later on, we can pass any refresh urls associated with the original channel back to the // window context which started the whole process. More comments about that are listed below.... // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. // ideally we should be able to just use mChannel (the channel we are extracting content from) or // the default load channel associated with the original load group. Unfortunately because // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel // which is what we really want.... nsCOMPtr origContextLoader; uriLoader->GetDocumentLoaderForContext(mWindowContext, getter_AddRefs(origContextLoader)); if (origContextLoader) origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel)); if(oldLoadGroup) oldLoadGroup->RemoveRequest(request, nsnull, NS_OK); aChannel->SetLoadGroup(newLoadGroup); nsCOMPtr req (do_QueryInterface(mLoadCookie)); aChannel->SetNotificationCallbacks(req); rv = newLoadGroup->AddRequest(request, nsnull); return rv; } #define SALT_SIZE 8 #define TABLE_SIZE 36 const char table[] = { 'a','b','c','d','e','f','g','h','i','j', 'k','l','m','n','o','p','q','r','s','t', 'u','v','w','x','y','z','0','1','2','3', '4','5','6','7','8','9'}; nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel) { nsresult rv = NS_OK; #ifdef XP_MAC // create a temp file for the data...and open it for writing. // use NS_MAC_DEFAULT_DOWNLOAD_DIR which gets download folder from InternetConfig // if it can't get download folder pref, then it uses desktop folder NS_GetSpecialDirectory(NS_MAC_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(mTempFile)); // while we're here, also set Mac type and creator if (mMimeInfo) { nsCOMPtr macfile = do_QueryInterface(mTempFile); if (macfile) { PRUint32 type, creator; mMimeInfo->GetMacType(&type); mMimeInfo->GetMacCreator(&creator); macfile->SetFileType(type); macfile->SetFileCreator(creator); } } #else // create a temp file for the data...and open it for writing. NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile)); #endif nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(mSourceUrl)); nsCOMPtr url = do_QueryInterface(mSourceUrl); // We need to do two things here, (1) extract the file name that's part of the url // and store this is as mSuggestedfileName. This way, when we show the user a file picker, // we can have it pre filled with the suggested file name. // (2) We need to generate a name for the temp file that we are going to be streaming data to. // We don't want this name to be predictable for security reasons so we are going to generate a // "salted" name..... if (url) { // try to extract the file name from the url and use that as a first pass as the // leaf name of our temp file... char *leafName = nsnull; // may be shortened by NS_UnescapeURL url->GetFileName(&leafName); if (leafName) { NS_UnescapeURL(leafName); mSuggestedFileName = NS_ConvertUTF8toUCS2(leafName); nsMemory::Free(leafName); } } // step (2), generate a salted file name for the temp file.... nsCAutoString saltedTempLeafName; // this salting code was ripped directly from the profile manager. // turn PR_Now() into milliseconds since epoch 1058 // and salt rand with that. double fpTime; LL_L2D(fpTime, PR_Now()); srand((uint)(fpTime * 1e-6 + 0.5)); PRInt32 i; for (i=0;iAppend(saltedTempLeafName.get()); // make this file unique!!! mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); NS_DEFINE_CID(kFileTransportServiceCID, NS_FILETRANSPORTSERVICE_CID); nsCOMPtr fts = do_GetService(kFileTransportServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr fileTransport; rv = fts->CreateTransport(mTempFile, PR_WRONLY | PR_CREATE_FILE, 0600, getter_AddRefs(fileTransport)); if (NS_FAILED(rv)) return rv; rv = fileTransport->OpenOutputStream(0, -1, 0, getter_AddRefs(mOutStream)); #ifdef XP_MAC nsXPIDLCString contentType; mMimeInfo->GetMIMEType(getter_Copies(contentType)); if (contentType && (nsCRT::strcasecmp(contentType, APPLICATION_APPLEFILE) == 0) || (nsCRT::strcasecmp(contentType, MULTIPART_APPLEDOUBLE) == 0)) { nsCOMPtr appleFileDecoder = do_CreateInstance(NS_IAPPLEFILEDECODER_CONTRACTID, &rv); if (NS_SUCCEEDED(rv) && appleFileDecoder) { rv = appleFileDecoder->Initialize(mOutStream, mTempFile); if (NS_SUCCEEDED(rv)) mOutStream = do_QueryInterface(appleFileDecoder, &rv); } } #endif return rv; } NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt) { NS_ENSURE_ARG(request); // first, check to see if we've been canceled.... if (mCanceled) // then go cancel our underlying channel too return request->Cancel(NS_BINDING_ABORTED); nsCOMPtr aChannel = do_QueryInterface(request); nsresult rv = SetUpTempFile(aChannel); // retarget all load notifcations to our docloader instead of the original window's docloader... RetargetLoadNotifications(request); // ignore failure... ExtractSuggestedFileNameFromChannel(aChannel); nsCOMPtr httpChannel = do_QueryInterface( aChannel ); if ( httpChannel ) { // Turn off content encoding conversions. httpChannel->SetApplyConversion( PR_FALSE ); } // now that the temp file is set up, find out if we need to invoke a dialog asking the user what // they want us to do with this content... PRBool alwaysAsk = PR_TRUE; mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); if (alwaysAsk) { // do this first! make sure we don't try to take an action until the user tells us what they want to do // with it... mReceivedDispostionInfo = PR_FALSE; // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); NS_ENSURE_SUCCESS(rv, rv); rv = mDialog->Show( this, mWindowContext ); // what do we do if the dialog failed? I guess we should call Cancel and abort the load.... } else { mReceivedDispostionInfo = PR_TRUE; // no need to wait for a response from the user // We need to do the save/open immediately, then. PRInt32 action = nsIMIMEInfo::saveToDisk; mMimeInfo->GetPreferredAction( &action ); if ( action == nsIMIMEInfo::saveToDisk ) { rv = SaveToDisk(nsnull, PR_FALSE); } else { rv = LaunchWithApplication(nsnull, PR_FALSE); } } mTimeDownloadStarted = PR_Now(); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count) { // first, check to see if we've been canceled.... if (mCanceled) // then go cancel our underlying channel too return request->Cancel(NS_BINDING_ABORTED); // read the data out of the stream and write it to the temp file. if (mOutStream && mDataBuffer && count > 0) { PRUint32 numBytesRead = 0; PRUint32 numBytesWritten = 0; while (count > 0) // while we still have bytes to copy... { inStr->Read(mDataBuffer, PR_MIN(count, DATA_BUFFER_SIZE - 1), &numBytesRead); if (count >= numBytesRead) count -= numBytesRead; // subtract off the number of bytes we just read else count = 0; mOutStream->Write(mDataBuffer, numBytesRead, &numBytesWritten); } } return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, nsresult aStatus) { mStopRequestIssued = PR_TRUE; // first, check to see if we've been canceled.... if (mCanceled) // then go cancel our underlying channel too return request->Cancel(NS_BINDING_ABORTED); // go ahead and execute the application passing in our temp file as an argument // this may involve us calling back into the OS external app service to make the call // for actually launching the helper app. It'd be great if nsIFile::spawn could be made to work // on the mac...right now the mac implementation ignores all arguments passed in. // close the stream... if (mOutStream) { mOutStream->Close(); mOutStream = nsnull; } return ExecuteDesiredAction(); } nsresult nsExternalAppHandler::ExecuteDesiredAction() { nsresult rv = NS_OK; if (mProgressWindowCreated && !mCanceled) { nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk; mMimeInfo->GetPreferredAction(&action); if (action == nsIMIMEInfo::saveToDisk) rv = MoveFile(mFinalFileDestination); else { // Make sure the suggested name is unique since in this case we don't // have a file name that was guaranteed to be unique by going through // the File Save dialog rv = mFinalFileDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); if (NS_SUCCEEDED(rv)) { // Source and dest dirs should be == so this should just do a rename rv = MoveFile(mFinalFileDestination); if (NS_SUCCEEDED(rv)) rv = OpenWithApplication(nsnull); } } } return rv; } nsresult nsExternalAppHandler::Init(nsIMIMEInfo * aMIMEInfo, const char * aTempFileExtension, nsISupports * aWindowContext) { mWindowContext = aWindowContext; mMimeInfo = aMIMEInfo; // make sure the extention includes the '.' if (aTempFileExtension && *aTempFileExtension != '.') mTempFileExtension = "."; mTempFileExtension.Append(aTempFileExtension); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo) { *aMIMEInfo = mMimeInfo; NS_IF_ADDREF(*aMIMEInfo); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI) { NS_ENSURE_ARG(aSourceURI); *aSourceURI = mSourceUrl; NS_IF_ADDREF(*aSourceURI); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(PRUnichar ** aSuggestedFileName) { *aSuggestedFileName = ToNewUnicode(mSuggestedFileName); return NS_OK; } nsresult nsExternalAppHandler::ShowProgressDialog() { // we are back from the helper app dialog (where the user chooses to save or open), but we aren't // done processing the load. in this case, throw up a progress dialog so the user can see what's going on... nsresult rv = NS_OK; if ( !mDialog ) { // Get helper app launcher dialog. mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); NS_ENSURE_SUCCESS(rv, rv); } rv = mDialog->ShowProgressDialog(this, mWindowContext); return rv; } nsresult nsExternalAppHandler::PromptForSaveToFile(nsILocalFile ** aNewFile, const PRUnichar * aDefaultFile, const PRUnichar * aFileExtension) { // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request // XXX Convert to use file picker? nsresult rv = NS_OK; if (!mDialog) { // Get helper app launcher dialog. mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); NS_ENSURE_SUCCESS(rv, rv); } // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined... rv = mDialog->PromptForSaveToFile(mWindowContext, aDefaultFile, aFileExtension, aNewFile); return rv; } nsresult nsExternalAppHandler::MoveFile(nsIFile * aNewFileLocation) { nsresult rv = NS_OK; NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?"); nsCOMPtr fileToUse = do_QueryInterface(aNewFileLocation); // if the on stop request was actually issued then it's now time to actually perform the file move.... if (mStopRequestIssued && fileToUse) { // Unfortunately, MoveTo will fail if a file already exists at the user specified location.... // but the user has told us, this is where they want the file! (when we threw up the save to file dialog, // it told them the file already exists and do they wish to over write it. So it should be okay to delete // fileToUse if it already exists. PRBool equalToTempFile = PR_FALSE; PRBool filetoUseAlreadyExists = PR_FALSE; fileToUse->Equals(mTempFile, &equalToTempFile); fileToUse->Exists(&filetoUseAlreadyExists); if (filetoUseAlreadyExists && !equalToTempFile) fileToUse->Remove(PR_FALSE); // extract the new leaf name from the file location nsXPIDLCString fileName; fileToUse->GetLeafName(getter_Copies(fileName)); nsCOMPtr directoryLocation; fileToUse->GetParent(getter_AddRefs(directoryLocation)); if (directoryLocation) { rv = mTempFile->MoveTo(directoryLocation, fileName); } } return rv; } // SaveToDisk should only be called by the helper app dialog which allows // the user to say launch with application or save to disk. It doesn't actually // perform the save, it just prompts for the destination file name. The actual save // won't happen until we are done downloading the content and are sure we've // shown a progress dialog. This was done to simplify the // logic that was showing up in this method. Internal callers who actually want // to preform the save should call ::MoveFile NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, PRBool aRememberThisPreference) { nsresult rv = NS_OK; if (mCanceled) return NS_OK; mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); if (!aNewFileLocation) { nsXPIDLString leafName; nsCOMPtr fileToUse; mTempFile->GetUnicodeLeafName(getter_Copies(leafName)); if (mSuggestedFileName.IsEmpty()) rv = PromptForSaveToFile(getter_AddRefs(fileToUse), leafName, NS_ConvertASCIItoUCS2(mTempFileExtension).get()); else { nsAutoString fileExt; PRInt32 pos = mSuggestedFileName.RFindChar(PRUnichar('.')); if (pos >= 0) mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos); if (fileExt.IsEmpty()) fileExt = NS_ConvertASCIItoUCS2(mTempFileExtension).get(); rv = PromptForSaveToFile(getter_AddRefs(fileToUse), mSuggestedFileName.get(), fileExt.get()); } if (NS_FAILED(rv)) return Cancel(); mFinalFileDestination = do_QueryInterface(fileToUse); if (!mProgressWindowCreated) ShowProgressDialog(); // now that the user has chosen the file location to save to, it's okay to fire the refresh tag // if there is one. We don't want to do this before the save as dialog goes away because this dialog // is modal and we do bad things if you try to load a web page in the underlying window while a modal // dialog is still up. ProcessAnyRefreshTags(); } mReceivedDispostionInfo = PR_TRUE; return rv; } nsresult nsExternalAppHandler::OpenWithApplication(nsIFile * aApplication) { nsresult rv = NS_OK; if (mCanceled) return NS_OK; // we only should have gotten here if the on stop request had been fired already. NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?"); // if a stop request was already issued then proceed with launching the application. if (mStopRequestIssued) { nsCOMPtr helperAppService (do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID)); if (helperAppService) { rv = helperAppService->LaunchAppWithTempFile(mMimeInfo, mFinalFileDestination); #ifndef XP_MAC // Mac users have been very verbal about temp files being deleted on app exit - they // don't like it - but we'll continue to do this on other platforms for now helperAppService->DeleteTemporaryFileOnExit(mFinalFileDestination); #endif } } return rv; } // LaunchWithApplication should only be called by the helper app dialog which allows // the user to say launch with application or save to disk. It doesn't actually // perform launch with application. That won't happen until we are done downloading // the content and are sure we've showna progress dialog. This was done to simplify the // logic that was showing up in this method. NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, PRBool aRememberThisPreference) { if (mCanceled) return NS_OK; mMimeInfo->SetPreferredAction(nsIMIMEInfo::useHelperApp); // launch the progress window now that the user has picked the desired action. if (!mProgressWindowCreated) ShowProgressDialog(); // user has chosen to launch using an application, fire any refresh tags now... ProcessAnyRefreshTags(); mReceivedDispostionInfo = PR_TRUE; if (mMimeInfo && aApplication) mMimeInfo->SetPreferredApplicationHandler(aApplication); // Now that the user has elected to launch the downloaded file with a helper app, we're justified in // removing the 'salted' name. We'll rename to what was specified in mSuggestedFileName after the // download is done prior to launching the helper app. So that any existing file of that name won't // be overwritten we call CreateUnique() before calling MoveFile(). Also note that we use the same // directory as originally downloaded to so that MoveFile() just does an in place rename. nsCOMPtr fileToUse; // The directories specified here must match those specified in SetUpTempFile(). This avoids // having to do a copy of the file when it finishes downloading and the potential for errors // that would introduce #ifdef XP_MAC NS_GetSpecialDirectory(NS_MAC_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(fileToUse)); #else NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(fileToUse)); #endif fileToUse->AppendUnicode(mSuggestedFileName.get()); // We'll make sure this results in a unique name later mFinalFileDestination = do_QueryInterface(fileToUse); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::Cancel() { mCanceled = PR_TRUE; // shutdown our stream to the temp file if (mOutStream) { mOutStream->Close(); mOutStream = nsnull; } // clean up after ourselves and delete the temp file... if (mTempFile) { mTempFile->Remove(PR_TRUE); mTempFile = nsnull; } return NS_OK; } void nsExternalAppHandler::ProcessAnyRefreshTags() { // one last thing, try to see if the original window context supports a refresh interface... // Sometimes, when you download content that requires an external handler, there is // a refresh header associated with the download. This refresh header points to a page // the content provider wants the user to see after they download the content. How do we // pass this refresh information back to the caller? For now, try to get the refresh URI // interface. If the window context where the request originated came from supports this // then we can force it to process the refresh information (if there is any) from this channel. if (mWindowContext && mOriginalChannel) { nsCOMPtr refreshHandler (do_GetInterface(mWindowContext)); if (refreshHandler) refreshHandler->SetupRefreshURI(mOriginalChannel); mOriginalChannel = nsnull; } } // nsIURIContentListener implementation NS_IMETHODIMP nsExternalAppHandler::OnStartURIOpen(nsIURI* aURI, PRBool* aAbortOpen) { return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::IsPreferred(const char * aContentType, char ** aDesiredContentType, PRBool * aCanHandleContent) { NS_NOTREACHED("IsPreferred"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExternalAppHandler::CanHandleContent(const char * aContentType, PRBool aIsContentPreferred, char ** aDesiredContentType, PRBool * aCanHandleContent) { NS_NOTREACHED("CanHandleContent"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExternalAppHandler::DoContent(const char * aContentType, PRBool aIsContentPreferred, nsIRequest * aRequest, nsIStreamListener ** aContentHandler, PRBool * aAbortProcess) { NS_NOTREACHED("DoContent"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExternalAppHandler::GetParentContentListener(nsIURIContentListener** aParent) { *aParent = nsnull; return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::SetParentContentListener(nsIURIContentListener* aParent) { return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::GetLoadCookie(nsISupports ** aLoadCookie) { *aLoadCookie = mLoadCookie; NS_IF_ADDREF(*aLoadCookie); return NS_OK; } NS_IMETHODIMP nsExternalAppHandler::SetLoadCookie(nsISupports * aLoadCookie) { mLoadCookie = aLoadCookie; return NS_OK; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // The following section contains our nsIMIMEService implementation and related methods. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // nsIMIMEService methods NS_IMETHODIMP nsExternalHelperAppService::GetFromExtension(const char *aFileExt, nsIMIMEInfo **_retval) { nsresult rv = NS_OK; nsCAutoString fileExt(aFileExt); if (fileExt.IsEmpty()) return NS_ERROR_FAILURE; ToLowerCase(fileExt); // if the file extension contains a '.', our hash key doesn't include the '.' // so skip over it... if (fileExt.First() == '.') fileExt.Cut(0, 1); // cut the '.' nsCStringKey key(fileExt.get()); nsIMIMEInfo *cachedInfo = (nsIMIMEInfo *) mMimeInfoCache->Get(&key); if (cachedInfo) { cachedInfo->Clone(_retval); } // if we don't have a match in our hash table, then query the user provided // data source if (!*_retval) rv = GetMIMEInfoForExtensionFromDS(aFileExt, _retval); // if we still don't have a match, then we give up, we don't know // anything about it... return an error. if (!*_retval) rv = NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsExternalHelperAppService::GetFromMIMEType(const char *aMIMEType, nsIMIMEInfo **_retval) { nsresult rv = NS_OK; nsCAutoString MIMEType(aMIMEType); ToLowerCase(MIMEType); nsCStringKey key(MIMEType.get()); nsIMIMEInfo *cachedInfo = (nsIMIMEInfo *) mMimeInfoCache->Get(&key); if (cachedInfo) { cachedInfo->Clone(_retval); } // if we don't have a match in our hash table, then query the user provided // data source containing additional content types... if (!*_retval) rv = GetMIMEInfoForMimeTypeFromDS(aMIMEType, _retval); // if we still don't have a match, then we give up, we don't know anything about it... // return an error. if (!*_retval) rv = NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const char *aFileExt, char **aContentType) { nsresult rv = NS_OK; nsCOMPtr info; rv = GetFromExtension(aFileExt, getter_AddRefs(info)); if (NS_FAILED(rv)) { /* Try the plugins */ const char* mimeType; nsCOMPtr pluginHost (do_GetService(kPluginManagerCID, &rv)); if (NS_SUCCEEDED(rv)) { if (pluginHost->IsPluginEnabledForExtension(aFileExt, mimeType) == NS_OK) { *aContentType = nsCRT::strdup(mimeType); rv = NS_OK; return rv; } else { rv = NS_ERROR_FAILURE; } } } if (NS_FAILED(rv)) { rv = GetMIMEInfoForExtensionFromExtras(aFileExt, getter_AddRefs(info)); } if (NS_FAILED(rv)) { return rv; } /* endif */ return info->GetMIMEType(aContentType); } NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, char **aContentType) { nsresult rv = NS_ERROR_FAILURE; // first try to get a url out of the uri so we can skip post // filename stuff (i.e. query string) nsCOMPtr url = do_QueryInterface(aURI, &rv); #ifdef XP_MAC if (NS_SUCCEEDED(rv)) { nsXPIDLCString fileExt; url->GetFileExtension(getter_Copies(fileExt)); nsresult rv2; nsCOMPtr fileurl = do_QueryInterface( url, &rv2 ); if ( NS_SUCCEEDED ( rv2 ) ) { nsCOMPtr file; rv2 = fileurl->GetFile( getter_AddRefs( file ) ); if ( NS_SUCCEEDED( rv2 ) ) { rv2 = GetTypeFromFile( file, aContentType ); if( NS_SUCCEEDED ( rv2 ) ) return rv2; } } } #endif if (NS_SUCCEEDED(rv)) { nsXPIDLCString ext; rv = url->GetFileExtension(getter_Copies(ext)); if (NS_FAILED(rv)) return rv; if (ext.IsEmpty()) { *aContentType = nsnull; return NS_ERROR_FAILURE; } rv = GetTypeFromExtension(ext, aContentType); return rv; } nsXPIDLCString cStrSpec; // no url, let's give the raw spec a shot rv = aURI->GetSpec(getter_Copies(cStrSpec)); if (NS_FAILED(rv)) return rv; nsCAutoString specStr(cStrSpec); // find the file extension (if any) nsCAutoString extStr; PRInt32 extLoc = specStr.RFindChar('.'); if (-1 != extLoc) { specStr.Right(extStr, specStr.Length() - extLoc - 1); rv = GetTypeFromExtension(extStr.get(), aContentType); } else return NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile( nsIFile* aFile, char **aContentType ) { nsresult rv; nsCOMPtr info; // Get the Extension char* fileName; const char* ext = nsnull; rv = aFile->GetLeafName(&fileName); if (NS_FAILED(rv)) return rv; if (fileName != nsnull) { PRInt32 len = nsCRT::strlen(fileName); for (PRInt32 i = len; i >= 0; i--) { if (fileName[i] == '.') { ext = &fileName[i + 1]; break; } } } nsCString fileExt( ext ); nsCRT::free(fileName); // Handle the mac case #ifdef XP_MAC nsCOMPtr macFile; macFile = do_QueryInterface( aFile, &rv ); if ( NS_SUCCEEDED( rv ) && fileExt.IsEmpty()) { PRUint32 type, creator; macFile->GetFileType( (OSType*)&type ); macFile->GetFileCreator( (OSType*)&creator ); nsCOMPtr icService (do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID)); if (icService) { rv = icService->GetMIMEInfoFromTypeCreator(type, creator, fileExt.get(), getter_AddRefs(info)); if ( NS_SUCCEEDED( rv) ) return info->GetMIMEType(aContentType); } } #endif // Windows, unix and mac when no type match occured. if (fileExt.IsEmpty()) return NS_ERROR_FAILURE; return GetTypeFromExtension( fileExt.get(), aContentType ); } nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromExtras(const char * aContentType, nsIMIMEInfo ** aMIMEInfo ) { nsresult rv = NS_OK; NS_ENSURE_ARG( aMIMEInfo ); *aMIMEInfo = 0; // Look for default entry with matching mime type. nsCAutoString MIMEType(aContentType); ToLowerCase(MIMEType); PRInt32 numEntries = sizeof(extraMimeEntries) / sizeof(extraMimeEntries[0]); for (PRInt32 index = 0; !*aMIMEInfo && index < numEntries; index++) { if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) ) { // This is the one. Create MIMEInfo object and set attributes appropriately. nsCOMPtr mimeInfo (do_CreateInstance(NS_MIMEINFO_CONTRACTID,&rv)); NS_ENSURE_SUCCESS( rv, rv ); mimeInfo->SetFileExtensions(extraMimeEntries[index].mFileExtensions); mimeInfo->SetMIMEType(extraMimeEntries[index].mMimeType); mimeInfo->SetDescription(NS_ConvertASCIItoUCS2(extraMimeEntries[index].mDescription).get()); mimeInfo->SetMacType(extraMimeEntries[index].mMactype); mimeInfo->SetMacCreator(extraMimeEntries[index].mMacCreator); *aMIMEInfo = mimeInfo; NS_ADDREF(*aMIMEInfo); } } return rv; } nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromExtras(const char* aExtension, nsIMIMEInfo ** aMIMEInfo ) { nsresult rv = NS_ERROR_NOT_AVAILABLE; NS_ENSURE_ARG( aMIMEInfo ); *aMIMEInfo = 0; // Look for default entry with matching extension. nsDependentCString extension(aExtension); nsDependentCString::const_iterator start, end, iter; PRInt32 numEntries = sizeof(extraMimeEntries) / sizeof(extraMimeEntries[0]); for (PRInt32 index = 0; !*aMIMEInfo && index < numEntries; index++) { nsDependentCString extList(extraMimeEntries[index].mFileExtensions); extList.BeginReading(start); extList.EndReading(end); iter = start; while (start != end) { FindCharInReadable(',', iter, end); if (Substring(start, iter).Equals(extension, nsCaseInsensitiveCStringComparator())) { // This is the one. Create MIMEInfo object and set // attributes appropriately. nsCOMPtr mimeInfo (do_CreateInstance(NS_MIMEINFO_CONTRACTID,&rv)); NS_ENSURE_SUCCESS( rv, rv ); mimeInfo->SetFileExtensions(extraMimeEntries[index].mFileExtensions); mimeInfo->SetMIMEType(extraMimeEntries[index].mMimeType); mimeInfo->SetDescription(NS_ConvertASCIItoUCS2(extraMimeEntries[index].mDescription).get()); mimeInfo->SetMacType(extraMimeEntries[index].mMactype); mimeInfo->SetMacCreator(extraMimeEntries[index].mMacCreator); *aMIMEInfo = mimeInfo; NS_ADDREF(*aMIMEInfo); break; } if (iter != end) { ++iter; } start = iter; } } return rv; } nsresult nsExternalHelperAppService::AddDefaultMimeTypesToCache() { PRInt32 numEntries = sizeof(defaultMimeEntries) / sizeof(defaultMimeEntries[0]); for (PRInt32 index = 0; index < numEntries; index++) { // create a mime info object for each default mime entry and add it to our cache nsCOMPtr mimeInfo (do_CreateInstance(NS_MIMEINFO_CONTRACTID)); mimeInfo->SetFileExtensions(defaultMimeEntries[index].mFileExtensions); mimeInfo->SetMIMEType(defaultMimeEntries[index].mMimeType); mimeInfo->SetDescription(NS_ConvertASCIItoUCS2(defaultMimeEntries[index].mDescription).get()); mimeInfo->SetMacType(defaultMimeEntries[index].mMactype); mimeInfo->SetMacCreator(defaultMimeEntries[index].mMacCreator); AddMimeInfoToCache(mimeInfo); } return NS_OK; } nsresult nsExternalHelperAppService::AddMimeInfoToCache(nsIMIMEInfo * aMIMEInfo) { NS_ENSURE_ARG(aMIMEInfo); nsresult rv = NS_OK; // Next add the new root MIME mapping. nsXPIDLCString mimeType; nsIMIMEInfo* oldInfo; rv = aMIMEInfo->GetMIMEType(getter_Copies(mimeType)); if (NS_SUCCEEDED(rv)) { nsCStringKey key(mimeType); oldInfo = (nsIMIMEInfo*)mMimeInfoCache->Put(&key, aMIMEInfo); // release the old info to prevent a leak NS_IF_RELEASE(oldInfo); // add a reference for the hash table entry.... NS_ADDREF(aMIMEInfo); } // now we need to add entries for each file extension char** extensions; PRUint32 count; rv = aMIMEInfo->GetFileExtensions(&count, &extensions ); if (NS_FAILED(rv)) return NS_OK; for ( PRUint32 i = 0; i < count; i++ ) { nsCStringKey key(extensions[i]); oldInfo = (nsIMIMEInfo*) mMimeInfoCache->Put(&key, aMIMEInfo); NS_IF_RELEASE(oldInfo); // release the old info to prevent a leak NS_ADDREF(aMIMEInfo); // addref this new entry in the table nsMemory::Free( extensions[i] ); } nsMemory::Free( extensions ); return NS_OK; } // static helper function to help us release hash table entries... static PRBool DeleteEntry(nsHashKey *aKey, void *aData, void* closure) { nsIMIMEInfo *entry = (nsIMIMEInfo*) aData; NS_RELEASE(entry); return PR_TRUE; };