michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim:expandtab:shiftwidth=2:tabstop=2:cin: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */ michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Base64.h" michael@0: michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/dom/TabChild.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #include "nsExternalHelperAppService.h" michael@0: #include "nsCExternalHandlerService.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIFileURL.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIDirectoryService.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIStringEnumerator.h" michael@0: #include "nsMemory.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIMIMEService.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsITransfer.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIRequest.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIMutableArray.h" michael@0: michael@0: // used to access our datastore of user-configured helper applications michael@0: #include "nsIHandlerService.h" michael@0: #include "nsIMIMEInfo.h" michael@0: #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI michael@0: #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri michael@0: #include "nsIHelperAppLauncherDialog.h" michael@0: #include "nsIContentDispatchChooser.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsChannelProperties.h" michael@0: michael@0: #include "nsMimeTypes.h" michael@0: // used for header disposition information. michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIEncodedChannel.h" michael@0: #include "nsIMultiPartChannel.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "nsIObserverService.h" // so we can be a profile change observer michael@0: #include "nsIPropertyBag2.h" // for the 64-bit content length michael@0: michael@0: #ifdef XP_MACOSX michael@0: #include "nsILocalFileMac.h" michael@0: #endif michael@0: michael@0: #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289) michael@0: #include "nsPluginHost.h" michael@0: #include "nsEscape.h" michael@0: michael@0: #include "nsIStringBundle.h" // XXX needed to localize error msgs michael@0: #include "nsIPrompt.h" michael@0: michael@0: #include "nsITextToSubURI.h" // to unescape the filename michael@0: #include "nsIMIMEHeaderParam.h" michael@0: michael@0: #include "nsIWindowWatcher.h" michael@0: michael@0: #include "nsIDownloadHistory.h" // to mark downloads as visited michael@0: #include "nsDocShellCID.h" michael@0: michael@0: #include "nsCRT.h" michael@0: #include "nsLocalHandlerApp.h" michael@0: michael@0: #include "nsIRandomGenerator.h" michael@0: michael@0: #include "ContentChild.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "ExternalHelperAppChild.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include "nsWindowsHelpers.h" michael@0: #endif michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: #include "AndroidBridge.h" michael@0: #endif michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/ipc/URIUtils.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "nsDeviceStorage.h" michael@0: #endif michael@0: michael@0: #ifdef NECKO_PROTOCOL_rtsp michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIMessageManager.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::ipc; michael@0: michael@0: // Download Folder location constants michael@0: #define NS_PREF_DOWNLOAD_DIR "browser.download.dir" michael@0: #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList" michael@0: enum { michael@0: NS_FOLDER_VALUE_DESKTOP = 0 michael@0: , NS_FOLDER_VALUE_DOWNLOADS = 1 michael@0: , NS_FOLDER_VALUE_CUSTOM = 2 michael@0: }; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr; michael@0: #endif michael@0: michael@0: // Using level 3 here because the OSHelperAppServices use a log level michael@0: // of PR_LOG_DEBUG (4), and we want less detailed output here michael@0: // Using 3 instead of PR_LOG_WARN because we don't output warnings michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3) michael@0: michael@0: static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] = michael@0: "browser.helperApps.neverAsk.saveToDisk"; michael@0: static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] = michael@0: "browser.helperApps.neverAsk.openFile"; michael@0: michael@0: // Helper functions for Content-Disposition headers michael@0: michael@0: /** michael@0: * Given a URI fragment, unescape it michael@0: * @param aFragment The string to unescape michael@0: * @param aURI The URI from which this fragment is taken. Only its character set michael@0: * will be used. michael@0: * @param aResult [out] Unescaped string. michael@0: */ michael@0: static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI, michael@0: nsAString& aResult) michael@0: { michael@0: // First, we need a charset michael@0: nsAutoCString originCharset; michael@0: nsresult rv = aURI->GetOriginCharset(originCharset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now, we need the unescaper michael@0: nsCOMPtr textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult); michael@0: } michael@0: michael@0: /** michael@0: * UTF-8 version of UnescapeFragment. michael@0: * @param aFragment The string to unescape michael@0: * @param aURI The URI from which this fragment is taken. Only its character set michael@0: * will be used. michael@0: * @param aResult [out] Unescaped string, UTF-8 encoded. michael@0: * @note It is safe to pass the same string for aFragment and aResult. michael@0: * @note When this function fails, aResult will not be modified. michael@0: */ michael@0: static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI, michael@0: nsACString& aResult) michael@0: { michael@0: nsAutoString result; michael@0: nsresult rv = UnescapeFragment(aFragment, aURI, result); michael@0: if (NS_SUCCEEDED(rv)) michael@0: CopyUTF16toUTF8(result, aResult); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Given a channel, returns the filename and extension the channel has. michael@0: * This uses the URL and other sources (nsIMultiPartChannel). michael@0: * Also gives back whether the channel requested external handling (i.e. michael@0: * whether Content-Disposition: attachment was sent) michael@0: * @param aChannel The channel to extract the filename/extension from michael@0: * @param aFileName [out] Reference to the string where the filename should be michael@0: * stored. Empty if it could not be retrieved. michael@0: * WARNING - this filename may contain characters which the OS does not michael@0: * allow as part of filenames! michael@0: * @param aExtension [out] Reference to the string where the extension should michael@0: * be stored. Empty if it could not be retrieved. Stored in UTF-8. michael@0: * @param aAllowURLExtension (optional) Get the extension from the URL if no michael@0: * Content-Disposition header is present. Default is true. michael@0: * @retval true The server sent Content-Disposition:attachment or equivalent michael@0: * @retval false Content-Disposition: inline or no content-disposition header michael@0: * was sent. michael@0: */ michael@0: static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel, michael@0: nsString& aFileName, michael@0: nsCString& aExtension, michael@0: bool aAllowURLExtension = true) michael@0: { michael@0: aExtension.Truncate(); michael@0: /* michael@0: * If the channel is an http or part of a multipart channel and we michael@0: * have a content disposition header set, then use the file name michael@0: * suggested there as the preferred file name to SUGGEST to the michael@0: * user. we shouldn't actually use that without their michael@0: * permission... otherwise just use our temp file michael@0: */ michael@0: bool handleExternally = false; michael@0: uint32_t disp; michael@0: nsresult rv = aChannel->GetContentDisposition(&disp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: aChannel->GetContentDispositionFilename(aFileName); michael@0: if (disp == nsIChannel::DISPOSITION_ATTACHMENT) michael@0: handleExternally = true; michael@0: } michael@0: michael@0: // If the disposition header didn't work, try the filename from nsIURL michael@0: nsCOMPtr uri; michael@0: aChannel->GetURI(getter_AddRefs(uri)); michael@0: nsCOMPtr url(do_QueryInterface(uri)); michael@0: if (url && aFileName.IsEmpty()) michael@0: { michael@0: if (aAllowURLExtension) { michael@0: url->GetFileExtension(aExtension); michael@0: UnescapeFragment(aExtension, url, aExtension); michael@0: michael@0: // Windows ignores terminating dots. So we have to as well, so michael@0: // that our security checks do "the right thing" michael@0: // In case the aExtension consisted only of the dot, the code below will michael@0: // extract an aExtension from the filename michael@0: aExtension.Trim(".", false); michael@0: } michael@0: michael@0: // try to extract the file name from the url and use that as a first pass as the michael@0: // leaf name of our temp file... michael@0: nsAutoCString leafName; michael@0: url->GetFileName(leafName); michael@0: if (!leafName.IsEmpty()) michael@0: { michael@0: rv = UnescapeFragment(leafName, url, aFileName); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: CopyUTF8toUTF16(leafName, aFileName); // use escaped name michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Extract Extension, if we have a filename; otherwise, michael@0: // truncate the string michael@0: if (aExtension.IsEmpty()) { michael@0: if (!aFileName.IsEmpty()) michael@0: { michael@0: // Windows ignores terminating dots. So we have to as well, so michael@0: // that our security checks do "the right thing" michael@0: aFileName.Trim(".", false); michael@0: michael@0: // XXX RFindCharInReadable!! michael@0: nsAutoString fileNameStr(aFileName); michael@0: int32_t idx = fileNameStr.RFindChar(char16_t('.')); michael@0: if (idx != kNotFound) michael@0: CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension); michael@0: } michael@0: } michael@0: michael@0: michael@0: return handleExternally; michael@0: } michael@0: michael@0: /** michael@0: * Obtains the directory to use. This tends to vary per platform, and michael@0: * needs to be consistent throughout our codepaths. For platforms where michael@0: * helper apps use the downloads directory, this should be kept in michael@0: * sync with nsDownloadManager.cpp michael@0: * michael@0: * Optionally skip availability of the directory and storage. michael@0: */ michael@0: static nsresult GetDownloadDirectory(nsIFile **_directory, michael@0: bool aSkipChecks = false) michael@0: { michael@0: nsCOMPtr dir; michael@0: #ifdef XP_MACOSX michael@0: // On OS X, we first try to get the users download location, if it's set. michael@0: switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) { michael@0: case NS_FOLDER_VALUE_DESKTOP: michael@0: (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir)); michael@0: break; michael@0: case NS_FOLDER_VALUE_CUSTOM: michael@0: { michael@0: Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, michael@0: NS_GET_IID(nsIFile), michael@0: getter_AddRefs(dir)); michael@0: if (!dir) break; michael@0: michael@0: // If we're not checking for availability we're done. michael@0: if (aSkipChecks) { michael@0: dir.forget(_directory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We have the directory, and now we need to make sure it exists michael@0: bool dirExists = false; michael@0: (void) dir->Exists(&dirExists); michael@0: if (dirExists) break; michael@0: michael@0: nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: if (NS_FAILED(rv)) { michael@0: dir = nullptr; michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: case NS_FOLDER_VALUE_DOWNLOADS: michael@0: // This is just the OS default location, so fall out michael@0: break; michael@0: } michael@0: michael@0: if (!dir) { michael@0: // If not, we default to the OS X default download location. michael@0: nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR, michael@0: getter_AddRefs(dir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: #elif defined(MOZ_WIDGET_GONK) michael@0: // On Gonk, store the files on the sdcard in the downloads directory. michael@0: // We need to check with the volume manager which storage point is michael@0: // available. michael@0: michael@0: // Pick the default storage in case multiple (internal and external) ones michael@0: // are available. michael@0: nsString storageName; michael@0: nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"), michael@0: storageName); michael@0: michael@0: DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"), michael@0: storageName, michael@0: NS_LITERAL_STRING("downloads")); michael@0: NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED); michael@0: michael@0: // If we're not checking for availability we're done. michael@0: if (aSkipChecks) { michael@0: dsf.mFile.forget(_directory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Check device storage status before continuing. michael@0: nsString storageStatus; michael@0: dsf.GetStatus(storageStatus); michael@0: michael@0: // If we get an "unavailable" status, it means the sd card is not present. michael@0: // We'll also catch internal errors by looking for an empty string and assume michael@0: // the SD card isn't present when this occurs. michael@0: if (storageStatus.EqualsLiteral("unavailable") || michael@0: storageStatus.IsEmpty()) { michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: // If we get a status other than 'available' here it means the card is busy michael@0: // because it's mounted via USB or it is being formatted. michael@0: if (!storageStatus.EqualsLiteral("available")) { michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: } michael@0: michael@0: bool alreadyThere; michael@0: nsresult rv = dsf.mFile->Exists(&alreadyThere); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!alreadyThere) { michael@0: rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: dir = dsf.mFile; michael@0: #elif defined(ANDROID) michael@0: // On mobile devices, we are avoiding exposing users to the file michael@0: // system, and don't save downloads to temp directories michael@0: michael@0: // On Android we only return something if we have and SD-card michael@0: char* downloadDir = getenv("DOWNLOADS_DIRECTORY"); michael@0: nsresult rv; michael@0: if (downloadDir) { michael@0: nsCOMPtr ldir; michael@0: rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir), michael@0: true, getter_AddRefs(ldir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: dir = do_QueryInterface(ldir); michael@0: michael@0: // If we're not checking for availability we're done. michael@0: if (aSkipChecks) { michael@0: dir.forget(_directory); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: else { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #elif defined(XP_WIN) michael@0: // On metro we want to be able to search opened files and the temp directory michael@0: // is exlcuded in searches. michael@0: nsresult rv; michael@0: if (IsRunningInWindowsMetro()) { michael@0: rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir)); michael@0: } else { michael@0: rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #else michael@0: // On all other platforms, we default to the systems temporary directory. michael@0: nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #endif michael@0: michael@0: NS_ASSERTION(dir, "Somehow we didn't get a download directory!"); michael@0: dir.forget(_directory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Structure for storing extension->type mappings. michael@0: * @see defaultMimeEntries michael@0: */ michael@0: struct nsDefaultMimeTypeEntry { michael@0: const char* mMimeType; michael@0: const char* mFileExtension; michael@0: }; michael@0: michael@0: /** michael@0: * Default extension->mimetype mappings. These are not overridable. michael@0: * If you add types here, make sure they are lowercase, or you'll regret it. michael@0: */ michael@0: static nsDefaultMimeTypeEntry defaultMimeEntries [] = michael@0: { michael@0: // The following are those extensions that we're asked about during startup, michael@0: // sorted by order used michael@0: { IMAGE_GIF, "gif" }, michael@0: { TEXT_XML, "xml" }, michael@0: { APPLICATION_RDF, "rdf" }, michael@0: { TEXT_XUL, "xul" }, michael@0: { IMAGE_PNG, "png" }, michael@0: // -- end extensions used during startup michael@0: { TEXT_CSS, "css" }, michael@0: { IMAGE_JPEG, "jpeg" }, michael@0: { IMAGE_JPEG, "jpg" }, michael@0: { IMAGE_SVG_XML, "svg" }, michael@0: { TEXT_HTML, "html" }, michael@0: { TEXT_HTML, "htm" }, michael@0: { APPLICATION_XPINSTALL, "xpi" }, michael@0: { "application/xhtml+xml", "xhtml" }, michael@0: { "application/xhtml+xml", "xht" }, michael@0: { TEXT_PLAIN, "txt" }, michael@0: { VIDEO_OGG, "ogv" }, michael@0: { VIDEO_OGG, "ogg" }, michael@0: { APPLICATION_OGG, "ogg" }, michael@0: { AUDIO_OGG, "oga" }, michael@0: #ifdef MOZ_OPUS michael@0: { AUDIO_OGG, "opus" }, michael@0: #endif michael@0: #ifdef MOZ_WEBM michael@0: { VIDEO_WEBM, "webm" }, michael@0: { AUDIO_WEBM, "webm" }, michael@0: #endif michael@0: #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF) michael@0: { VIDEO_MP4, "mp4" }, michael@0: { AUDIO_MP4, "m4a" }, michael@0: { AUDIO_MP3, "mp3" }, michael@0: #endif michael@0: #ifdef MOZ_RAW michael@0: { VIDEO_RAW, "yuv" } michael@0: #endif michael@0: }; michael@0: michael@0: /** michael@0: * This is a small private struct used to help us initialize some michael@0: * default mime types. michael@0: */ michael@0: struct nsExtraMimeTypeEntry { michael@0: const char* mMimeType; michael@0: const char* mFileExtensions; michael@0: const char* mDescription; michael@0: }; michael@0: michael@0: #ifdef XP_MACOSX michael@0: #define MAC_TYPE(x) x michael@0: #else michael@0: #define MAC_TYPE(x) 0 michael@0: #endif michael@0: michael@0: /** michael@0: * This table lists all of the 'extra' content types that we can deduce from particular michael@0: * file extensions. These entries also ensure that we provide a good descriptive name michael@0: * when we encounter files with these content types and/or extensions. These can be michael@0: * overridden by user helper app prefs. michael@0: * If you add types here, make sure they are lowercase, or you'll regret it. michael@0: */ michael@0: static nsExtraMimeTypeEntry extraMimeEntries [] = michael@0: { michael@0: #if defined(VMS) michael@0: { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" }, michael@0: #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up... michael@0: { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" }, michael@0: #else michael@0: { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" }, michael@0: #endif michael@0: { APPLICATION_GZIP2, "gz", "gzip" }, michael@0: { "application/x-arj", "arj", "ARJ file" }, michael@0: { "application/rtf", "rtf", "Rich Text Format File" }, michael@0: { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" }, michael@0: { APPLICATION_PDF, "pdf", "Portable Document Format" }, michael@0: { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" }, michael@0: { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" }, michael@0: { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" }, michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: { "application/vnd.android.package-archive", "apk", "Android Package" }, michael@0: #endif michael@0: { IMAGE_ART, "art", "ART Image" }, michael@0: { IMAGE_BMP, "bmp", "BMP Image" }, michael@0: { IMAGE_GIF, "gif", "GIF Image" }, michael@0: { IMAGE_ICO, "ico,cur", "ICO Image" }, michael@0: { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" }, michael@0: { IMAGE_PNG, "png", "PNG Image" }, michael@0: { IMAGE_TIFF, "tiff,tif", "TIFF Image" }, michael@0: { IMAGE_XBM, "xbm", "XBM Image" }, michael@0: { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" }, michael@0: { MESSAGE_RFC822, "eml", "RFC-822 data" }, michael@0: { TEXT_PLAIN, "txt,text", "Text File" }, michael@0: { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" }, michael@0: { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" }, michael@0: { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" }, michael@0: { APPLICATION_RDF, "rdf", "Resource Description Framework" }, michael@0: { TEXT_XUL, "xul", "XML-Based User Interface Language" }, michael@0: { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" }, michael@0: { TEXT_CSS, "css", "Style Sheet" }, michael@0: { TEXT_VCARD, "vcf,vcard", "Contact Information" }, michael@0: { VIDEO_OGG, "ogv", "Ogg Video" }, michael@0: { VIDEO_OGG, "ogg", "Ogg Video" }, michael@0: { APPLICATION_OGG, "ogg", "Ogg Video"}, michael@0: { AUDIO_OGG, "oga", "Ogg Audio" }, michael@0: { AUDIO_OGG, "opus", "Opus Audio" }, michael@0: #ifdef MOZ_WIDGET_GONK michael@0: { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" }, michael@0: #endif michael@0: { VIDEO_WEBM, "webm", "Web Media Video" }, michael@0: { AUDIO_WEBM, "webm", "Web Media Audio" }, michael@0: { AUDIO_MP3, "mp3", "MPEG Audio" }, michael@0: { VIDEO_MP4, "mp4", "MPEG-4 Video" }, michael@0: { AUDIO_MP4, "m4a", "MPEG-4 Audio" }, michael@0: { VIDEO_RAW, "yuv", "Raw YUV Video" }, michael@0: { AUDIO_WAV, "wav", "Waveform Audio" }, michael@0: { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" }, michael@0: { AUDIO_MIDI, "mid", "Standard MIDI Audio" } michael@0: }; michael@0: michael@0: #undef MAC_TYPE michael@0: michael@0: /** michael@0: * File extensions for which decoding should be disabled. michael@0: * NOTE: These MUST be lower-case and ASCII. michael@0: */ michael@0: static nsDefaultMimeTypeEntry nonDecodableExtensions [] = { michael@0: { APPLICATION_GZIP, "gz" }, michael@0: { APPLICATION_GZIP, "tgz" }, michael@0: { APPLICATION_ZIP, "zip" }, michael@0: { APPLICATION_COMPRESS, "z" }, michael@0: { APPLICATION_GZIP, "svgz" } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: nsExternalHelperAppService, michael@0: nsIExternalHelperAppService, michael@0: nsPIExternalAppLauncher, michael@0: nsIExternalProtocolService, michael@0: nsIMIMEService, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: nsExternalHelperAppService::nsExternalHelperAppService() michael@0: { michael@0: } michael@0: nsresult nsExternalHelperAppService::Init() michael@0: { michael@0: // Add an observer for profile change michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (!obs) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!mLog) { michael@0: mLog = PR_NewLogModule("HelperAppService"); michael@0: if (!mLog) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: #endif michael@0: michael@0: nsresult rv = obs->AddObserver(this, "profile-before-change", true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return obs->AddObserver(this, "last-pb-context-exited", true); michael@0: } michael@0: michael@0: nsExternalHelperAppService::~nsExternalHelperAppService() michael@0: { michael@0: } michael@0: michael@0: #ifdef NECKO_PROTOCOL_rtsp michael@0: namespace { michael@0: /** michael@0: * A stack helper to clear the currently pending exception in a JS context. michael@0: */ michael@0: class AutoClearPendingException { michael@0: public: michael@0: AutoClearPendingException(JSContext* aCx) : michael@0: mCx(aCx) { michael@0: } michael@0: ~AutoClearPendingException() { michael@0: JS_ClearPendingException(mCx); michael@0: } michael@0: private: michael@0: JSContext *mCx; michael@0: }; michael@0: } // anonymous namespace michael@0: michael@0: /** michael@0: * This function sends a message. This 'content-handler' message is handled in michael@0: * b2g/chrome/content/shell.js where it starts an activity request that will michael@0: * open the video app. michael@0: */ michael@0: void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI) michael@0: { michael@0: bool rv; michael@0: michael@0: // Get a system principal. michael@0: nsCOMPtr securityManager = michael@0: do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE_VOID(securityManager); michael@0: michael@0: nsCOMPtr principal; michael@0: securityManager->GetSystemPrincipal(getter_AddRefs(principal)); michael@0: NS_ENSURE_TRUE_VOID(principal); michael@0: michael@0: // Construct the message in jsVal format. michael@0: AutoSafeJSContext cx; michael@0: AutoClearPendingException helper(cx); michael@0: JS::Rooted msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: NS_ENSURE_TRUE_VOID(msgObj); michael@0: JS::Rooted jsVal(cx); michael@0: michael@0: // Set the "type" property of the message. This is a fake MIME type. michael@0: { michael@0: NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp"); michael@0: JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length()); michael@0: NS_ENSURE_TRUE_VOID(typeStr); michael@0: jsVal.setString(typeStr); michael@0: rv = JS_SetProperty(cx, msgObj, "type", jsVal); michael@0: NS_ENSURE_TRUE_VOID(rv); michael@0: } michael@0: // Set the "url" and "title" properties of the message. michael@0: // They are the same in the case of RTSP streaming. michael@0: { michael@0: nsAutoCString spec; michael@0: aURI->GetSpec(spec); michael@0: JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length()); michael@0: NS_ENSURE_TRUE_VOID(urlStr); michael@0: jsVal.setString(urlStr); michael@0: rv = JS_SetProperty(cx, msgObj, "url", jsVal); michael@0: NS_ENSURE_TRUE_VOID(rv); michael@0: rv = JS_SetProperty(cx, msgObj, "title", jsVal); michael@0: } michael@0: jsVal.setObject(*msgObj); michael@0: michael@0: // Send the message. michael@0: nsCOMPtr cpmm = michael@0: do_GetService("@mozilla.org/childprocessmessagemanager;1"); michael@0: NS_ENSURE_TRUE_VOID(cpmm); michael@0: cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"), michael@0: jsVal, JS::NullHandleValue, principal, cx, 2); michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType, michael@0: nsIRequest *aRequest, michael@0: nsIInterfaceRequestor *aWindowContext, michael@0: bool aForceSave, michael@0: nsIStreamListener ** aStreamListener) michael@0: { michael@0: nsAutoString fileName; michael@0: nsAutoCString fileExtension; michael@0: uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE; michael@0: uint32_t contentDisposition = -1; michael@0: michael@0: nsresult rv; michael@0: michael@0: // Get the file extension and name that we will need later michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: nsCOMPtr uri; michael@0: int64_t contentLength = -1; michael@0: if (channel) { michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: channel->GetContentLength(&contentLength); michael@0: channel->GetContentDisposition(&contentDisposition); michael@0: channel->GetContentDispositionFilename(fileName); michael@0: } michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: nsCOMPtr window = do_GetInterface(aWindowContext); michael@0: NS_ENSURE_STATE(window); michael@0: michael@0: // We need to get a hold of a ContentChild so that we can begin forwarding michael@0: // this data to the parent. In the HTTP case, this is unfortunate, since michael@0: // we're actually passing data from parent->child->parent wastefully, but michael@0: // the Right Fix will eventually be to short-circuit those channels on the michael@0: // parent side based on some sort of subscription concept. michael@0: using mozilla::dom::ContentChild; michael@0: using mozilla::dom::ExternalHelperAppChild; michael@0: ContentChild *child = ContentChild::GetSingleton(); michael@0: if (!child) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCString disp; michael@0: if (channel) { michael@0: channel->GetContentDispositionHeader(disp); michael@0: } michael@0: michael@0: nsCOMPtr referrer; michael@0: rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer)); michael@0: michael@0: OptionalURIParams uriParams, referrerParams; michael@0: SerializeURI(uri, uriParams); michael@0: SerializeURI(referrer, referrerParams); michael@0: michael@0: // Now we build a protocol for forwarding our data to the parent. The michael@0: // protocol will act as a listener on the child-side and create a "real" michael@0: // helperAppService listener on the parent-side, via another call to michael@0: // DoContent. michael@0: mozilla::dom::PExternalHelperAppChild *pc = michael@0: child->SendPExternalHelperAppConstructor(uriParams, michael@0: nsCString(aMimeContentType), michael@0: disp, contentDisposition, michael@0: fileName, aForceSave, michael@0: contentLength, referrerParams, michael@0: mozilla::dom::TabChild::GetFrom(window)); michael@0: ExternalHelperAppChild *childListener = static_cast(pc); michael@0: michael@0: NS_ADDREF(*aStreamListener = childListener); michael@0: michael@0: nsRefPtr handler = michael@0: new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this, michael@0: fileName, michael@0: reason, aForceSave); michael@0: if (!handler) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: childListener->SetHandler(handler); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (channel) { michael@0: // Check if we have a POST request, in which case we don't want to use michael@0: // the url's extension michael@0: bool allowURLExt = true; michael@0: nsCOMPtr httpChan = do_QueryInterface(channel); michael@0: if (httpChan) { michael@0: nsAutoCString requestMethod; michael@0: httpChan->GetRequestMethod(requestMethod); michael@0: allowURLExt = !requestMethod.Equals("POST"); michael@0: } michael@0: michael@0: // Check if we had a query string - we don't want to check the URL michael@0: // extension if a query is present in the URI michael@0: // If we already know we don't want to check the URL extension, don't michael@0: // bother checking the query michael@0: if (uri && allowURLExt) { michael@0: nsCOMPtr url = do_QueryInterface(uri); michael@0: michael@0: if (url) { michael@0: nsAutoCString query; michael@0: michael@0: // We only care about the query for HTTP and HTTPS URLs michael@0: bool isHTTP, isHTTPS; michael@0: rv = uri->SchemeIs("http", &isHTTP); michael@0: if (NS_FAILED(rv)) michael@0: isHTTP = false; michael@0: rv = uri->SchemeIs("https", &isHTTPS); michael@0: if (NS_FAILED(rv)) michael@0: isHTTPS = false; michael@0: michael@0: if (isHTTP || isHTTPS) michael@0: url->GetQuery(query); michael@0: michael@0: // Only get the extension if the query is empty; if it isn't, then the michael@0: // extension likely belongs to a cgi script and isn't helpful michael@0: allowURLExt = query.IsEmpty(); michael@0: } michael@0: } michael@0: // Extract name & extension michael@0: bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName, michael@0: fileExtension, michael@0: allowURLExt); michael@0: LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)", michael@0: fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(), michael@0: isAttachment)); michael@0: if (isAttachment) michael@0: reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST; michael@0: } michael@0: michael@0: LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n", michael@0: PromiseFlatCString(aMimeContentType).get(), fileExtension.get())); michael@0: michael@0: // we get the mime service here even though we're the default implementation of it, michael@0: // so it's possible to override only the mime service and not need to reimplement the michael@0: // whole external helper app service itself michael@0: nsCOMPtr mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID)); michael@0: NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE); michael@0: michael@0: // Try to find a mime object by looking at the mime type/extension michael@0: nsCOMPtr mimeInfo; michael@0: if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) { michael@0: nsAutoCString mimeType; michael@0: if (!fileExtension.IsEmpty()) { michael@0: mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo)); michael@0: if (mimeInfo) { michael@0: mimeInfo->GetMIMEType(mimeType); michael@0: michael@0: LOG(("OS-Provided mime type '%s' for extension '%s'\n", michael@0: mimeType.get(), fileExtension.get())); michael@0: } michael@0: } michael@0: michael@0: if (fileExtension.IsEmpty() || mimeType.IsEmpty()) { michael@0: // Extension lookup gave us no useful match michael@0: mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension, michael@0: getter_AddRefs(mimeInfo)); michael@0: mimeType.AssignLiteral(APPLICATION_OCTET_STREAM); michael@0: } michael@0: if (channel) michael@0: channel->SetContentType(mimeType); michael@0: // Don't overwrite SERVERREQUEST michael@0: if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) michael@0: reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED; michael@0: } michael@0: else { michael@0: mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension, michael@0: getter_AddRefs(mimeInfo)); michael@0: } michael@0: LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get())); michael@0: michael@0: // No mimeinfo -> we can't continue. probably OOM. michael@0: if (!mimeInfo) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: *aStreamListener = nullptr; michael@0: // We want the mimeInfo's primary extension to pass it to michael@0: // nsExternalAppHandler michael@0: nsAutoCString buf; michael@0: mimeInfo->GetPrimaryExtension(buf); michael@0: michael@0: nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo, michael@0: buf, michael@0: aWindowContext, michael@0: this, michael@0: fileName, michael@0: reason, michael@0: aForceSave); michael@0: if (!handler) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*aStreamListener = handler); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension, michael@0: const nsACString& aEncodingType, michael@0: bool *aApplyDecoding) michael@0: { michael@0: *aApplyDecoding = true; michael@0: uint32_t i; michael@0: for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) { michael@0: if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) && michael@0: aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) { michael@0: *aApplyDecoding = false; michael@0: break; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath, michael@0: nsIFile ** aFile) michael@0: { michael@0: nsDependentString platformAppPath(aPlatformAppPath); michael@0: // First, check if we have an absolute path michael@0: nsIFile* localFile = nullptr; michael@0: nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *aFile = localFile; michael@0: bool exists; michael@0: if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) { michael@0: NS_RELEASE(*aFile); michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Second, check if file exists in mozilla program directory michael@0: rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = (*aFile)->Append(platformAppPath); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool exists = false; michael@0: rv = (*aFile)->Exists(&exists); michael@0: if (NS_SUCCEEDED(rv) && exists) michael@0: return NS_OK; michael@0: } michael@0: NS_RELEASE(*aFile); michael@0: } michael@0: michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: // begin external protocol service default implementation... michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, michael@0: bool * aHandlerExists) michael@0: { michael@0: nsCOMPtr handlerInfo; michael@0: nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme), michael@0: getter_AddRefs(handlerInfo)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // See if we have any known possible handler apps for this michael@0: nsCOMPtr possibleHandlers; michael@0: handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers)); michael@0: michael@0: uint32_t length; michael@0: possibleHandlers->GetLength(&length); michael@0: if (length) { michael@0: *aHandlerExists = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if not, fall back on an os-based handler michael@0: return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult) michael@0: { michael@0: // check the per protocol setting first. it always takes precedence. michael@0: // if not set, then use the global setting. michael@0: michael@0: nsAutoCString prefName("network.protocol-handler.expose."); michael@0: prefName += aProtocolScheme; michael@0: bool val; michael@0: if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) { michael@0: *aResult = val; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // by default, no protocol is exposed. i.e., by default all link clicks must michael@0: // go through the external protocol service. most applications override this michael@0: // default behavior. michael@0: *aResult = michael@0: Preferences::GetBool("network.protocol-handler.expose-all", false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL) michael@0: { michael@0: return LoadURI(aURL, nullptr); michael@0: } michael@0: michael@0: static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external."; michael@0: static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default"; michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::LoadURI(nsIURI *aURI, michael@0: nsIInterfaceRequestor *aWindowContext) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: URIParams uri; michael@0: SerializeURI(aURI, uri); michael@0: michael@0: mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString spec; michael@0: aURI->GetSpec(spec); michael@0: michael@0: if (spec.Find("%00") != -1) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: spec.ReplaceSubstring("\"", "%22"); michael@0: spec.ReplaceSubstring("`", "%60"); michael@0: michael@0: nsCOMPtr ios(do_GetIOService()); michael@0: nsCOMPtr uri; michael@0: nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString scheme; michael@0: uri->GetScheme(scheme); michael@0: if (scheme.IsEmpty()) michael@0: return NS_OK; // must have a scheme michael@0: michael@0: // Deny load if the prefs say to do so michael@0: nsAutoCString externalPref(kExternalProtocolPrefPrefix); michael@0: externalPref += scheme; michael@0: bool allowLoad = false; michael@0: if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) { michael@0: // no scheme-specific value, check the default michael@0: if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref, michael@0: &allowLoad))) { michael@0: return NS_OK; // missing default pref michael@0: } michael@0: } michael@0: michael@0: if (!allowLoad) { michael@0: return NS_OK; // explicitly denied michael@0: } michael@0: michael@0: #ifdef NECKO_PROTOCOL_rtsp michael@0: // Handle rtsp protocol. michael@0: { michael@0: bool isRTSP = false; michael@0: rv = aURI->SchemeIs("rtsp", &isRTSP); michael@0: if (NS_SUCCEEDED(rv) && isRTSP) { michael@0: LaunchVideoAppForRtsp(aURI); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr handler; michael@0: rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsHandlerInfoAction preferredAction; michael@0: handler->GetPreferredAction(&preferredAction); michael@0: bool alwaysAsk = true; michael@0: handler->GetAlwaysAskBeforeHandling(&alwaysAsk); michael@0: michael@0: // if we are not supposed to ask, and the preferred action is to use michael@0: // a helper app or the system default, we just launch the URI. michael@0: if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp || michael@0: preferredAction == nsIHandlerInfo::useSystemDefault)) michael@0: return handler->LaunchWithURI(uri, aWindowContext); michael@0: michael@0: nsCOMPtr chooser = michael@0: do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return chooser->Ask(handler, aWindowContext, uri, michael@0: nsIContentDispatchChooser::REASON_CANNOT_HANDLE); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval) michael@0: { michael@0: // this method should only be implemented by each OS specific implementation of this service. michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: // Methods related to deleting temporary files on exit michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile, michael@0: nsCOMArray &aFileList) michael@0: { michael@0: bool isFile = false; michael@0: michael@0: // as a safety measure, make sure the nsIFile is really a file and not a directory object. michael@0: aTemporaryFile->IsFile(&isFile); michael@0: if (!isFile) return NS_OK; michael@0: michael@0: aFileList.AppendObject(aTemporaryFile); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) michael@0: { michael@0: return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile) michael@0: { michael@0: return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList); michael@0: } michael@0: michael@0: void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray &fileList) michael@0: { michael@0: int32_t numEntries = fileList.Count(); michael@0: nsIFile* localFile; michael@0: for (int32_t index = 0; index < numEntries; index++) michael@0: { michael@0: localFile = fileList[index]; michael@0: if (localFile) { michael@0: // First make the file writable, since the temp file is probably readonly. michael@0: localFile->SetPermissions(0600); michael@0: localFile->Remove(false); michael@0: } michael@0: } michael@0: michael@0: fileList.Clear(); michael@0: } michael@0: michael@0: void nsExternalHelperAppService::ExpungeTemporaryFiles() michael@0: { michael@0: ExpungeTemporaryFilesHelper(mTemporaryFilesList); michael@0: } michael@0: michael@0: void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() michael@0: { michael@0: ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList); michael@0: } michael@0: michael@0: static const char kExternalWarningPrefPrefix[] = michael@0: "network.protocol-handler.warn-external."; michael@0: static const char kExternalWarningDefaultPref[] = michael@0: "network.protocol-handler.warn-external-default"; michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme, michael@0: nsIHandlerInfo **aHandlerInfo) michael@0: { michael@0: // XXX enterprise customers should be able to turn this support off with a michael@0: // single master pref (maybe use one of the "exposed" prefs here?) michael@0: michael@0: bool exists; michael@0: nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo); michael@0: if (NS_FAILED(rv)) { michael@0: // Either it knows nothing, or we ran out of memory michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); michael@0: if (handlerSvc) { michael@0: bool hasHandler = false; michael@0: (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler); michael@0: if (hasHandler) { michael@0: rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString()); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return SetProtocolHandlerDefaults(*aHandlerInfo, exists); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme, michael@0: bool *found, michael@0: nsIHandlerInfo **aHandlerInfo) michael@0: { michael@0: // intended to be implemented by the subclass michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo, michael@0: bool aOSHandlerExists) michael@0: { michael@0: // this type isn't in our database, so we've only got an OS default handler, michael@0: // if one exists michael@0: michael@0: if (aOSHandlerExists) { michael@0: // we've got a default, so use it michael@0: aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault); michael@0: michael@0: // whether or not to ask the user depends on the warning preference michael@0: nsAutoCString scheme; michael@0: aHandlerInfo->GetType(scheme); michael@0: michael@0: nsAutoCString warningPref(kExternalWarningPrefPrefix); michael@0: warningPref += scheme; michael@0: bool warn; michael@0: if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) { michael@0: // no scheme-specific value, check the default michael@0: warn = Preferences::GetBool(kExternalWarningDefaultPref, true); michael@0: } michael@0: aHandlerInfo->SetAlwaysAskBeforeHandling(warn); michael@0: } else { michael@0: // If no OS default existed, we set the preferred action to alwaysAsk. michael@0: // This really means not initialized (i.e. there's no available handler) michael@0: // to all the code... michael@0: aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XPCOM profile change observer michael@0: NS_IMETHODIMP michael@0: nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData ) michael@0: { michael@0: if (!strcmp(aTopic, "profile-before-change")) { michael@0: ExpungeTemporaryFiles(); michael@0: } else if (!strcmp(aTopic, "last-pb-context-exited")) { michael@0: ExpungeTemporaryPrivateFiles(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: // begin external app handler implementation michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: NS_IMPL_ADDREF(nsExternalAppHandler) michael@0: NS_IMPL_RELEASE(nsExternalAppHandler) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher) michael@0: NS_INTERFACE_MAP_ENTRY(nsICancelable) michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, michael@0: const nsCSubstring& aTempFileExtension, michael@0: nsIInterfaceRequestor* aWindowContext, michael@0: nsExternalHelperAppService *aExtProtSvc, michael@0: const nsAString& aSuggestedFilename, michael@0: uint32_t aReason, bool aForceSave) michael@0: : mMimeInfo(aMIMEInfo) michael@0: , mWindowContext(aWindowContext) michael@0: , mWindowToClose(nullptr) michael@0: , mSuggestedFileName(aSuggestedFilename) michael@0: , mForceSave(aForceSave) michael@0: , mCanceled(false) michael@0: , mShouldCloseWindow(false) michael@0: , mStopRequestIssued(false) michael@0: , mReason(aReason) michael@0: , mContentLength(-1) michael@0: , mProgress(0) michael@0: , mSaver(nullptr) michael@0: , mDialogProgressListener(nullptr) michael@0: , mTransfer(nullptr) michael@0: , mRequest(nullptr) michael@0: , mExtProtSvc(aExtProtSvc) michael@0: { michael@0: michael@0: // make sure the extention includes the '.' michael@0: if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.') michael@0: mTempFileExtension = char16_t('.'); michael@0: AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension); michael@0: michael@0: // replace platform specific path separator and illegal characters to avoid any confusion michael@0: mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); michael@0: mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); michael@0: michael@0: // Remove unsafe bidi characters which might have spoofing implications (bug 511521). michael@0: const char16_t unsafeBidiCharacters[] = { michael@0: char16_t(0x061c), // Arabic Letter Mark michael@0: char16_t(0x200e), // Left-to-Right Mark michael@0: char16_t(0x200f), // Right-to-Left Mark michael@0: char16_t(0x202a), // Left-to-Right Embedding michael@0: char16_t(0x202b), // Right-to-Left Embedding michael@0: char16_t(0x202c), // Pop Directional Formatting michael@0: char16_t(0x202d), // Left-to-Right Override michael@0: char16_t(0x202e), // Right-to-Left Override michael@0: char16_t(0x2066), // Left-to-Right Isolate michael@0: char16_t(0x2067), // Right-to-Left Isolate michael@0: char16_t(0x2068), // First Strong Isolate michael@0: char16_t(0x2069), // Pop Directional Isolate michael@0: char16_t(0) michael@0: }; michael@0: mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_'); michael@0: mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_'); michael@0: michael@0: // Make sure extension is correct. michael@0: EnsureSuggestedFileName(); michael@0: michael@0: mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096); michael@0: } michael@0: michael@0: nsExternalAppHandler::~nsExternalAppHandler() michael@0: { michael@0: MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted"); michael@0: } michael@0: michael@0: void michael@0: nsExternalAppHandler::DidDivertRequest(nsIRequest *request) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process"); michael@0: // Remove our request from the child loadGroup michael@0: RetargetLoadNotifications(request); michael@0: MaybeCloseWindow(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener) michael@0: { michael@0: // This is always called by nsHelperDlg.js. Go ahead and register the michael@0: // progress listener. At this point, we don't have mTransfer. michael@0: mDialogProgressListener = aWebProgressListener; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) michael@0: { michael@0: if (mFinalFileDestination) michael@0: *aTarget = mFinalFileDestination; michael@0: else michael@0: *aTarget = mTempFile; michael@0: michael@0: NS_IF_ADDREF(*aTarget); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec) michael@0: { michael@0: // Use the real target if it's been set michael@0: if (mFinalFileDestination) michael@0: return mFinalFileDestination->IsExecutable(aExec); michael@0: michael@0: // Otherwise, use the stored executable-ness of the temporary michael@0: *aExec = mTempFileIsExecutable; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) michael@0: { michael@0: *aTime = mTimeDownloadStarted; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength) michael@0: { michael@0: *aContentLength = mContentLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request) michael@0: { michael@0: // we are going to run the downloading of the helper app in our own little docloader / load group context. michael@0: // so go ahead and force the creation of a load group and doc loader for us to use... michael@0: nsCOMPtr aChannel = do_QueryInterface(request); michael@0: if (!aChannel) michael@0: return; michael@0: michael@0: // we need to store off the original (pre redirect!) channel that initiated the load. We do michael@0: // this so later on, we can pass any refresh urls associated with the original channel back to the michael@0: // window context which started the whole process. More comments about that are listed below.... michael@0: // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. michael@0: // ideally we should be able to just use mChannel (the channel we are extracting content from) or michael@0: // the default load channel associated with the original load group. Unfortunately because michael@0: // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel michael@0: // which is what we really want.... michael@0: michael@0: // Note that we need to do this before removing aChannel from the loadgroup, michael@0: // since that would mess with the original channel on the loader. michael@0: nsCOMPtr origContextLoader = michael@0: do_GetInterface(mWindowContext); michael@0: if (origContextLoader) michael@0: origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel)); michael@0: michael@0: bool isPrivate = NS_UsePrivateBrowsing(aChannel); michael@0: michael@0: nsCOMPtr oldLoadGroup; michael@0: aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup)); michael@0: michael@0: if(oldLoadGroup) michael@0: oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED); michael@0: michael@0: aChannel->SetLoadGroup(nullptr); michael@0: aChannel->SetNotificationCallbacks(nullptr); michael@0: michael@0: nsCOMPtr pbChannel = do_QueryInterface(aChannel); michael@0: if (pbChannel) { michael@0: pbChannel->SetPrivate(isPrivate); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Make mTempFileExtension contain an extension exactly when its previous value michael@0: * is different from mSuggestedFileName's extension, so that it can be appended michael@0: * to mSuggestedFileName and form a valid, useful leaf name. michael@0: * This is required so that the (renamed) temporary file has the correct extension michael@0: * after downloading to make sure the OS will launch the application corresponding michael@0: * to the MIME type (which was used to calculate mTempFileExtension). This prevents michael@0: * a cgi-script named foobar.exe that returns application/zip from being named michael@0: * foobar.exe and executed as an executable file. It also blocks content that michael@0: * a web site might provide with a content-disposition header indicating michael@0: * filename="foobar.exe" from being downloaded to a file with extension .exe michael@0: * and executed. michael@0: */ michael@0: void nsExternalAppHandler::EnsureSuggestedFileName() michael@0: { michael@0: // Make sure there is a mTempFileExtension (not "" or "."). michael@0: // Remember that mTempFileExtension will always have the leading "." michael@0: // (the check for empty is just to be safe). michael@0: if (mTempFileExtension.Length() > 1) michael@0: { michael@0: // Get mSuggestedFileName's current extension. michael@0: nsAutoString fileExt; michael@0: int32_t pos = mSuggestedFileName.RFindChar('.'); michael@0: if (pos != kNotFound) michael@0: mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos); michael@0: michael@0: // Now, compare fileExt to mTempFileExtension. michael@0: if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator())) michael@0: { michael@0: // Matches -> mTempFileExtension can be empty michael@0: mTempFileExtension.Truncate(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel) michael@0: { michael@0: // First we need to try to get the destination directory for the temporary michael@0: // file. michael@0: nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // At this point, we do not have a filename for the temp file. For security michael@0: // purposes, this cannot be predictable, so we must use a cryptographic michael@0: // quality PRNG to generate one. michael@0: // We will request raw random bytes, and transform that to a base64 string, michael@0: // as all characters from the base64 set are acceptable for filenames. For michael@0: // each three bytes of random data, we will get four bytes of ASCII. Request michael@0: // a bit more, to be safe, and truncate to the length we want in the end. michael@0: michael@0: const uint32_t wantedFileNameLength = 8; michael@0: const uint32_t requiredBytesLength = michael@0: static_cast((wantedFileNameLength + 1) / 4 * 3); michael@0: michael@0: nsCOMPtr rg = michael@0: do_GetService("@mozilla.org/security/random-generator;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint8_t *buffer; michael@0: rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString tempLeafName; michael@0: nsDependentCSubstring randomData(reinterpret_cast(buffer), requiredBytesLength); michael@0: rv = Base64Encode(randomData, tempLeafName); michael@0: NS_Free(buffer); michael@0: buffer = nullptr; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: tempLeafName.Truncate(wantedFileNameLength); michael@0: michael@0: // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need michael@0: // to replace illegal characters -- notably '/' michael@0: tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); michael@0: michael@0: // now append our extension. michael@0: nsAutoCString ext; michael@0: mMimeInfo->GetPrimaryExtension(ext); michael@0: if (!ext.IsEmpty()) { michael@0: ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); michael@0: if (ext.First() != '.') michael@0: tempLeafName.Append('.'); michael@0: tempLeafName.Append(ext); michael@0: } michael@0: michael@0: // We need to temporarily create a dummy file with the correct michael@0: // file extension to determine the executable-ness, so do this before adding michael@0: // the extra .part extension. michael@0: nsCOMPtr dummyFile; michael@0: rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set the file name without .part michael@0: rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Store executable-ness then delete michael@0: dummyFile->IsExecutable(&mTempFileIsExecutable); michael@0: dummyFile->Remove(false); michael@0: michael@0: // Add an additional .part to prevent the OS from running this file in the michael@0: // default application. michael@0: tempLeafName.Append(NS_LITERAL_CSTRING(".part")); michael@0: michael@0: rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName)); michael@0: // make this file unique!!! michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now save the temp leaf name, minus the ".part" bit, so we can use it later. michael@0: // This is a bit broken in the case when createUnique actually had to append michael@0: // some numbers, because then we now have a filename like foo.bar-1.part and michael@0: // we'll end up with foo.bar-1.bar as our final filename if we end up using michael@0: // this. But the other options are all bad too.... Ideally we'd have a way michael@0: // to tell createUnique to put its unique marker before the extension that michael@0: // comes before ".part" or something. michael@0: rv = mTempFile->GetLeafName(mTempLeafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")), michael@0: NS_ERROR_UNEXPECTED); michael@0: michael@0: // Strip off the ".part" from mTempLeafName michael@0: mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1); michael@0: michael@0: MOZ_ASSERT(!mSaver, "Output file initialization called more than once!"); michael@0: mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, michael@0: &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mSaver->SetObserver(this); michael@0: if (NS_FAILED(rv)) { michael@0: mSaver = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: rv = mSaver->EnableSha256(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mSaver->EnableSignatureInfo(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: LOG(("Enabled hashing and signature verification")); michael@0: michael@0: rv = mSaver->SetTarget(mTempFile, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt) michael@0: { michael@0: NS_PRECONDITION(request, "OnStartRequest without request?"); michael@0: michael@0: // Set mTimeDownloadStarted here as the download has already started and michael@0: // we want to record the start time before showing the filepicker. michael@0: mTimeDownloadStarted = PR_Now(); michael@0: michael@0: mRequest = request; michael@0: michael@0: nsCOMPtr aChannel = do_QueryInterface(request); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr fileChan(do_QueryInterface(request)); michael@0: mIsFileChannel = fileChan != nullptr; michael@0: michael@0: // Get content length michael@0: if (aChannel) { michael@0: aChannel->GetContentLength(&mContentLength); michael@0: } michael@0: michael@0: nsCOMPtr props(do_QueryInterface(request, &rv)); michael@0: // Determine whether a new window was opened specifically for this request michael@0: if (props) { michael@0: bool tmp = false; michael@0: props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"), michael@0: &tmp); michael@0: mShouldCloseWindow = tmp; michael@0: } michael@0: michael@0: // Now get the URI michael@0: if (aChannel) michael@0: { michael@0: aChannel->GetURI(getter_AddRefs(mSourceUrl)); michael@0: } michael@0: michael@0: // retarget all load notifications to our docloader instead of the original window's docloader... michael@0: RetargetLoadNotifications(request); michael@0: michael@0: // Check to see if there is a refresh header on the original channel. michael@0: if (mOriginalChannel) { michael@0: nsCOMPtr httpChannel(do_QueryInterface(mOriginalChannel)); michael@0: if (httpChannel) { michael@0: nsAutoCString refreshHeader; michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"), michael@0: refreshHeader); michael@0: if (!refreshHeader.IsEmpty()) { michael@0: mShouldCloseWindow = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Close the underlying DOMWindow if there is no refresh header michael@0: // and it was opened specifically for the download michael@0: MaybeCloseWindow(); michael@0: michael@0: // In an IPC setting, we're allowing the child process, here, to make michael@0: // decisions about decoding the channel (e.g. decompression). It will michael@0: // still forward the decoded (uncompressed) data back to the parent. michael@0: // Con: Uncompressed data means more IPC overhead. michael@0: // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel. michael@0: // Parent process doesn't need to expect CPU time on decompression. michael@0: nsCOMPtr encChannel = do_QueryInterface( aChannel ); michael@0: if (encChannel) michael@0: { michael@0: // Turn off content encoding conversions if needed michael@0: bool applyConversion = true; michael@0: michael@0: nsCOMPtr sourceURL(do_QueryInterface(mSourceUrl)); michael@0: if (sourceURL) michael@0: { michael@0: nsAutoCString extension; michael@0: sourceURL->GetFileExtension(extension); michael@0: if (!extension.IsEmpty()) michael@0: { michael@0: nsCOMPtr encEnum; michael@0: encChannel->GetContentEncodings(getter_AddRefs(encEnum)); michael@0: if (encEnum) michael@0: { michael@0: bool hasMore; michael@0: rv = encEnum->HasMore(&hasMore); michael@0: if (NS_SUCCEEDED(rv) && hasMore) michael@0: { michael@0: nsAutoCString encType; michael@0: rv = encEnum->GetNext(encType); michael@0: if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) michael@0: { michael@0: mExtProtSvc->ApplyDecodingForExtension(extension, encType, michael@0: &applyConversion); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: encChannel->SetApplyConversion( applyConversion ); michael@0: } michael@0: michael@0: // At this point, the child process has done everything it can usefully do michael@0: // for OnStartRequest. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) michael@0: return NS_OK; michael@0: michael@0: rv = SetUpTempFile(aChannel); michael@0: if (NS_FAILED(rv)) { michael@0: nsresult transferError = rv; michael@0: michael@0: rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel)); michael@0: #ifdef PR_LOGGING michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Failed to create transfer to report failure." michael@0: "Will fallback to prompter!")); michael@0: } michael@0: #endif michael@0: michael@0: mCanceled = true; michael@0: request->Cancel(transferError); michael@0: michael@0: nsAutoString path; michael@0: if (mTempFile) michael@0: mTempFile->GetPath(path); michael@0: michael@0: SendStatusChange(kWriteError, transferError, request, path); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Inform channel it is open on behalf of a download to prevent caching. michael@0: nsCOMPtr httpInternal = do_QueryInterface(aChannel); michael@0: if (httpInternal) { michael@0: httpInternal->SetChannelIsForDownload(true); michael@0: } michael@0: michael@0: // now that the temp file is set up, find out if we need to invoke a dialog michael@0: // asking the user what they want us to do with this content... michael@0: michael@0: // We can get here for three reasons: "can't handle", "sniffed type", or michael@0: // "server sent content-disposition:attachment". In the first case we want michael@0: // to honor the user's "always ask" pref; in the other two cases we want to michael@0: // honor it only if the default action is "save". Opening attachments in michael@0: // helper apps by default breaks some websites (especially if the attachment michael@0: // is one part of a multipart document). Opening sniffed content in helper michael@0: // apps by default introduces security holes that we'd rather not have. michael@0: michael@0: // So let's find out whether the user wants to be prompted. If he does not, michael@0: // check mReason and the preferred action to see what we should do. michael@0: michael@0: bool alwaysAsk = true; michael@0: mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); michael@0: if (alwaysAsk) michael@0: { michael@0: // But we *don't* ask if this mimeInfo didn't come from michael@0: // our user configuration datastore and the user has said michael@0: // at some point in the distant past that they don't michael@0: // want to be asked. The latter fact would have been michael@0: // stored in pref strings back in the old days. michael@0: michael@0: bool mimeTypeIsInDatastore = false; michael@0: nsCOMPtr handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); michael@0: if (handlerSvc) michael@0: handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore); michael@0: if (!handlerSvc || !mimeTypeIsInDatastore) michael@0: { michael@0: nsAutoCString MIMEType; michael@0: mMimeInfo->GetMIMEType(MIMEType); michael@0: michael@0: if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get())) michael@0: { michael@0: // Don't need to ask after all. michael@0: alwaysAsk = false; michael@0: // Make sure action matches pref (save to disk). michael@0: mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); michael@0: } michael@0: else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get())) michael@0: { michael@0: // Don't need to ask after all. michael@0: alwaysAsk = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t action = nsIMIMEInfo::saveToDisk; michael@0: mMimeInfo->GetPreferredAction( &action ); michael@0: michael@0: // OK, now check why we're here michael@0: if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) { michael@0: // Force asking if we're not saving. See comment back when we fetched the michael@0: // alwaysAsk boolean for details. michael@0: alwaysAsk = (action != nsIMIMEInfo::saveToDisk); michael@0: } michael@0: michael@0: // if we were told that we _must_ save to disk without asking, all the stuff michael@0: // before this is irrelevant; override it michael@0: if (mForceSave) { michael@0: alwaysAsk = false; michael@0: action = nsIMIMEInfo::saveToDisk; michael@0: } michael@0: michael@0: if (alwaysAsk) michael@0: { michael@0: // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request michael@0: mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // this will create a reference cycle (the dialog holds a reference to us as michael@0: // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer. michael@0: rv = mDialog->Show( this, mWindowContext, mReason ); michael@0: michael@0: // what do we do if the dialog failed? I guess we should call Cancel and abort the load.... michael@0: } michael@0: else michael@0: { michael@0: michael@0: // We need to do the save/open immediately, then. michael@0: #ifdef XP_WIN michael@0: /* We need to see whether the file we've got here could be michael@0: * executable. If it could, we had better not try to open it! michael@0: * We can skip this check, though, if we have a setting to open in a michael@0: * helper app. michael@0: * This code mirrors the code in michael@0: * nsExternalAppHandler::LaunchWithApplication so that what we michael@0: * test here is as close as possible to what will really be michael@0: * happening if we decide to execute michael@0: */ michael@0: nsCOMPtr prefApp; michael@0: mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp)); michael@0: if (action != nsIMIMEInfo::useHelperApp || !prefApp) { michael@0: nsCOMPtr fileToTest; michael@0: GetTargetFile(getter_AddRefs(fileToTest)); michael@0: if (fileToTest) { michael@0: bool isExecutable; michael@0: rv = fileToTest->IsExecutable(&isExecutable); michael@0: if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good michael@0: action = nsIMIMEInfo::saveToDisk; michael@0: } michael@0: } else { // Paranoia is good here too, though this really should not happen michael@0: NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! "); michael@0: action = nsIMIMEInfo::saveToDisk; michael@0: } michael@0: } michael@0: michael@0: #endif michael@0: if (action == nsIMIMEInfo::useHelperApp || michael@0: action == nsIMIMEInfo::useSystemDefault) michael@0: { michael@0: rv = LaunchWithApplication(nullptr, false); michael@0: } michael@0: else // Various unknown actions go here too michael@0: { michael@0: rv = SaveToDisk(nullptr, false); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Convert error info into proper message text and send OnStatusChange michael@0: // notification to the dialog progress listener or nsITransfer implementation. michael@0: void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path) michael@0: { michael@0: nsAutoString msgId; michael@0: switch(rv) michael@0: { michael@0: case NS_ERROR_OUT_OF_MEMORY: michael@0: // No memory michael@0: msgId.AssignLiteral("noMemory"); michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_DISK_FULL: michael@0: case NS_ERROR_FILE_NO_DEVICE_SPACE: michael@0: // Out of space on target volume. michael@0: msgId.AssignLiteral("diskFull"); michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_READ_ONLY: michael@0: // Attempt to write to read/only file. michael@0: msgId.AssignLiteral("readOnly"); michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_ACCESS_DENIED: michael@0: if (type == kWriteError) { michael@0: // Attempt to write without sufficient permissions. michael@0: #if defined(ANDROID) michael@0: // On Android (and Gonk), this means the SD card is present but michael@0: // unavailable (read-only). michael@0: msgId.AssignLiteral("SDAccessErrorCardReadOnly"); michael@0: #else michael@0: msgId.AssignLiteral("accessError"); michael@0: #endif michael@0: } michael@0: else michael@0: { michael@0: msgId.AssignLiteral("launchError"); michael@0: } michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_NOT_FOUND: michael@0: case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST: michael@0: case NS_ERROR_FILE_UNRECOGNIZED_PATH: michael@0: // Helper app not found, let's verify this happened on launch michael@0: if (type == kLaunchError) { michael@0: msgId.AssignLiteral("helperAppNotFound"); michael@0: break; michael@0: } michael@0: #if defined(ANDROID) michael@0: else if (type == kWriteError) { michael@0: // On Android (and Gonk), this means the SD card is missing (not in michael@0: // SD slot). michael@0: msgId.AssignLiteral("SDAccessErrorCardMissing"); michael@0: break; michael@0: } michael@0: #endif michael@0: // fall through michael@0: michael@0: default: michael@0: // Generic read/write/launch error message. michael@0: switch(type) michael@0: { michael@0: case kReadError: michael@0: msgId.AssignLiteral("readError"); michael@0: break; michael@0: case kWriteError: michael@0: msgId.AssignLiteral("writeError"); michael@0: break; michael@0: case kLaunchError: michael@0: msgId.AssignLiteral("launchError"); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, michael@0: ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n", michael@0: NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv)); michael@0: PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, michael@0: (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get())); michael@0: michael@0: // Get properties file bundle and extract status string. michael@0: nsCOMPtr stringService = michael@0: mozilla::services::GetStringBundleService(); michael@0: if (stringService) michael@0: { michael@0: nsCOMPtr bundle; michael@0: if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle)))) michael@0: { michael@0: nsXPIDLString msgText; michael@0: const char16_t *strings[] = { path.get() }; michael@0: if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)))) michael@0: { michael@0: if (mDialogProgressListener) michael@0: { michael@0: // We have a listener, let it handle the error. michael@0: mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText); michael@0: } else if (mTransfer) { michael@0: mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText); michael@0: } michael@0: else michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // We don't have a listener. Simply show the alert ourselves. michael@0: nsresult qiRv; michael@0: nsCOMPtr prompter(do_GetInterface(mWindowContext, &qiRv)); michael@0: nsXPIDLString title; michael@0: bundle->FormatStringFromName(MOZ_UTF16("title"), michael@0: strings, michael@0: 1, michael@0: getter_Copies(title)); michael@0: michael@0: PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG, michael@0: ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'", michael@0: mWindowContext.get(), michael@0: prompter.get(), michael@0: qiRv, michael@0: NS_ConvertUTF16toUTF8(title).get(), michael@0: NS_ConvertUTF16toUTF8(msgText).get())); michael@0: michael@0: // If we didn't have a prompter we will try and get a window michael@0: // instead, get it's docshell and use it to alert the user. michael@0: if (!prompter) michael@0: { michael@0: nsCOMPtr window(do_GetInterface(mWindowContext)); michael@0: if (!window || !window->GetDocShell()) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: prompter = do_GetInterface(window->GetDocShell(), &qiRv); michael@0: michael@0: PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG, michael@0: ("No prompter from mWindowContext, using DocShell, " \ michael@0: "window=0x%p, docShell=0x%p, " \ michael@0: "prompter=0x%p, qi rv=0x%08X", michael@0: window.get(), michael@0: window->GetDocShell(), michael@0: prompter.get(), michael@0: qiRv)); michael@0: michael@0: // If we still don't have a prompter, there's nothing else we michael@0: // can do so just return. michael@0: if (!prompter) michael@0: { michael@0: PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, michael@0: ("No prompter from DocShell, no way to alert user")); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // We should always have a prompter at this point. michael@0: prompter->Alert(title, msgText); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, michael@0: nsIInputStream * inStr, michael@0: uint64_t sourceOffset, uint32_t count) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: // first, check to see if we've been canceled.... michael@0: if (mCanceled || !mSaver) // then go cancel our underlying channel too michael@0: return request->Cancel(NS_BINDING_ABORTED); michael@0: michael@0: // read the data out of the stream and write it to the temp file. michael@0: if (count > 0) michael@0: { michael@0: mProgress += count; michael@0: michael@0: nsCOMPtr saver = do_QueryInterface(mSaver); michael@0: rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: // Send progress notification. michael@0: if (mTransfer) { michael@0: mTransfer->OnProgressChange64(nullptr, request, mProgress, michael@0: mContentLength, mProgress, michael@0: mContentLength); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // An error occurred, notify listener. michael@0: nsAutoString tempFilePath; michael@0: if (mTempFile) michael@0: mTempFile->GetPath(tempFilePath); michael@0: SendStatusChange(kReadError, rv, request, tempFilePath); michael@0: michael@0: // Cancel the download. michael@0: Cancel(rv); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: LOG(("nsExternalAppHandler::OnStopRequest\n" michael@0: " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n", michael@0: mCanceled, mTransfer.get(), aStatus)); michael@0: michael@0: mStopRequestIssued = true; michael@0: michael@0: // Cancel if the request did not complete successfully. michael@0: if (!mCanceled && NS_FAILED(aStatus)) michael@0: { michael@0: // Send error notification. michael@0: nsAutoString tempFilePath; michael@0: if (mTempFile) michael@0: mTempFile->GetPath(tempFilePath); michael@0: SendStatusChange( kReadError, aStatus, request, tempFilePath ); michael@0: michael@0: Cancel(aStatus); michael@0: } michael@0: michael@0: // first, check to see if we've been canceled.... michael@0: if (mCanceled || !mSaver) michael@0: return NS_OK; michael@0: michael@0: return mSaver->Finish(NS_OK); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver, michael@0: nsIFile *aTarget) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver, michael@0: nsresult aStatus) michael@0: { michael@0: LOG(("nsExternalAppHandler::OnSaveComplete\n" michael@0: " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n", michael@0: aSaver, aStatus, mCanceled, mTransfer.get())); michael@0: michael@0: if (!mCanceled) { michael@0: // Save the hash michael@0: (void)mSaver->GetSha256Hash(mHash); michael@0: (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo)); michael@0: // Free the reference that the saver keeps on us, even if we couldn't get michael@0: // the hash. michael@0: mSaver = nullptr; michael@0: michael@0: if (NS_FAILED(aStatus)) { michael@0: nsAutoString path; michael@0: mTempFile->GetPath(path); michael@0: michael@0: // It may happen when e10s is enabled that there will be no transfer michael@0: // object available to communicate status as expected by the system. michael@0: // Let's try and create a temporary transfer object to take care of this michael@0: // for us, we'll fall back to using the prompt service if we absolutely michael@0: // have to. michael@0: if (!mTransfer) { michael@0: nsCOMPtr channel = do_QueryInterface(mRequest); michael@0: // We don't care if this fails. michael@0: CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel)); michael@0: } michael@0: michael@0: SendStatusChange(kWriteError, aStatus, nullptr, path); michael@0: if (!mCanceled) michael@0: Cancel(aStatus); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Notify the transfer object that we are done if the user has chosen an michael@0: // action. If the user hasn't chosen an action, the progress listener michael@0: // (nsITransfer) will be notified in CreateTransfer. michael@0: if (mTransfer) { michael@0: NotifyTransfer(aStatus); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsExternalAppHandler::NotifyTransfer(nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread"); michael@0: MOZ_ASSERT(mTransfer, "We must have an nsITransfer"); michael@0: michael@0: LOG(("Notifying progress listener")); michael@0: michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: (void)mTransfer->SetSha256Hash(mHash); michael@0: (void)mTransfer->SetSignatureInfo(mSignatureInfo); michael@0: (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress, michael@0: mContentLength, mProgress, mContentLength); michael@0: } michael@0: michael@0: (void)mTransfer->OnStateChange(nullptr, nullptr, michael@0: nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_REQUEST | michael@0: nsIWebProgressListener::STATE_IS_NETWORK, aStatus); michael@0: michael@0: // This nsITransfer object holds a reference to us (we are its observer), so michael@0: // we need to release the reference to break a reference cycle (and therefore michael@0: // to prevent leaking). We do this even if the previous calls failed. michael@0: mTransfer = nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo) michael@0: { michael@0: *aMIMEInfo = mMimeInfo; michael@0: NS_ADDREF(*aMIMEInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI) michael@0: { michael@0: NS_ENSURE_ARG(aSourceURI); michael@0: *aSourceURI = mSourceUrl; michael@0: NS_IF_ADDREF(*aSourceURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName) michael@0: { michael@0: aSuggestedFileName = mSuggestedFileName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsExternalAppHandler::CreateTransfer() michael@0: { michael@0: LOG(("nsExternalAppHandler::CreateTransfer")); michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread"); michael@0: // We are back from the helper app dialog (where the user chooses to save or michael@0: // open), but we aren't done processing the load. in this case, throw up a michael@0: // progress dialog so the user can see what's going on. michael@0: // Also, release our reference to mDialog. We don't need it anymore, and we michael@0: // need to break the reference cycle. michael@0: mDialog = nullptr; michael@0: if (!mDialogProgressListener) { michael@0: NS_WARNING("The dialog should nullify the dialog progress listener"); michael@0: } michael@0: nsresult rv; michael@0: michael@0: // We must be able to create an nsITransfer object. If not, it doesn't matter michael@0: // much that we can't launch the helper application or save to disk. Work on michael@0: // a local copy rather than mTransfer until we know we succeeded, to make it michael@0: // clearer that this function is re-entrant. michael@0: nsCOMPtr transfer = do_CreateInstance( michael@0: NS_TRANSFER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Initialize the download michael@0: nsCOMPtr target; michael@0: rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(mRequest); michael@0: michael@0: rv = transfer->Init(mSourceUrl, target, EmptyString(), michael@0: mMimeInfo, mTimeDownloadStarted, mTempFile, this, michael@0: channel && NS_UsePrivateBrowsing(channel)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now let's add the download to history michael@0: nsCOMPtr dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID)); michael@0: if (dh) { michael@0: nsCOMPtr referrer; michael@0: nsCOMPtr channel = do_QueryInterface(mRequest); michael@0: if (channel) { michael@0: NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer)); michael@0: } michael@0: michael@0: if (channel && !NS_UsePrivateBrowsing(channel)) { michael@0: dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target); michael@0: } michael@0: } michael@0: michael@0: // If we were cancelled since creating the transfer, just return. It is michael@0: // always ok to return NS_OK if we are cancelled. Callers of this function michael@0: // must call Cancel if CreateTransfer fails, but there's no need to cancel michael@0: // twice. michael@0: if (mCanceled) { michael@0: return NS_OK; michael@0: } michael@0: rv = transfer->OnStateChange(nullptr, mRequest, michael@0: nsIWebProgressListener::STATE_START | michael@0: nsIWebProgressListener::STATE_IS_REQUEST | michael@0: nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mCanceled) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mRequest = nullptr; michael@0: // Finally, save the transfer to mTransfer. michael@0: mTransfer = transfer; michael@0: transfer = nullptr; michael@0: michael@0: // While we were bringing up the progress dialog, we actually finished michael@0: // processing the url. If that's the case then mStopRequestIssued will be michael@0: // true and OnSaveComplete has been called. michael@0: if (mStopRequestIssued && !mSaver && mTransfer) { michael@0: NotifyTransfer(NS_OK); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr transfer = michael@0: do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we don't have a download directory we're kinda screwed but it's OK michael@0: // we'll still report the error via the prompter. michael@0: nsCOMPtr pseudoFile; michael@0: rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Append the default suggested filename. If the user restarts the transfer michael@0: // we will re-trigger a filename check anyway to ensure that it is unique. michael@0: rv = pseudoFile->Append(mSuggestedFileName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr pseudoTarget; michael@0: rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(), michael@0: mMimeInfo, mTimeDownloadStarted, nullptr, this, michael@0: aIsPrivateBrowsing); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Our failed transfer is ready. michael@0: mTransfer = transfer.forget(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile) michael@0: { michael@0: if (aFile) michael@0: ContinueSave(aFile); michael@0: else michael@0: Cancel(NS_BINDING_ABORTED); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension) michael@0: { michael@0: // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request michael@0: // Convert to use file picker? No, then embeddors could not do any sort of michael@0: // "AutoDownload" w/o showing a prompt michael@0: nsresult rv = NS_OK; michael@0: if (!mDialog) michael@0: { michael@0: // Get helper app launcher dialog. michael@0: mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); michael@0: if (rv != NS_OK) { michael@0: Cancel(NS_BINDING_ABORTED); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape michael@0: // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined... michael@0: michael@0: // Now, be sure to keep |this| alive, and the dialog michael@0: // If we don't do this, users that close the helper app dialog while the file michael@0: // picker is up would cause Cancel() to be called, and the dialog would be michael@0: // released, which would release this object too, which would crash. michael@0: // See Bug 249143 michael@0: nsIFile* fileToUse; michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: nsCOMPtr dlg(mDialog); michael@0: rv = mDialog->PromptForSaveToFile(this, michael@0: mWindowContext, michael@0: aDefaultFile.get(), michael@0: aFileExtension.get(), michael@0: mForceSave, &fileToUse); michael@0: michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync. michael@0: rv = mDialog->PromptForSaveToFileAsync(this, michael@0: mWindowContext, michael@0: aDefaultFile.get(), michael@0: aFileExtension.get(), michael@0: mForceSave); michael@0: } else { michael@0: SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr); michael@0: } michael@0: } michael@0: michael@0: // SaveToDisk should only be called by the helper app dialog which allows michael@0: // the user to say launch with application or save to disk. It doesn't actually michael@0: // perform the save, it just prompts for the destination file name. michael@0: NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference) michael@0: { michael@0: if (mCanceled) michael@0: return NS_OK; michael@0: michael@0: mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); michael@0: michael@0: if (!aNewFileLocation) { michael@0: if (mSuggestedFileName.IsEmpty()) michael@0: RequestSaveDestination(mTempLeafName, mTempFileExtension); michael@0: else michael@0: { michael@0: nsAutoString fileExt; michael@0: int32_t pos = mSuggestedFileName.RFindChar('.'); michael@0: if (pos >= 0) michael@0: mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos); michael@0: if (fileExt.IsEmpty()) michael@0: fileExt = mTempFileExtension; michael@0: michael@0: RequestSaveDestination(mSuggestedFileName, fileExt); michael@0: } michael@0: } else { michael@0: ContinueSave(aNewFileLocation); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation) michael@0: { michael@0: if (mCanceled) michael@0: return NS_OK; michael@0: michael@0: NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr fileToUse = do_QueryInterface(aNewFileLocation); michael@0: mFinalFileDestination = do_QueryInterface(fileToUse); michael@0: michael@0: // Move what we have in the final directory, but append .part michael@0: // to it, to indicate that it's unfinished. Do not call SetTarget on the michael@0: // saver if we are done (Finish has been called) but OnSaverComplete has not michael@0: // been called. michael@0: if (mFinalFileDestination && mSaver && !mStopRequestIssued) michael@0: { michael@0: nsCOMPtr movedFile; michael@0: mFinalFileDestination->Clone(getter_AddRefs(movedFile)); michael@0: if (movedFile) { michael@0: // Get the old leaf name and append .part to it michael@0: nsAutoString name; michael@0: mFinalFileDestination->GetLeafName(name); michael@0: name.AppendLiteral(".part"); michael@0: movedFile->SetLeafName(name); michael@0: michael@0: rv = mSaver->SetTarget(movedFile, true); michael@0: if (NS_FAILED(rv)) { michael@0: nsAutoString path; michael@0: mTempFile->GetPath(path); michael@0: SendStatusChange(kWriteError, rv, nullptr, path); michael@0: Cancel(rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mTempFile = movedFile; michael@0: } michael@0: } michael@0: michael@0: // The helper app dialog has told us what to do and we have a final file michael@0: // destination. michael@0: rv = CreateTransfer(); michael@0: // If we fail to create the transfer, Cancel. michael@0: if (NS_FAILED(rv)) { michael@0: Cancel(rv); michael@0: return rv; michael@0: } michael@0: michael@0: // now that the user has chosen the file location to save to, it's okay to fire the refresh tag michael@0: // if there is one. We don't want to do this before the save as dialog goes away because this dialog michael@0: // is modal and we do bad things if you try to load a web page in the underlying window while a modal michael@0: // dialog is still up. michael@0: ProcessAnyRefreshTags(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // LaunchWithApplication should only be called by the helper app dialog which michael@0: // allows the user to say launch with application or save to disk. It doesn't michael@0: // actually perform launch with application. michael@0: NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference) michael@0: { michael@0: if (mCanceled) michael@0: return NS_OK; michael@0: michael@0: // user has chosen to launch using an application, fire any refresh tags now... michael@0: ProcessAnyRefreshTags(); michael@0: michael@0: if (mMimeInfo && aApplication) { michael@0: PlatformLocalHandlerApp_t *handlerApp = michael@0: new PlatformLocalHandlerApp_t(EmptyString(), aApplication); michael@0: mMimeInfo->SetPreferredApplicationHandler(handlerApp); michael@0: } michael@0: michael@0: // Now check if the file is local, in which case we won't bother with saving michael@0: // it to a temporary directory and just launch it from where it is michael@0: nsCOMPtr fileUrl(do_QueryInterface(mSourceUrl)); michael@0: if (fileUrl && mIsFileChannel) michael@0: { michael@0: Cancel(NS_BINDING_ABORTED); michael@0: nsCOMPtr file; michael@0: nsresult rv = fileUrl->GetFile(getter_AddRefs(file)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: rv = mMimeInfo->LaunchWithFile(file); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return NS_OK; michael@0: } michael@0: nsAutoString path; michael@0: if (file) michael@0: file->GetPath(path); michael@0: // If we get here, an error happened michael@0: SendStatusChange(kLaunchError, rv, nullptr, path); michael@0: return rv; michael@0: } michael@0: michael@0: // Now that the user has elected to launch the downloaded file with a helper michael@0: // app, we're justified in removing the 'salted' name. We'll rename to what michael@0: // was specified in mSuggestedFileName after the download is done prior to michael@0: // launching the helper app. So that any existing file of that name won't be michael@0: // overwritten we call CreateUnique(). Also note that we use the same michael@0: // directory as originally downloaded so nsDownload can rename in place michael@0: // later. michael@0: nsCOMPtr fileToUse; michael@0: (void) GetDownloadDirectory(getter_AddRefs(fileToUse)); michael@0: michael@0: if (mSuggestedFileName.IsEmpty()) michael@0: { michael@0: // Keep using the leafname of the temp file, since we're just starting a helper michael@0: mSuggestedFileName = mTempLeafName; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: fileToUse->Append(mSuggestedFileName + mTempFileExtension); michael@0: #else michael@0: fileToUse->Append(mSuggestedFileName); michael@0: #endif michael@0: michael@0: nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: if(NS_SUCCEEDED(rv)) michael@0: { michael@0: mFinalFileDestination = do_QueryInterface(fileToUse); michael@0: // launch the progress window now that the user has picked the desired action. michael@0: rv = CreateTransfer(); michael@0: if (NS_FAILED(rv)) { michael@0: Cancel(rv); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // Cancel the download and report an error. We do not want to end up in michael@0: // a state where it appears that we have a normal download that is michael@0: // pointing to a file that we did not actually create. michael@0: nsAutoString path; michael@0: mTempFile->GetPath(path); michael@0: SendStatusChange(kWriteError, rv, nullptr, path); michael@0: Cancel(rv); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) michael@0: { michael@0: NS_ENSURE_ARG(NS_FAILED(aReason)); michael@0: michael@0: if (mCanceled) { michael@0: return NS_OK; michael@0: } michael@0: mCanceled = true; michael@0: michael@0: if (mSaver) { michael@0: // We are still writing to the target file. Give the saver a chance to michael@0: // close the target file, then notify the transfer object if necessary in michael@0: // the OnSaveComplete callback. michael@0: mSaver->Finish(aReason); michael@0: mSaver = nullptr; michael@0: } else { michael@0: if (mStopRequestIssued && mTempFile) { michael@0: // This branch can only happen when the user cancels the helper app dialog michael@0: // when the request has completed. The temp file has to be removed here, michael@0: // because mSaver has been released at that time with the temp file left. michael@0: (void)mTempFile->Remove(false); michael@0: } michael@0: michael@0: // Notify the transfer object that the download has been canceled, if the michael@0: // user has already chosen an action and we didn't notify already. michael@0: if (mTransfer) { michael@0: NotifyTransfer(aReason); michael@0: } michael@0: } michael@0: michael@0: // Break our reference cycle with the helper app dialog (set up in michael@0: // OnStartRequest) michael@0: mDialog = nullptr; michael@0: michael@0: mRequest = nullptr; michael@0: michael@0: // Release the listener, to break the reference cycle with it (we are the michael@0: // observer of the listener). michael@0: mDialogProgressListener = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsExternalAppHandler::ProcessAnyRefreshTags() michael@0: { michael@0: // one last thing, try to see if the original window context supports a refresh interface... michael@0: // Sometimes, when you download content that requires an external handler, there is michael@0: // a refresh header associated with the download. This refresh header points to a page michael@0: // the content provider wants the user to see after they download the content. How do we michael@0: // pass this refresh information back to the caller? For now, try to get the refresh URI michael@0: // interface. If the window context where the request originated came from supports this michael@0: // then we can force it to process the refresh information (if there is any) from this channel. michael@0: if (mWindowContext && mOriginalChannel) michael@0: { michael@0: nsCOMPtr refreshHandler (do_GetInterface(mWindowContext)); michael@0: if (refreshHandler) { michael@0: refreshHandler->SetupRefreshURI(mOriginalChannel); michael@0: } michael@0: mOriginalChannel = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType) michael@0: { michael@0: // Search the obsolete pref strings. michael@0: nsAdoptingCString prefCString = Preferences::GetCString(prefName); michael@0: if (prefCString.IsEmpty()) { michael@0: // Default is true, if not found in the pref string. michael@0: return true; michael@0: } michael@0: michael@0: NS_UnescapeURL(prefCString); michael@0: nsACString::const_iterator start, end; michael@0: prefCString.BeginReading(start); michael@0: prefCString.EndReading(end); michael@0: return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType), michael@0: start, end); michael@0: } michael@0: michael@0: nsresult nsExternalAppHandler::MaybeCloseWindow() michael@0: { michael@0: nsCOMPtr window = do_GetInterface(mWindowContext); michael@0: NS_ENSURE_STATE(window); michael@0: michael@0: if (mShouldCloseWindow) { michael@0: // Reset the window context to the opener window so that the dependent michael@0: // dialogs have a parent michael@0: nsCOMPtr opener; michael@0: window->GetOpener(getter_AddRefs(opener)); michael@0: michael@0: bool isClosed; michael@0: if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) { michael@0: mWindowContext = do_GetInterface(opener); michael@0: michael@0: // Now close the old window. Do it on a timer so that we don't run michael@0: // into issues trying to close the window before it has fully opened. michael@0: NS_ASSERTION(!mTimer, "mTimer was already initialized once!"); michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (!mTimer) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT); michael@0: mWindowToClose = window; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsExternalAppHandler::Notify(nsITimer* timer) michael@0: { michael@0: NS_ASSERTION(mWindowToClose, "No window to close after timer fired"); michael@0: michael@0: mWindowToClose->Close(); michael@0: mWindowToClose = nullptr; michael@0: mTimer = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: // The following section contains our nsIMIMEService implementation and related methods. michael@0: // michael@0: ////////////////////////////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // nsIMIMEService methods michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval) michael@0: { michael@0: NS_PRECONDITION(!aMIMEType.IsEmpty() || michael@0: !aFileExt.IsEmpty(), michael@0: "Give me something to work with"); michael@0: LOG(("Getting mimeinfo from type '%s' ext '%s'\n", michael@0: PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get())); michael@0: michael@0: *_retval = nullptr; michael@0: michael@0: // OK... we need a type. Get one. michael@0: nsAutoCString typeToUse(aMIMEType); michael@0: if (typeToUse.IsEmpty()) { michael@0: nsresult rv = GetTypeFromExtension(aFileExt, typeToUse); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // We promise to only send lower case mime types to the OS michael@0: ToLowerCase(typeToUse); michael@0: michael@0: // (1) Ask the OS for a mime info michael@0: bool found; michael@0: *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take(); michael@0: LOG(("OS gave back 0x%p - found: %i\n", *_retval, found)); michael@0: // If we got no mimeinfo, something went wrong. Probably lack of memory. michael@0: if (!*_retval) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // (2) Now, let's see if we can find something in our datastore michael@0: // This will not overwrite the OS information that interests us michael@0: // (i.e. default application, default app. description) michael@0: nsresult rv; michael@0: nsCOMPtr handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); michael@0: if (handlerSvc) { michael@0: bool hasHandler = false; michael@0: (void) handlerSvc->Exists(*_retval, &hasHandler); michael@0: if (hasHandler) { michael@0: rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString()); michael@0: LOG(("Data source: Via type: retval 0x%08x\n", rv)); michael@0: } else { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: found = found || NS_SUCCEEDED(rv); michael@0: michael@0: if (!found || NS_FAILED(rv)) { michael@0: // No type match, try extension match michael@0: if (!aFileExt.IsEmpty()) { michael@0: nsAutoCString overrideType; michael@0: rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType); michael@0: if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) { michael@0: // We can't check handlerSvc->Exists() here, because we have a michael@0: // overideType. That's ok, it just results in some console noise. michael@0: // (If there's no handler for the override type, it throws) michael@0: rv = handlerSvc->FillHandlerInfo(*_retval, overrideType); michael@0: LOG(("Data source: Via ext: retval 0x%08x\n", rv)); michael@0: found = found || NS_SUCCEEDED(rv); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // (3) No match yet. Ask extras. michael@0: if (!found) { michael@0: rv = NS_ERROR_FAILURE; michael@0: #ifdef XP_WIN michael@0: /* XXX Gross hack to wallpaper over the most common Win32 michael@0: * extension issues caused by the fix for bug 116938. See bug michael@0: * 120327, comment 271 for why this is needed. Not even sure we michael@0: * want to remove this once we have fixed all this stuff to work michael@0: * right; any info we get from extras on this type is pretty much michael@0: * useless.... michael@0: */ michael@0: if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator())) michael@0: #endif michael@0: rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval); michael@0: LOG(("Searched extras (by type), rv 0x%08X\n", rv)); michael@0: // If that didn't work out, try file extension from extras michael@0: if (NS_FAILED(rv) && !aFileExt.IsEmpty()) { michael@0: rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval); michael@0: LOG(("Searched extras (by ext), rv 0x%08X\n", rv)); michael@0: } michael@0: // If that still didn't work, set the file description to "ext File" michael@0: if (NS_FAILED(rv) && !aFileExt.IsEmpty()) { michael@0: // XXXzpao This should probably be localized michael@0: nsAutoCString desc(aFileExt); michael@0: desc.Append(" File"); michael@0: (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc)); michael@0: LOG(("Falling back to 'File' file description\n")); michael@0: } michael@0: } michael@0: michael@0: // Finally, check if we got a file extension and if yes, if it is an michael@0: // extension on the mimeinfo, in which case we want it to be the primary one michael@0: if (!aFileExt.IsEmpty()) { michael@0: bool matches = false; michael@0: (*_retval)->ExtensionExists(aFileExt, &matches); michael@0: LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches)); michael@0: if (matches) michael@0: (*_retval)->SetPrimaryExtension(aFileExt); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString type; michael@0: (*_retval)->GetMIMEType(type); michael@0: michael@0: nsAutoCString ext; michael@0: (*_retval)->GetPrimaryExtension(ext); michael@0: LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get())); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType) michael@0: { michael@0: // OK. We want to try the following sources of mimetype information, in this order: michael@0: // 1. defaultMimeEntries array michael@0: // 2. User-set preferences (managed by the handler service) michael@0: // 3. OS-provided information michael@0: // 4. our "extras" array michael@0: // 5. Information from plugins michael@0: // 6. The "ext-to-type-mapping" category michael@0: michael@0: // Early return if called with an empty extension parameter michael@0: if (aFileExt.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult rv = NS_OK; michael@0: // First of all, check our default entries michael@0: for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++) michael@0: { michael@0: if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) { michael@0: aContentType = defaultMimeEntries[i].mMimeType; michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // Check user-set prefs michael@0: nsCOMPtr handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); michael@0: if (handlerSvc) michael@0: rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType); michael@0: if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: // Ask OS. michael@0: bool found = false; michael@0: nsCOMPtr mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found); michael@0: if (mi && found) michael@0: return mi->GetMIMEType(aContentType); michael@0: michael@0: // Check extras array. michael@0: found = GetTypeFromExtras(aFileExt, aContentType); michael@0: if (found) michael@0: return NS_OK; michael@0: michael@0: const nsCString& flatExt = PromiseFlatCString(aFileExt); michael@0: // Try the plugins michael@0: const char* mimeType; michael@0: nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv)); michael@0: nsPluginHost* pluginHost = static_cast(pluginHostCOM.get()); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) { michael@0: aContentType = mimeType; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: rv = NS_OK; michael@0: // Let's see if an extension added something michael@0: nsCOMPtr catMan(do_GetService("@mozilla.org/categorymanager;1")); michael@0: if (catMan) { michael@0: // The extension in the category entry is always stored as lowercase michael@0: nsAutoCString lowercaseFileExt(aFileExt); michael@0: ToLowerCase(lowercaseFileExt); michael@0: // Read the MIME type from the category entry, if available michael@0: nsXPIDLCString type; michael@0: rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(), michael@0: getter_Copies(type)); michael@0: aContentType = type; michael@0: } michael@0: else { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval) michael@0: { michael@0: NS_ENSURE_ARG(!aMIMEType.IsEmpty()); michael@0: michael@0: nsCOMPtr mi; michael@0: nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return mi->GetPrimaryExtension(_retval); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: nsresult rv = NS_ERROR_NOT_AVAILABLE; michael@0: aContentType.Truncate(); michael@0: michael@0: // First look for a file to use. If we have one, we just use that. michael@0: nsCOMPtr fileUrl = do_QueryInterface(aURI); michael@0: if (fileUrl) { michael@0: nsCOMPtr file; michael@0: rv = fileUrl->GetFile(getter_AddRefs(file)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = GetTypeFromFile(file, aContentType); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // we got something! michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Now try to get an nsIURL so we don't have to do our own parsing michael@0: nsCOMPtr url = do_QueryInterface(aURI); michael@0: if (url) { michael@0: nsAutoCString ext; michael@0: rv = url->GetFileExtension(ext); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (ext.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: UnescapeFragment(ext, url, ext); michael@0: michael@0: return GetTypeFromExtension(ext, aContentType); michael@0: } michael@0: michael@0: // no url, let's give the raw spec a shot michael@0: nsAutoCString specStr; michael@0: rv = aURI->GetSpec(specStr); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: UnescapeFragment(specStr, aURI, specStr); michael@0: michael@0: // find the file extension (if any) michael@0: int32_t extLoc = specStr.RFindChar('.'); michael@0: int32_t specLength = specStr.Length(); michael@0: if (-1 != extLoc && michael@0: extLoc != specLength - 1 && michael@0: // nothing over 20 chars long can be sanely considered an michael@0: // extension.... Dat dere would be just data. michael@0: specLength - extLoc < 20) michael@0: { michael@0: return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType); michael@0: } michael@0: michael@0: // We found no information; say so. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: nsresult rv; michael@0: nsCOMPtr info; michael@0: michael@0: // Get the Extension michael@0: nsAutoString fileName; michael@0: rv = aFile->GetLeafName(fileName); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsAutoCString fileExt; michael@0: if (!fileName.IsEmpty()) michael@0: { michael@0: int32_t len = fileName.Length(); michael@0: for (int32_t i = len; i >= 0; i--) michael@0: { michael@0: if (fileName[i] == char16_t('.')) michael@0: { michael@0: CopyUTF16toUTF8(fileName.get() + i + 1, fileExt); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (fileExt.IsEmpty()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return GetTypeFromExtension(fileExt, aContentType); michael@0: } michael@0: michael@0: nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras( michael@0: const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo) michael@0: { michael@0: NS_ENSURE_ARG( aMIMEInfo ); michael@0: michael@0: NS_ENSURE_ARG( !aContentType.IsEmpty() ); michael@0: michael@0: // Look for default entry with matching mime type. michael@0: nsAutoCString MIMEType(aContentType); michael@0: ToLowerCase(MIMEType); michael@0: int32_t numEntries = ArrayLength(extraMimeEntries); michael@0: for (int32_t index = 0; index < numEntries; index++) michael@0: { michael@0: if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) ) michael@0: { michael@0: // This is the one. Set attributes appropriately. michael@0: aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions)); michael@0: aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription)); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras( michael@0: const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo) michael@0: { michael@0: nsAutoCString type; michael@0: bool found = GetTypeFromExtras(aExtension, type); michael@0: if (!found) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo); michael@0: } michael@0: michael@0: bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType) michael@0: { michael@0: NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!"); michael@0: michael@0: // Look for default entry with matching extension. michael@0: nsDependentCString::const_iterator start, end, iter; michael@0: int32_t numEntries = ArrayLength(extraMimeEntries); michael@0: for (int32_t index = 0; index < numEntries; index++) michael@0: { michael@0: nsDependentCString extList(extraMimeEntries[index].mFileExtensions); michael@0: extList.BeginReading(start); michael@0: extList.EndReading(end); michael@0: iter = start; michael@0: while (start != end) michael@0: { michael@0: FindCharInReadable(',', iter, end); michael@0: if (Substring(start, iter).Equals(aExtension, michael@0: nsCaseInsensitiveCStringComparator())) michael@0: { michael@0: aMIMEType = extraMimeEntries[index].mMimeType; michael@0: return true; michael@0: } michael@0: if (iter != end) { michael@0: ++iter; michael@0: } michael@0: start = iter; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: }