1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2962 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim:expandtab:shiftwidth=2:tabstop=2:cin: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#ifdef MOZ_LOGGING 1.11 +#define FORCE_PR_LOG 1.12 +#endif 1.13 + 1.14 +#include "base/basictypes.h" 1.15 + 1.16 +/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */ 1.17 +#include "mozilla/ArrayUtils.h" 1.18 +#include "mozilla/Base64.h" 1.19 + 1.20 +#include "mozilla/dom/ContentChild.h" 1.21 +#include "mozilla/dom/TabChild.h" 1.22 +#include "nsXULAppAPI.h" 1.23 + 1.24 +#include "nsExternalHelperAppService.h" 1.25 +#include "nsCExternalHandlerService.h" 1.26 +#include "nsIURI.h" 1.27 +#include "nsIURL.h" 1.28 +#include "nsIFile.h" 1.29 +#include "nsIFileURL.h" 1.30 +#include "nsIChannel.h" 1.31 +#include "nsIDirectoryService.h" 1.32 +#include "nsAppDirectoryServiceDefs.h" 1.33 +#include "nsICategoryManager.h" 1.34 +#include "nsDependentSubstring.h" 1.35 +#include "nsXPIDLString.h" 1.36 +#include "nsUnicharUtils.h" 1.37 +#include "nsIStringEnumerator.h" 1.38 +#include "nsMemory.h" 1.39 +#include "nsIStreamListener.h" 1.40 +#include "nsIMIMEService.h" 1.41 +#include "nsILoadGroup.h" 1.42 +#include "nsIWebProgressListener.h" 1.43 +#include "nsITransfer.h" 1.44 +#include "nsReadableUtils.h" 1.45 +#include "nsIRequest.h" 1.46 +#include "nsDirectoryServiceDefs.h" 1.47 +#include "nsIInterfaceRequestor.h" 1.48 +#include "nsThreadUtils.h" 1.49 +#include "nsAutoPtr.h" 1.50 +#include "nsIMutableArray.h" 1.51 + 1.52 +// used to access our datastore of user-configured helper applications 1.53 +#include "nsIHandlerService.h" 1.54 +#include "nsIMIMEInfo.h" 1.55 +#include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI 1.56 +#include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri 1.57 +#include "nsIHelperAppLauncherDialog.h" 1.58 +#include "nsIContentDispatchChooser.h" 1.59 +#include "nsNetUtil.h" 1.60 +#include "nsIIOService.h" 1.61 +#include "nsNetCID.h" 1.62 +#include "nsChannelProperties.h" 1.63 + 1.64 +#include "nsMimeTypes.h" 1.65 +// used for header disposition information. 1.66 +#include "nsIHttpChannel.h" 1.67 +#include "nsIHttpChannelInternal.h" 1.68 +#include "nsIEncodedChannel.h" 1.69 +#include "nsIMultiPartChannel.h" 1.70 +#include "nsIFileChannel.h" 1.71 +#include "nsIObserverService.h" // so we can be a profile change observer 1.72 +#include "nsIPropertyBag2.h" // for the 64-bit content length 1.73 + 1.74 +#ifdef XP_MACOSX 1.75 +#include "nsILocalFileMac.h" 1.76 +#endif 1.77 + 1.78 +#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289) 1.79 +#include "nsPluginHost.h" 1.80 +#include "nsEscape.h" 1.81 + 1.82 +#include "nsIStringBundle.h" // XXX needed to localize error msgs 1.83 +#include "nsIPrompt.h" 1.84 + 1.85 +#include "nsITextToSubURI.h" // to unescape the filename 1.86 +#include "nsIMIMEHeaderParam.h" 1.87 + 1.88 +#include "nsIWindowWatcher.h" 1.89 + 1.90 +#include "nsIDownloadHistory.h" // to mark downloads as visited 1.91 +#include "nsDocShellCID.h" 1.92 + 1.93 +#include "nsCRT.h" 1.94 +#include "nsLocalHandlerApp.h" 1.95 + 1.96 +#include "nsIRandomGenerator.h" 1.97 + 1.98 +#include "ContentChild.h" 1.99 +#include "nsXULAppAPI.h" 1.100 +#include "nsPIDOMWindow.h" 1.101 +#include "nsIDocShellTreeOwner.h" 1.102 +#include "nsIDocShellTreeItem.h" 1.103 +#include "ExternalHelperAppChild.h" 1.104 + 1.105 +#ifdef XP_WIN 1.106 +#include "nsWindowsHelpers.h" 1.107 +#endif 1.108 + 1.109 +#ifdef MOZ_WIDGET_ANDROID 1.110 +#include "AndroidBridge.h" 1.111 +#endif 1.112 + 1.113 +#include "mozilla/Preferences.h" 1.114 +#include "mozilla/ipc/URIUtils.h" 1.115 + 1.116 +#ifdef MOZ_WIDGET_GONK 1.117 +#include "nsDeviceStorage.h" 1.118 +#endif 1.119 + 1.120 +#ifdef NECKO_PROTOCOL_rtsp 1.121 +#include "nsIScriptSecurityManager.h" 1.122 +#include "nsIMessageManager.h" 1.123 +#endif 1.124 + 1.125 +using namespace mozilla; 1.126 +using namespace mozilla::ipc; 1.127 + 1.128 +// Download Folder location constants 1.129 +#define NS_PREF_DOWNLOAD_DIR "browser.download.dir" 1.130 +#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList" 1.131 +enum { 1.132 + NS_FOLDER_VALUE_DESKTOP = 0 1.133 +, NS_FOLDER_VALUE_DOWNLOADS = 1 1.134 +, NS_FOLDER_VALUE_CUSTOM = 2 1.135 +}; 1.136 + 1.137 +#ifdef PR_LOGGING 1.138 +PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr; 1.139 +#endif 1.140 + 1.141 +// Using level 3 here because the OSHelperAppServices use a log level 1.142 +// of PR_LOG_DEBUG (4), and we want less detailed output here 1.143 +// Using 3 instead of PR_LOG_WARN because we don't output warnings 1.144 +#undef LOG 1.145 +#define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args) 1.146 +#define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3) 1.147 + 1.148 +static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] = 1.149 + "browser.helperApps.neverAsk.saveToDisk"; 1.150 +static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] = 1.151 + "browser.helperApps.neverAsk.openFile"; 1.152 + 1.153 +// Helper functions for Content-Disposition headers 1.154 + 1.155 +/** 1.156 + * Given a URI fragment, unescape it 1.157 + * @param aFragment The string to unescape 1.158 + * @param aURI The URI from which this fragment is taken. Only its character set 1.159 + * will be used. 1.160 + * @param aResult [out] Unescaped string. 1.161 + */ 1.162 +static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI, 1.163 + nsAString& aResult) 1.164 +{ 1.165 + // First, we need a charset 1.166 + nsAutoCString originCharset; 1.167 + nsresult rv = aURI->GetOriginCharset(originCharset); 1.168 + NS_ENSURE_SUCCESS(rv, rv); 1.169 + 1.170 + // Now, we need the unescaper 1.171 + nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); 1.172 + NS_ENSURE_SUCCESS(rv, rv); 1.173 + 1.174 + return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult); 1.175 +} 1.176 + 1.177 +/** 1.178 + * UTF-8 version of UnescapeFragment. 1.179 + * @param aFragment The string to unescape 1.180 + * @param aURI The URI from which this fragment is taken. Only its character set 1.181 + * will be used. 1.182 + * @param aResult [out] Unescaped string, UTF-8 encoded. 1.183 + * @note It is safe to pass the same string for aFragment and aResult. 1.184 + * @note When this function fails, aResult will not be modified. 1.185 + */ 1.186 +static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI, 1.187 + nsACString& aResult) 1.188 +{ 1.189 + nsAutoString result; 1.190 + nsresult rv = UnescapeFragment(aFragment, aURI, result); 1.191 + if (NS_SUCCEEDED(rv)) 1.192 + CopyUTF16toUTF8(result, aResult); 1.193 + return rv; 1.194 +} 1.195 + 1.196 +/** 1.197 + * Given a channel, returns the filename and extension the channel has. 1.198 + * This uses the URL and other sources (nsIMultiPartChannel). 1.199 + * Also gives back whether the channel requested external handling (i.e. 1.200 + * whether Content-Disposition: attachment was sent) 1.201 + * @param aChannel The channel to extract the filename/extension from 1.202 + * @param aFileName [out] Reference to the string where the filename should be 1.203 + * stored. Empty if it could not be retrieved. 1.204 + * WARNING - this filename may contain characters which the OS does not 1.205 + * allow as part of filenames! 1.206 + * @param aExtension [out] Reference to the string where the extension should 1.207 + * be stored. Empty if it could not be retrieved. Stored in UTF-8. 1.208 + * @param aAllowURLExtension (optional) Get the extension from the URL if no 1.209 + * Content-Disposition header is present. Default is true. 1.210 + * @retval true The server sent Content-Disposition:attachment or equivalent 1.211 + * @retval false Content-Disposition: inline or no content-disposition header 1.212 + * was sent. 1.213 + */ 1.214 +static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel, 1.215 + nsString& aFileName, 1.216 + nsCString& aExtension, 1.217 + bool aAllowURLExtension = true) 1.218 +{ 1.219 + aExtension.Truncate(); 1.220 + /* 1.221 + * If the channel is an http or part of a multipart channel and we 1.222 + * have a content disposition header set, then use the file name 1.223 + * suggested there as the preferred file name to SUGGEST to the 1.224 + * user. we shouldn't actually use that without their 1.225 + * permission... otherwise just use our temp file 1.226 + */ 1.227 + bool handleExternally = false; 1.228 + uint32_t disp; 1.229 + nsresult rv = aChannel->GetContentDisposition(&disp); 1.230 + if (NS_SUCCEEDED(rv)) 1.231 + { 1.232 + aChannel->GetContentDispositionFilename(aFileName); 1.233 + if (disp == nsIChannel::DISPOSITION_ATTACHMENT) 1.234 + handleExternally = true; 1.235 + } 1.236 + 1.237 + // If the disposition header didn't work, try the filename from nsIURL 1.238 + nsCOMPtr<nsIURI> uri; 1.239 + aChannel->GetURI(getter_AddRefs(uri)); 1.240 + nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); 1.241 + if (url && aFileName.IsEmpty()) 1.242 + { 1.243 + if (aAllowURLExtension) { 1.244 + url->GetFileExtension(aExtension); 1.245 + UnescapeFragment(aExtension, url, aExtension); 1.246 + 1.247 + // Windows ignores terminating dots. So we have to as well, so 1.248 + // that our security checks do "the right thing" 1.249 + // In case the aExtension consisted only of the dot, the code below will 1.250 + // extract an aExtension from the filename 1.251 + aExtension.Trim(".", false); 1.252 + } 1.253 + 1.254 + // try to extract the file name from the url and use that as a first pass as the 1.255 + // leaf name of our temp file... 1.256 + nsAutoCString leafName; 1.257 + url->GetFileName(leafName); 1.258 + if (!leafName.IsEmpty()) 1.259 + { 1.260 + rv = UnescapeFragment(leafName, url, aFileName); 1.261 + if (NS_FAILED(rv)) 1.262 + { 1.263 + CopyUTF8toUTF16(leafName, aFileName); // use escaped name 1.264 + } 1.265 + } 1.266 + } 1.267 + 1.268 + // Extract Extension, if we have a filename; otherwise, 1.269 + // truncate the string 1.270 + if (aExtension.IsEmpty()) { 1.271 + if (!aFileName.IsEmpty()) 1.272 + { 1.273 + // Windows ignores terminating dots. So we have to as well, so 1.274 + // that our security checks do "the right thing" 1.275 + aFileName.Trim(".", false); 1.276 + 1.277 + // XXX RFindCharInReadable!! 1.278 + nsAutoString fileNameStr(aFileName); 1.279 + int32_t idx = fileNameStr.RFindChar(char16_t('.')); 1.280 + if (idx != kNotFound) 1.281 + CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension); 1.282 + } 1.283 + } 1.284 + 1.285 + 1.286 + return handleExternally; 1.287 +} 1.288 + 1.289 +/** 1.290 + * Obtains the directory to use. This tends to vary per platform, and 1.291 + * needs to be consistent throughout our codepaths. For platforms where 1.292 + * helper apps use the downloads directory, this should be kept in 1.293 + * sync with nsDownloadManager.cpp 1.294 + * 1.295 + * Optionally skip availability of the directory and storage. 1.296 + */ 1.297 +static nsresult GetDownloadDirectory(nsIFile **_directory, 1.298 + bool aSkipChecks = false) 1.299 +{ 1.300 + nsCOMPtr<nsIFile> dir; 1.301 +#ifdef XP_MACOSX 1.302 + // On OS X, we first try to get the users download location, if it's set. 1.303 + switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) { 1.304 + case NS_FOLDER_VALUE_DESKTOP: 1.305 + (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir)); 1.306 + break; 1.307 + case NS_FOLDER_VALUE_CUSTOM: 1.308 + { 1.309 + Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, 1.310 + NS_GET_IID(nsIFile), 1.311 + getter_AddRefs(dir)); 1.312 + if (!dir) break; 1.313 + 1.314 + // If we're not checking for availability we're done. 1.315 + if (aSkipChecks) { 1.316 + dir.forget(_directory); 1.317 + return NS_OK; 1.318 + } 1.319 + 1.320 + // We have the directory, and now we need to make sure it exists 1.321 + bool dirExists = false; 1.322 + (void) dir->Exists(&dirExists); 1.323 + if (dirExists) break; 1.324 + 1.325 + nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755); 1.326 + if (NS_FAILED(rv)) { 1.327 + dir = nullptr; 1.328 + break; 1.329 + } 1.330 + } 1.331 + break; 1.332 + case NS_FOLDER_VALUE_DOWNLOADS: 1.333 + // This is just the OS default location, so fall out 1.334 + break; 1.335 + } 1.336 + 1.337 + if (!dir) { 1.338 + // If not, we default to the OS X default download location. 1.339 + nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR, 1.340 + getter_AddRefs(dir)); 1.341 + NS_ENSURE_SUCCESS(rv, rv); 1.342 + } 1.343 +#elif defined(MOZ_WIDGET_GONK) 1.344 + // On Gonk, store the files on the sdcard in the downloads directory. 1.345 + // We need to check with the volume manager which storage point is 1.346 + // available. 1.347 + 1.348 + // Pick the default storage in case multiple (internal and external) ones 1.349 + // are available. 1.350 + nsString storageName; 1.351 + nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"), 1.352 + storageName); 1.353 + 1.354 + DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"), 1.355 + storageName, 1.356 + NS_LITERAL_STRING("downloads")); 1.357 + NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED); 1.358 + 1.359 + // If we're not checking for availability we're done. 1.360 + if (aSkipChecks) { 1.361 + dsf.mFile.forget(_directory); 1.362 + return NS_OK; 1.363 + } 1.364 + 1.365 + // Check device storage status before continuing. 1.366 + nsString storageStatus; 1.367 + dsf.GetStatus(storageStatus); 1.368 + 1.369 + // If we get an "unavailable" status, it means the sd card is not present. 1.370 + // We'll also catch internal errors by looking for an empty string and assume 1.371 + // the SD card isn't present when this occurs. 1.372 + if (storageStatus.EqualsLiteral("unavailable") || 1.373 + storageStatus.IsEmpty()) { 1.374 + return NS_ERROR_FILE_NOT_FOUND; 1.375 + } 1.376 + 1.377 + // If we get a status other than 'available' here it means the card is busy 1.378 + // because it's mounted via USB or it is being formatted. 1.379 + if (!storageStatus.EqualsLiteral("available")) { 1.380 + return NS_ERROR_FILE_ACCESS_DENIED; 1.381 + } 1.382 + 1.383 + bool alreadyThere; 1.384 + nsresult rv = dsf.mFile->Exists(&alreadyThere); 1.385 + NS_ENSURE_SUCCESS(rv, rv); 1.386 + if (!alreadyThere) { 1.387 + rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770); 1.388 + NS_ENSURE_SUCCESS(rv, rv); 1.389 + } 1.390 + dir = dsf.mFile; 1.391 +#elif defined(ANDROID) 1.392 + // On mobile devices, we are avoiding exposing users to the file 1.393 + // system, and don't save downloads to temp directories 1.394 + 1.395 + // On Android we only return something if we have and SD-card 1.396 + char* downloadDir = getenv("DOWNLOADS_DIRECTORY"); 1.397 + nsresult rv; 1.398 + if (downloadDir) { 1.399 + nsCOMPtr<nsIFile> ldir; 1.400 + rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir), 1.401 + true, getter_AddRefs(ldir)); 1.402 + NS_ENSURE_SUCCESS(rv, rv); 1.403 + dir = do_QueryInterface(ldir); 1.404 + 1.405 + // If we're not checking for availability we're done. 1.406 + if (aSkipChecks) { 1.407 + dir.forget(_directory); 1.408 + return NS_OK; 1.409 + } 1.410 + } 1.411 + else { 1.412 + return NS_ERROR_FAILURE; 1.413 + } 1.414 +#elif defined(XP_WIN) 1.415 + // On metro we want to be able to search opened files and the temp directory 1.416 + // is exlcuded in searches. 1.417 + nsresult rv; 1.418 + if (IsRunningInWindowsMetro()) { 1.419 + rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir)); 1.420 + } else { 1.421 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir)); 1.422 + } 1.423 + NS_ENSURE_SUCCESS(rv, rv); 1.424 +#else 1.425 + // On all other platforms, we default to the systems temporary directory. 1.426 + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir)); 1.427 + NS_ENSURE_SUCCESS(rv, rv); 1.428 +#endif 1.429 + 1.430 + NS_ASSERTION(dir, "Somehow we didn't get a download directory!"); 1.431 + dir.forget(_directory); 1.432 + return NS_OK; 1.433 +} 1.434 + 1.435 +/** 1.436 + * Structure for storing extension->type mappings. 1.437 + * @see defaultMimeEntries 1.438 + */ 1.439 +struct nsDefaultMimeTypeEntry { 1.440 + const char* mMimeType; 1.441 + const char* mFileExtension; 1.442 +}; 1.443 + 1.444 +/** 1.445 + * Default extension->mimetype mappings. These are not overridable. 1.446 + * If you add types here, make sure they are lowercase, or you'll regret it. 1.447 + */ 1.448 +static nsDefaultMimeTypeEntry defaultMimeEntries [] = 1.449 +{ 1.450 + // The following are those extensions that we're asked about during startup, 1.451 + // sorted by order used 1.452 + { IMAGE_GIF, "gif" }, 1.453 + { TEXT_XML, "xml" }, 1.454 + { APPLICATION_RDF, "rdf" }, 1.455 + { TEXT_XUL, "xul" }, 1.456 + { IMAGE_PNG, "png" }, 1.457 + // -- end extensions used during startup 1.458 + { TEXT_CSS, "css" }, 1.459 + { IMAGE_JPEG, "jpeg" }, 1.460 + { IMAGE_JPEG, "jpg" }, 1.461 + { IMAGE_SVG_XML, "svg" }, 1.462 + { TEXT_HTML, "html" }, 1.463 + { TEXT_HTML, "htm" }, 1.464 + { APPLICATION_XPINSTALL, "xpi" }, 1.465 + { "application/xhtml+xml", "xhtml" }, 1.466 + { "application/xhtml+xml", "xht" }, 1.467 + { TEXT_PLAIN, "txt" }, 1.468 + { VIDEO_OGG, "ogv" }, 1.469 + { VIDEO_OGG, "ogg" }, 1.470 + { APPLICATION_OGG, "ogg" }, 1.471 + { AUDIO_OGG, "oga" }, 1.472 +#ifdef MOZ_OPUS 1.473 + { AUDIO_OGG, "opus" }, 1.474 +#endif 1.475 +#ifdef MOZ_WEBM 1.476 + { VIDEO_WEBM, "webm" }, 1.477 + { AUDIO_WEBM, "webm" }, 1.478 +#endif 1.479 +#if defined(MOZ_GSTREAMER) || defined(MOZ_WMF) 1.480 + { VIDEO_MP4, "mp4" }, 1.481 + { AUDIO_MP4, "m4a" }, 1.482 + { AUDIO_MP3, "mp3" }, 1.483 +#endif 1.484 +#ifdef MOZ_RAW 1.485 + { VIDEO_RAW, "yuv" } 1.486 +#endif 1.487 +}; 1.488 + 1.489 +/** 1.490 + * This is a small private struct used to help us initialize some 1.491 + * default mime types. 1.492 + */ 1.493 +struct nsExtraMimeTypeEntry { 1.494 + const char* mMimeType; 1.495 + const char* mFileExtensions; 1.496 + const char* mDescription; 1.497 +}; 1.498 + 1.499 +#ifdef XP_MACOSX 1.500 +#define MAC_TYPE(x) x 1.501 +#else 1.502 +#define MAC_TYPE(x) 0 1.503 +#endif 1.504 + 1.505 +/** 1.506 + * This table lists all of the 'extra' content types that we can deduce from particular 1.507 + * file extensions. These entries also ensure that we provide a good descriptive name 1.508 + * when we encounter files with these content types and/or extensions. These can be 1.509 + * overridden by user helper app prefs. 1.510 + * If you add types here, make sure they are lowercase, or you'll regret it. 1.511 + */ 1.512 +static nsExtraMimeTypeEntry extraMimeEntries [] = 1.513 +{ 1.514 +#if defined(VMS) 1.515 + { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" }, 1.516 +#elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up... 1.517 + { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" }, 1.518 +#else 1.519 + { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" }, 1.520 +#endif 1.521 + { APPLICATION_GZIP2, "gz", "gzip" }, 1.522 + { "application/x-arj", "arj", "ARJ file" }, 1.523 + { "application/rtf", "rtf", "Rich Text Format File" }, 1.524 + { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" }, 1.525 + { APPLICATION_PDF, "pdf", "Portable Document Format" }, 1.526 + { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" }, 1.527 + { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" }, 1.528 + { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" }, 1.529 +#ifdef MOZ_WIDGET_ANDROID 1.530 + { "application/vnd.android.package-archive", "apk", "Android Package" }, 1.531 +#endif 1.532 + { IMAGE_ART, "art", "ART Image" }, 1.533 + { IMAGE_BMP, "bmp", "BMP Image" }, 1.534 + { IMAGE_GIF, "gif", "GIF Image" }, 1.535 + { IMAGE_ICO, "ico,cur", "ICO Image" }, 1.536 + { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" }, 1.537 + { IMAGE_PNG, "png", "PNG Image" }, 1.538 + { IMAGE_TIFF, "tiff,tif", "TIFF Image" }, 1.539 + { IMAGE_XBM, "xbm", "XBM Image" }, 1.540 + { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" }, 1.541 + { MESSAGE_RFC822, "eml", "RFC-822 data" }, 1.542 + { TEXT_PLAIN, "txt,text", "Text File" }, 1.543 + { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" }, 1.544 + { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" }, 1.545 + { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" }, 1.546 + { APPLICATION_RDF, "rdf", "Resource Description Framework" }, 1.547 + { TEXT_XUL, "xul", "XML-Based User Interface Language" }, 1.548 + { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" }, 1.549 + { TEXT_CSS, "css", "Style Sheet" }, 1.550 + { TEXT_VCARD, "vcf,vcard", "Contact Information" }, 1.551 + { VIDEO_OGG, "ogv", "Ogg Video" }, 1.552 + { VIDEO_OGG, "ogg", "Ogg Video" }, 1.553 + { APPLICATION_OGG, "ogg", "Ogg Video"}, 1.554 + { AUDIO_OGG, "oga", "Ogg Audio" }, 1.555 + { AUDIO_OGG, "opus", "Opus Audio" }, 1.556 +#ifdef MOZ_WIDGET_GONK 1.557 + { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" }, 1.558 +#endif 1.559 + { VIDEO_WEBM, "webm", "Web Media Video" }, 1.560 + { AUDIO_WEBM, "webm", "Web Media Audio" }, 1.561 + { AUDIO_MP3, "mp3", "MPEG Audio" }, 1.562 + { VIDEO_MP4, "mp4", "MPEG-4 Video" }, 1.563 + { AUDIO_MP4, "m4a", "MPEG-4 Audio" }, 1.564 + { VIDEO_RAW, "yuv", "Raw YUV Video" }, 1.565 + { AUDIO_WAV, "wav", "Waveform Audio" }, 1.566 + { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" }, 1.567 + { AUDIO_MIDI, "mid", "Standard MIDI Audio" } 1.568 +}; 1.569 + 1.570 +#undef MAC_TYPE 1.571 + 1.572 +/** 1.573 + * File extensions for which decoding should be disabled. 1.574 + * NOTE: These MUST be lower-case and ASCII. 1.575 + */ 1.576 +static nsDefaultMimeTypeEntry nonDecodableExtensions [] = { 1.577 + { APPLICATION_GZIP, "gz" }, 1.578 + { APPLICATION_GZIP, "tgz" }, 1.579 + { APPLICATION_ZIP, "zip" }, 1.580 + { APPLICATION_COMPRESS, "z" }, 1.581 + { APPLICATION_GZIP, "svgz" } 1.582 +}; 1.583 + 1.584 +NS_IMPL_ISUPPORTS( 1.585 + nsExternalHelperAppService, 1.586 + nsIExternalHelperAppService, 1.587 + nsPIExternalAppLauncher, 1.588 + nsIExternalProtocolService, 1.589 + nsIMIMEService, 1.590 + nsIObserver, 1.591 + nsISupportsWeakReference) 1.592 + 1.593 +nsExternalHelperAppService::nsExternalHelperAppService() 1.594 +{ 1.595 +} 1.596 +nsresult nsExternalHelperAppService::Init() 1.597 +{ 1.598 + // Add an observer for profile change 1.599 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.600 + if (!obs) 1.601 + return NS_ERROR_FAILURE; 1.602 + 1.603 +#ifdef PR_LOGGING 1.604 + if (!mLog) { 1.605 + mLog = PR_NewLogModule("HelperAppService"); 1.606 + if (!mLog) 1.607 + return NS_ERROR_OUT_OF_MEMORY; 1.608 + } 1.609 +#endif 1.610 + 1.611 + nsresult rv = obs->AddObserver(this, "profile-before-change", true); 1.612 + NS_ENSURE_SUCCESS(rv, rv); 1.613 + return obs->AddObserver(this, "last-pb-context-exited", true); 1.614 +} 1.615 + 1.616 +nsExternalHelperAppService::~nsExternalHelperAppService() 1.617 +{ 1.618 +} 1.619 + 1.620 +#ifdef NECKO_PROTOCOL_rtsp 1.621 +namespace { 1.622 +/** 1.623 + * A stack helper to clear the currently pending exception in a JS context. 1.624 + */ 1.625 +class AutoClearPendingException { 1.626 +public: 1.627 + AutoClearPendingException(JSContext* aCx) : 1.628 + mCx(aCx) { 1.629 + } 1.630 + ~AutoClearPendingException() { 1.631 + JS_ClearPendingException(mCx); 1.632 + } 1.633 +private: 1.634 + JSContext *mCx; 1.635 +}; 1.636 +} // anonymous namespace 1.637 + 1.638 +/** 1.639 + * This function sends a message. This 'content-handler' message is handled in 1.640 + * b2g/chrome/content/shell.js where it starts an activity request that will 1.641 + * open the video app. 1.642 + */ 1.643 +void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI) 1.644 +{ 1.645 + bool rv; 1.646 + 1.647 + // Get a system principal. 1.648 + nsCOMPtr<nsIScriptSecurityManager> securityManager = 1.649 + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); 1.650 + NS_ENSURE_TRUE_VOID(securityManager); 1.651 + 1.652 + nsCOMPtr<nsIPrincipal> principal; 1.653 + securityManager->GetSystemPrincipal(getter_AddRefs(principal)); 1.654 + NS_ENSURE_TRUE_VOID(principal); 1.655 + 1.656 + // Construct the message in jsVal format. 1.657 + AutoSafeJSContext cx; 1.658 + AutoClearPendingException helper(cx); 1.659 + JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); 1.660 + NS_ENSURE_TRUE_VOID(msgObj); 1.661 + JS::Rooted<JS::Value> jsVal(cx); 1.662 + 1.663 + // Set the "type" property of the message. This is a fake MIME type. 1.664 + { 1.665 + NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp"); 1.666 + JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length()); 1.667 + NS_ENSURE_TRUE_VOID(typeStr); 1.668 + jsVal.setString(typeStr); 1.669 + rv = JS_SetProperty(cx, msgObj, "type", jsVal); 1.670 + NS_ENSURE_TRUE_VOID(rv); 1.671 + } 1.672 + // Set the "url" and "title" properties of the message. 1.673 + // They are the same in the case of RTSP streaming. 1.674 + { 1.675 + nsAutoCString spec; 1.676 + aURI->GetSpec(spec); 1.677 + JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length()); 1.678 + NS_ENSURE_TRUE_VOID(urlStr); 1.679 + jsVal.setString(urlStr); 1.680 + rv = JS_SetProperty(cx, msgObj, "url", jsVal); 1.681 + NS_ENSURE_TRUE_VOID(rv); 1.682 + rv = JS_SetProperty(cx, msgObj, "title", jsVal); 1.683 + } 1.684 + jsVal.setObject(*msgObj); 1.685 + 1.686 + // Send the message. 1.687 + nsCOMPtr<nsIMessageSender> cpmm = 1.688 + do_GetService("@mozilla.org/childprocessmessagemanager;1"); 1.689 + NS_ENSURE_TRUE_VOID(cpmm); 1.690 + cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"), 1.691 + jsVal, JS::NullHandleValue, principal, cx, 2); 1.692 +} 1.693 +#endif 1.694 + 1.695 +NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType, 1.696 + nsIRequest *aRequest, 1.697 + nsIInterfaceRequestor *aWindowContext, 1.698 + bool aForceSave, 1.699 + nsIStreamListener ** aStreamListener) 1.700 +{ 1.701 + nsAutoString fileName; 1.702 + nsAutoCString fileExtension; 1.703 + uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE; 1.704 + uint32_t contentDisposition = -1; 1.705 + 1.706 + nsresult rv; 1.707 + 1.708 + // Get the file extension and name that we will need later 1.709 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 1.710 + nsCOMPtr<nsIURI> uri; 1.711 + int64_t contentLength = -1; 1.712 + if (channel) { 1.713 + channel->GetURI(getter_AddRefs(uri)); 1.714 + channel->GetContentLength(&contentLength); 1.715 + channel->GetContentDisposition(&contentDisposition); 1.716 + channel->GetContentDispositionFilename(fileName); 1.717 + } 1.718 + 1.719 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.720 + nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext); 1.721 + NS_ENSURE_STATE(window); 1.722 + 1.723 + // We need to get a hold of a ContentChild so that we can begin forwarding 1.724 + // this data to the parent. In the HTTP case, this is unfortunate, since 1.725 + // we're actually passing data from parent->child->parent wastefully, but 1.726 + // the Right Fix will eventually be to short-circuit those channels on the 1.727 + // parent side based on some sort of subscription concept. 1.728 + using mozilla::dom::ContentChild; 1.729 + using mozilla::dom::ExternalHelperAppChild; 1.730 + ContentChild *child = ContentChild::GetSingleton(); 1.731 + if (!child) 1.732 + return NS_ERROR_FAILURE; 1.733 + 1.734 + nsCString disp; 1.735 + if (channel) { 1.736 + channel->GetContentDispositionHeader(disp); 1.737 + } 1.738 + 1.739 + nsCOMPtr<nsIURI> referrer; 1.740 + rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer)); 1.741 + 1.742 + OptionalURIParams uriParams, referrerParams; 1.743 + SerializeURI(uri, uriParams); 1.744 + SerializeURI(referrer, referrerParams); 1.745 + 1.746 + // Now we build a protocol for forwarding our data to the parent. The 1.747 + // protocol will act as a listener on the child-side and create a "real" 1.748 + // helperAppService listener on the parent-side, via another call to 1.749 + // DoContent. 1.750 + mozilla::dom::PExternalHelperAppChild *pc = 1.751 + child->SendPExternalHelperAppConstructor(uriParams, 1.752 + nsCString(aMimeContentType), 1.753 + disp, contentDisposition, 1.754 + fileName, aForceSave, 1.755 + contentLength, referrerParams, 1.756 + mozilla::dom::TabChild::GetFrom(window)); 1.757 + ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc); 1.758 + 1.759 + NS_ADDREF(*aStreamListener = childListener); 1.760 + 1.761 + nsRefPtr<nsExternalAppHandler> handler = 1.762 + new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this, 1.763 + fileName, 1.764 + reason, aForceSave); 1.765 + if (!handler) 1.766 + return NS_ERROR_OUT_OF_MEMORY; 1.767 + 1.768 + childListener->SetHandler(handler); 1.769 + return NS_OK; 1.770 + } 1.771 + 1.772 + if (channel) { 1.773 + // Check if we have a POST request, in which case we don't want to use 1.774 + // the url's extension 1.775 + bool allowURLExt = true; 1.776 + nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel); 1.777 + if (httpChan) { 1.778 + nsAutoCString requestMethod; 1.779 + httpChan->GetRequestMethod(requestMethod); 1.780 + allowURLExt = !requestMethod.Equals("POST"); 1.781 + } 1.782 + 1.783 + // Check if we had a query string - we don't want to check the URL 1.784 + // extension if a query is present in the URI 1.785 + // If we already know we don't want to check the URL extension, don't 1.786 + // bother checking the query 1.787 + if (uri && allowURLExt) { 1.788 + nsCOMPtr<nsIURL> url = do_QueryInterface(uri); 1.789 + 1.790 + if (url) { 1.791 + nsAutoCString query; 1.792 + 1.793 + // We only care about the query for HTTP and HTTPS URLs 1.794 + bool isHTTP, isHTTPS; 1.795 + rv = uri->SchemeIs("http", &isHTTP); 1.796 + if (NS_FAILED(rv)) 1.797 + isHTTP = false; 1.798 + rv = uri->SchemeIs("https", &isHTTPS); 1.799 + if (NS_FAILED(rv)) 1.800 + isHTTPS = false; 1.801 + 1.802 + if (isHTTP || isHTTPS) 1.803 + url->GetQuery(query); 1.804 + 1.805 + // Only get the extension if the query is empty; if it isn't, then the 1.806 + // extension likely belongs to a cgi script and isn't helpful 1.807 + allowURLExt = query.IsEmpty(); 1.808 + } 1.809 + } 1.810 + // Extract name & extension 1.811 + bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName, 1.812 + fileExtension, 1.813 + allowURLExt); 1.814 + LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)", 1.815 + fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(), 1.816 + isAttachment)); 1.817 + if (isAttachment) 1.818 + reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST; 1.819 + } 1.820 + 1.821 + LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n", 1.822 + PromiseFlatCString(aMimeContentType).get(), fileExtension.get())); 1.823 + 1.824 + // we get the mime service here even though we're the default implementation of it, 1.825 + // so it's possible to override only the mime service and not need to reimplement the 1.826 + // whole external helper app service itself 1.827 + nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID)); 1.828 + NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE); 1.829 + 1.830 + // Try to find a mime object by looking at the mime type/extension 1.831 + nsCOMPtr<nsIMIMEInfo> mimeInfo; 1.832 + if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) { 1.833 + nsAutoCString mimeType; 1.834 + if (!fileExtension.IsEmpty()) { 1.835 + mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo)); 1.836 + if (mimeInfo) { 1.837 + mimeInfo->GetMIMEType(mimeType); 1.838 + 1.839 + LOG(("OS-Provided mime type '%s' for extension '%s'\n", 1.840 + mimeType.get(), fileExtension.get())); 1.841 + } 1.842 + } 1.843 + 1.844 + if (fileExtension.IsEmpty() || mimeType.IsEmpty()) { 1.845 + // Extension lookup gave us no useful match 1.846 + mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension, 1.847 + getter_AddRefs(mimeInfo)); 1.848 + mimeType.AssignLiteral(APPLICATION_OCTET_STREAM); 1.849 + } 1.850 + if (channel) 1.851 + channel->SetContentType(mimeType); 1.852 + // Don't overwrite SERVERREQUEST 1.853 + if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) 1.854 + reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED; 1.855 + } 1.856 + else { 1.857 + mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension, 1.858 + getter_AddRefs(mimeInfo)); 1.859 + } 1.860 + LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get())); 1.861 + 1.862 + // No mimeinfo -> we can't continue. probably OOM. 1.863 + if (!mimeInfo) 1.864 + return NS_ERROR_OUT_OF_MEMORY; 1.865 + 1.866 + *aStreamListener = nullptr; 1.867 + // We want the mimeInfo's primary extension to pass it to 1.868 + // nsExternalAppHandler 1.869 + nsAutoCString buf; 1.870 + mimeInfo->GetPrimaryExtension(buf); 1.871 + 1.872 + nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo, 1.873 + buf, 1.874 + aWindowContext, 1.875 + this, 1.876 + fileName, 1.877 + reason, 1.878 + aForceSave); 1.879 + if (!handler) 1.880 + return NS_ERROR_OUT_OF_MEMORY; 1.881 + NS_ADDREF(*aStreamListener = handler); 1.882 + 1.883 + return NS_OK; 1.884 +} 1.885 + 1.886 +NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension, 1.887 + const nsACString& aEncodingType, 1.888 + bool *aApplyDecoding) 1.889 +{ 1.890 + *aApplyDecoding = true; 1.891 + uint32_t i; 1.892 + for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) { 1.893 + if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) && 1.894 + aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) { 1.895 + *aApplyDecoding = false; 1.896 + break; 1.897 + } 1.898 + } 1.899 + return NS_OK; 1.900 +} 1.901 + 1.902 +nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath, 1.903 + nsIFile ** aFile) 1.904 +{ 1.905 + nsDependentString platformAppPath(aPlatformAppPath); 1.906 + // First, check if we have an absolute path 1.907 + nsIFile* localFile = nullptr; 1.908 + nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile); 1.909 + if (NS_SUCCEEDED(rv)) { 1.910 + *aFile = localFile; 1.911 + bool exists; 1.912 + if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) { 1.913 + NS_RELEASE(*aFile); 1.914 + return NS_ERROR_FILE_NOT_FOUND; 1.915 + } 1.916 + return NS_OK; 1.917 + } 1.918 + 1.919 + 1.920 + // Second, check if file exists in mozilla program directory 1.921 + rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile); 1.922 + if (NS_SUCCEEDED(rv)) { 1.923 + rv = (*aFile)->Append(platformAppPath); 1.924 + if (NS_SUCCEEDED(rv)) { 1.925 + bool exists = false; 1.926 + rv = (*aFile)->Exists(&exists); 1.927 + if (NS_SUCCEEDED(rv) && exists) 1.928 + return NS_OK; 1.929 + } 1.930 + NS_RELEASE(*aFile); 1.931 + } 1.932 + 1.933 + 1.934 + return NS_ERROR_NOT_AVAILABLE; 1.935 +} 1.936 + 1.937 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.938 +// begin external protocol service default implementation... 1.939 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.940 +NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, 1.941 + bool * aHandlerExists) 1.942 +{ 1.943 + nsCOMPtr<nsIHandlerInfo> handlerInfo; 1.944 + nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme), 1.945 + getter_AddRefs(handlerInfo)); 1.946 + NS_ENSURE_SUCCESS(rv, rv); 1.947 + 1.948 + // See if we have any known possible handler apps for this 1.949 + nsCOMPtr<nsIMutableArray> possibleHandlers; 1.950 + handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers)); 1.951 + 1.952 + uint32_t length; 1.953 + possibleHandlers->GetLength(&length); 1.954 + if (length) { 1.955 + *aHandlerExists = true; 1.956 + return NS_OK; 1.957 + } 1.958 + 1.959 + // if not, fall back on an os-based handler 1.960 + return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists); 1.961 +} 1.962 + 1.963 +NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult) 1.964 +{ 1.965 + // check the per protocol setting first. it always takes precedence. 1.966 + // if not set, then use the global setting. 1.967 + 1.968 + nsAutoCString prefName("network.protocol-handler.expose."); 1.969 + prefName += aProtocolScheme; 1.970 + bool val; 1.971 + if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) { 1.972 + *aResult = val; 1.973 + return NS_OK; 1.974 + } 1.975 + 1.976 + // by default, no protocol is exposed. i.e., by default all link clicks must 1.977 + // go through the external protocol service. most applications override this 1.978 + // default behavior. 1.979 + *aResult = 1.980 + Preferences::GetBool("network.protocol-handler.expose-all", false); 1.981 + 1.982 + return NS_OK; 1.983 +} 1.984 + 1.985 +NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL) 1.986 +{ 1.987 + return LoadURI(aURL, nullptr); 1.988 +} 1.989 + 1.990 +static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external."; 1.991 +static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default"; 1.992 + 1.993 +NS_IMETHODIMP 1.994 +nsExternalHelperAppService::LoadURI(nsIURI *aURI, 1.995 + nsIInterfaceRequestor *aWindowContext) 1.996 +{ 1.997 + NS_ENSURE_ARG_POINTER(aURI); 1.998 + 1.999 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.1000 + URIParams uri; 1.1001 + SerializeURI(aURI, uri); 1.1002 + 1.1003 + mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri); 1.1004 + return NS_OK; 1.1005 + } 1.1006 + 1.1007 + nsAutoCString spec; 1.1008 + aURI->GetSpec(spec); 1.1009 + 1.1010 + if (spec.Find("%00") != -1) 1.1011 + return NS_ERROR_MALFORMED_URI; 1.1012 + 1.1013 + spec.ReplaceSubstring("\"", "%22"); 1.1014 + spec.ReplaceSubstring("`", "%60"); 1.1015 + 1.1016 + nsCOMPtr<nsIIOService> ios(do_GetIOService()); 1.1017 + nsCOMPtr<nsIURI> uri; 1.1018 + nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); 1.1019 + NS_ENSURE_SUCCESS(rv, rv); 1.1020 + 1.1021 + nsAutoCString scheme; 1.1022 + uri->GetScheme(scheme); 1.1023 + if (scheme.IsEmpty()) 1.1024 + return NS_OK; // must have a scheme 1.1025 + 1.1026 + // Deny load if the prefs say to do so 1.1027 + nsAutoCString externalPref(kExternalProtocolPrefPrefix); 1.1028 + externalPref += scheme; 1.1029 + bool allowLoad = false; 1.1030 + if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) { 1.1031 + // no scheme-specific value, check the default 1.1032 + if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref, 1.1033 + &allowLoad))) { 1.1034 + return NS_OK; // missing default pref 1.1035 + } 1.1036 + } 1.1037 + 1.1038 + if (!allowLoad) { 1.1039 + return NS_OK; // explicitly denied 1.1040 + } 1.1041 + 1.1042 +#ifdef NECKO_PROTOCOL_rtsp 1.1043 + // Handle rtsp protocol. 1.1044 + { 1.1045 + bool isRTSP = false; 1.1046 + rv = aURI->SchemeIs("rtsp", &isRTSP); 1.1047 + if (NS_SUCCEEDED(rv) && isRTSP) { 1.1048 + LaunchVideoAppForRtsp(aURI); 1.1049 + return NS_OK; 1.1050 + } 1.1051 + } 1.1052 +#endif 1.1053 + 1.1054 + nsCOMPtr<nsIHandlerInfo> handler; 1.1055 + rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler)); 1.1056 + NS_ENSURE_SUCCESS(rv, rv); 1.1057 + 1.1058 + nsHandlerInfoAction preferredAction; 1.1059 + handler->GetPreferredAction(&preferredAction); 1.1060 + bool alwaysAsk = true; 1.1061 + handler->GetAlwaysAskBeforeHandling(&alwaysAsk); 1.1062 + 1.1063 + // if we are not supposed to ask, and the preferred action is to use 1.1064 + // a helper app or the system default, we just launch the URI. 1.1065 + if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp || 1.1066 + preferredAction == nsIHandlerInfo::useSystemDefault)) 1.1067 + return handler->LaunchWithURI(uri, aWindowContext); 1.1068 + 1.1069 + nsCOMPtr<nsIContentDispatchChooser> chooser = 1.1070 + do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv); 1.1071 + NS_ENSURE_SUCCESS(rv, rv); 1.1072 + 1.1073 + return chooser->Ask(handler, aWindowContext, uri, 1.1074 + nsIContentDispatchChooser::REASON_CANNOT_HANDLE); 1.1075 +} 1.1076 + 1.1077 +NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval) 1.1078 +{ 1.1079 + // this method should only be implemented by each OS specific implementation of this service. 1.1080 + return NS_ERROR_NOT_IMPLEMENTED; 1.1081 +} 1.1082 + 1.1083 + 1.1084 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.1085 +// Methods related to deleting temporary files on exit 1.1086 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.1087 + 1.1088 +/* static */ 1.1089 +nsresult 1.1090 +nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile, 1.1091 + nsCOMArray<nsIFile> &aFileList) 1.1092 +{ 1.1093 + bool isFile = false; 1.1094 + 1.1095 + // as a safety measure, make sure the nsIFile is really a file and not a directory object. 1.1096 + aTemporaryFile->IsFile(&isFile); 1.1097 + if (!isFile) return NS_OK; 1.1098 + 1.1099 + aFileList.AppendObject(aTemporaryFile); 1.1100 + 1.1101 + return NS_OK; 1.1102 +} 1.1103 + 1.1104 +NS_IMETHODIMP 1.1105 +nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) 1.1106 +{ 1.1107 + return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList); 1.1108 +} 1.1109 + 1.1110 +NS_IMETHODIMP 1.1111 +nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile) 1.1112 +{ 1.1113 + return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList); 1.1114 +} 1.1115 + 1.1116 +void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList) 1.1117 +{ 1.1118 + int32_t numEntries = fileList.Count(); 1.1119 + nsIFile* localFile; 1.1120 + for (int32_t index = 0; index < numEntries; index++) 1.1121 + { 1.1122 + localFile = fileList[index]; 1.1123 + if (localFile) { 1.1124 + // First make the file writable, since the temp file is probably readonly. 1.1125 + localFile->SetPermissions(0600); 1.1126 + localFile->Remove(false); 1.1127 + } 1.1128 + } 1.1129 + 1.1130 + fileList.Clear(); 1.1131 +} 1.1132 + 1.1133 +void nsExternalHelperAppService::ExpungeTemporaryFiles() 1.1134 +{ 1.1135 + ExpungeTemporaryFilesHelper(mTemporaryFilesList); 1.1136 +} 1.1137 + 1.1138 +void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() 1.1139 +{ 1.1140 + ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList); 1.1141 +} 1.1142 + 1.1143 +static const char kExternalWarningPrefPrefix[] = 1.1144 + "network.protocol-handler.warn-external."; 1.1145 +static const char kExternalWarningDefaultPref[] = 1.1146 + "network.protocol-handler.warn-external-default"; 1.1147 + 1.1148 +NS_IMETHODIMP 1.1149 +nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme, 1.1150 + nsIHandlerInfo **aHandlerInfo) 1.1151 +{ 1.1152 + // XXX enterprise customers should be able to turn this support off with a 1.1153 + // single master pref (maybe use one of the "exposed" prefs here?) 1.1154 + 1.1155 + bool exists; 1.1156 + nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo); 1.1157 + if (NS_FAILED(rv)) { 1.1158 + // Either it knows nothing, or we ran out of memory 1.1159 + return NS_ERROR_FAILURE; 1.1160 + } 1.1161 + 1.1162 + nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); 1.1163 + if (handlerSvc) { 1.1164 + bool hasHandler = false; 1.1165 + (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler); 1.1166 + if (hasHandler) { 1.1167 + rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString()); 1.1168 + if (NS_SUCCEEDED(rv)) 1.1169 + return NS_OK; 1.1170 + } 1.1171 + } 1.1172 + 1.1173 + return SetProtocolHandlerDefaults(*aHandlerInfo, exists); 1.1174 +} 1.1175 + 1.1176 +NS_IMETHODIMP 1.1177 +nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme, 1.1178 + bool *found, 1.1179 + nsIHandlerInfo **aHandlerInfo) 1.1180 +{ 1.1181 + // intended to be implemented by the subclass 1.1182 + return NS_ERROR_NOT_IMPLEMENTED; 1.1183 +} 1.1184 + 1.1185 +NS_IMETHODIMP 1.1186 +nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo, 1.1187 + bool aOSHandlerExists) 1.1188 +{ 1.1189 + // this type isn't in our database, so we've only got an OS default handler, 1.1190 + // if one exists 1.1191 + 1.1192 + if (aOSHandlerExists) { 1.1193 + // we've got a default, so use it 1.1194 + aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault); 1.1195 + 1.1196 + // whether or not to ask the user depends on the warning preference 1.1197 + nsAutoCString scheme; 1.1198 + aHandlerInfo->GetType(scheme); 1.1199 + 1.1200 + nsAutoCString warningPref(kExternalWarningPrefPrefix); 1.1201 + warningPref += scheme; 1.1202 + bool warn; 1.1203 + if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) { 1.1204 + // no scheme-specific value, check the default 1.1205 + warn = Preferences::GetBool(kExternalWarningDefaultPref, true); 1.1206 + } 1.1207 + aHandlerInfo->SetAlwaysAskBeforeHandling(warn); 1.1208 + } else { 1.1209 + // If no OS default existed, we set the preferred action to alwaysAsk. 1.1210 + // This really means not initialized (i.e. there's no available handler) 1.1211 + // to all the code... 1.1212 + aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk); 1.1213 + } 1.1214 + 1.1215 + return NS_OK; 1.1216 +} 1.1217 + 1.1218 +// XPCOM profile change observer 1.1219 +NS_IMETHODIMP 1.1220 +nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData ) 1.1221 +{ 1.1222 + if (!strcmp(aTopic, "profile-before-change")) { 1.1223 + ExpungeTemporaryFiles(); 1.1224 + } else if (!strcmp(aTopic, "last-pb-context-exited")) { 1.1225 + ExpungeTemporaryPrivateFiles(); 1.1226 + } 1.1227 + return NS_OK; 1.1228 +} 1.1229 + 1.1230 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.1231 +// begin external app handler implementation 1.1232 +////////////////////////////////////////////////////////////////////////////////////////////////////// 1.1233 + 1.1234 +NS_IMPL_ADDREF(nsExternalAppHandler) 1.1235 +NS_IMPL_RELEASE(nsExternalAppHandler) 1.1236 + 1.1237 +NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler) 1.1238 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) 1.1239 + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) 1.1240 + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) 1.1241 + NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher) 1.1242 + NS_INTERFACE_MAP_ENTRY(nsICancelable) 1.1243 + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 1.1244 + NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver) 1.1245 +NS_INTERFACE_MAP_END_THREADSAFE 1.1246 + 1.1247 +nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, 1.1248 + const nsCSubstring& aTempFileExtension, 1.1249 + nsIInterfaceRequestor* aWindowContext, 1.1250 + nsExternalHelperAppService *aExtProtSvc, 1.1251 + const nsAString& aSuggestedFilename, 1.1252 + uint32_t aReason, bool aForceSave) 1.1253 +: mMimeInfo(aMIMEInfo) 1.1254 +, mWindowContext(aWindowContext) 1.1255 +, mWindowToClose(nullptr) 1.1256 +, mSuggestedFileName(aSuggestedFilename) 1.1257 +, mForceSave(aForceSave) 1.1258 +, mCanceled(false) 1.1259 +, mShouldCloseWindow(false) 1.1260 +, mStopRequestIssued(false) 1.1261 +, mReason(aReason) 1.1262 +, mContentLength(-1) 1.1263 +, mProgress(0) 1.1264 +, mSaver(nullptr) 1.1265 +, mDialogProgressListener(nullptr) 1.1266 +, mTransfer(nullptr) 1.1267 +, mRequest(nullptr) 1.1268 +, mExtProtSvc(aExtProtSvc) 1.1269 +{ 1.1270 + 1.1271 + // make sure the extention includes the '.' 1.1272 + if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.') 1.1273 + mTempFileExtension = char16_t('.'); 1.1274 + AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension); 1.1275 + 1.1276 + // replace platform specific path separator and illegal characters to avoid any confusion 1.1277 + mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); 1.1278 + mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); 1.1279 + 1.1280 + // Remove unsafe bidi characters which might have spoofing implications (bug 511521). 1.1281 + const char16_t unsafeBidiCharacters[] = { 1.1282 + char16_t(0x061c), // Arabic Letter Mark 1.1283 + char16_t(0x200e), // Left-to-Right Mark 1.1284 + char16_t(0x200f), // Right-to-Left Mark 1.1285 + char16_t(0x202a), // Left-to-Right Embedding 1.1286 + char16_t(0x202b), // Right-to-Left Embedding 1.1287 + char16_t(0x202c), // Pop Directional Formatting 1.1288 + char16_t(0x202d), // Left-to-Right Override 1.1289 + char16_t(0x202e), // Right-to-Left Override 1.1290 + char16_t(0x2066), // Left-to-Right Isolate 1.1291 + char16_t(0x2067), // Right-to-Left Isolate 1.1292 + char16_t(0x2068), // First Strong Isolate 1.1293 + char16_t(0x2069), // Pop Directional Isolate 1.1294 + char16_t(0) 1.1295 + }; 1.1296 + mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_'); 1.1297 + mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_'); 1.1298 + 1.1299 + // Make sure extension is correct. 1.1300 + EnsureSuggestedFileName(); 1.1301 + 1.1302 + mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096); 1.1303 +} 1.1304 + 1.1305 +nsExternalAppHandler::~nsExternalAppHandler() 1.1306 +{ 1.1307 + MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted"); 1.1308 +} 1.1309 + 1.1310 +void 1.1311 +nsExternalAppHandler::DidDivertRequest(nsIRequest *request) 1.1312 +{ 1.1313 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process"); 1.1314 + // Remove our request from the child loadGroup 1.1315 + RetargetLoadNotifications(request); 1.1316 + MaybeCloseWindow(); 1.1317 +} 1.1318 + 1.1319 +NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener) 1.1320 +{ 1.1321 + // This is always called by nsHelperDlg.js. Go ahead and register the 1.1322 + // progress listener. At this point, we don't have mTransfer. 1.1323 + mDialogProgressListener = aWebProgressListener; 1.1324 + return NS_OK; 1.1325 +} 1.1326 + 1.1327 +NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) 1.1328 +{ 1.1329 + if (mFinalFileDestination) 1.1330 + *aTarget = mFinalFileDestination; 1.1331 + else 1.1332 + *aTarget = mTempFile; 1.1333 + 1.1334 + NS_IF_ADDREF(*aTarget); 1.1335 + return NS_OK; 1.1336 +} 1.1337 + 1.1338 +NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec) 1.1339 +{ 1.1340 + // Use the real target if it's been set 1.1341 + if (mFinalFileDestination) 1.1342 + return mFinalFileDestination->IsExecutable(aExec); 1.1343 + 1.1344 + // Otherwise, use the stored executable-ness of the temporary 1.1345 + *aExec = mTempFileIsExecutable; 1.1346 + return NS_OK; 1.1347 +} 1.1348 + 1.1349 +NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) 1.1350 +{ 1.1351 + *aTime = mTimeDownloadStarted; 1.1352 + return NS_OK; 1.1353 +} 1.1354 + 1.1355 +NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength) 1.1356 +{ 1.1357 + *aContentLength = mContentLength; 1.1358 + return NS_OK; 1.1359 +} 1.1360 + 1.1361 +void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request) 1.1362 +{ 1.1363 + // we are going to run the downloading of the helper app in our own little docloader / load group context. 1.1364 + // so go ahead and force the creation of a load group and doc loader for us to use... 1.1365 + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); 1.1366 + if (!aChannel) 1.1367 + return; 1.1368 + 1.1369 + // we need to store off the original (pre redirect!) channel that initiated the load. We do 1.1370 + // this so later on, we can pass any refresh urls associated with the original channel back to the 1.1371 + // window context which started the whole process. More comments about that are listed below.... 1.1372 + // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader. 1.1373 + // ideally we should be able to just use mChannel (the channel we are extracting content from) or 1.1374 + // the default load channel associated with the original load group. Unfortunately because 1.1375 + // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel 1.1376 + // which is what we really want.... 1.1377 + 1.1378 + // Note that we need to do this before removing aChannel from the loadgroup, 1.1379 + // since that would mess with the original channel on the loader. 1.1380 + nsCOMPtr<nsIDocumentLoader> origContextLoader = 1.1381 + do_GetInterface(mWindowContext); 1.1382 + if (origContextLoader) 1.1383 + origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel)); 1.1384 + 1.1385 + bool isPrivate = NS_UsePrivateBrowsing(aChannel); 1.1386 + 1.1387 + nsCOMPtr<nsILoadGroup> oldLoadGroup; 1.1388 + aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup)); 1.1389 + 1.1390 + if(oldLoadGroup) 1.1391 + oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED); 1.1392 + 1.1393 + aChannel->SetLoadGroup(nullptr); 1.1394 + aChannel->SetNotificationCallbacks(nullptr); 1.1395 + 1.1396 + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel); 1.1397 + if (pbChannel) { 1.1398 + pbChannel->SetPrivate(isPrivate); 1.1399 + } 1.1400 +} 1.1401 + 1.1402 +/** 1.1403 + * Make mTempFileExtension contain an extension exactly when its previous value 1.1404 + * is different from mSuggestedFileName's extension, so that it can be appended 1.1405 + * to mSuggestedFileName and form a valid, useful leaf name. 1.1406 + * This is required so that the (renamed) temporary file has the correct extension 1.1407 + * after downloading to make sure the OS will launch the application corresponding 1.1408 + * to the MIME type (which was used to calculate mTempFileExtension). This prevents 1.1409 + * a cgi-script named foobar.exe that returns application/zip from being named 1.1410 + * foobar.exe and executed as an executable file. It also blocks content that 1.1411 + * a web site might provide with a content-disposition header indicating 1.1412 + * filename="foobar.exe" from being downloaded to a file with extension .exe 1.1413 + * and executed. 1.1414 + */ 1.1415 +void nsExternalAppHandler::EnsureSuggestedFileName() 1.1416 +{ 1.1417 + // Make sure there is a mTempFileExtension (not "" or "."). 1.1418 + // Remember that mTempFileExtension will always have the leading "." 1.1419 + // (the check for empty is just to be safe). 1.1420 + if (mTempFileExtension.Length() > 1) 1.1421 + { 1.1422 + // Get mSuggestedFileName's current extension. 1.1423 + nsAutoString fileExt; 1.1424 + int32_t pos = mSuggestedFileName.RFindChar('.'); 1.1425 + if (pos != kNotFound) 1.1426 + mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos); 1.1427 + 1.1428 + // Now, compare fileExt to mTempFileExtension. 1.1429 + if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator())) 1.1430 + { 1.1431 + // Matches -> mTempFileExtension can be empty 1.1432 + mTempFileExtension.Truncate(); 1.1433 + } 1.1434 + } 1.1435 +} 1.1436 + 1.1437 +nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel) 1.1438 +{ 1.1439 + // First we need to try to get the destination directory for the temporary 1.1440 + // file. 1.1441 + nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile)); 1.1442 + NS_ENSURE_SUCCESS(rv, rv); 1.1443 + 1.1444 + // At this point, we do not have a filename for the temp file. For security 1.1445 + // purposes, this cannot be predictable, so we must use a cryptographic 1.1446 + // quality PRNG to generate one. 1.1447 + // We will request raw random bytes, and transform that to a base64 string, 1.1448 + // as all characters from the base64 set are acceptable for filenames. For 1.1449 + // each three bytes of random data, we will get four bytes of ASCII. Request 1.1450 + // a bit more, to be safe, and truncate to the length we want in the end. 1.1451 + 1.1452 + const uint32_t wantedFileNameLength = 8; 1.1453 + const uint32_t requiredBytesLength = 1.1454 + static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3); 1.1455 + 1.1456 + nsCOMPtr<nsIRandomGenerator> rg = 1.1457 + do_GetService("@mozilla.org/security/random-generator;1", &rv); 1.1458 + NS_ENSURE_SUCCESS(rv, rv); 1.1459 + 1.1460 + uint8_t *buffer; 1.1461 + rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer); 1.1462 + NS_ENSURE_SUCCESS(rv, rv); 1.1463 + 1.1464 + nsAutoCString tempLeafName; 1.1465 + nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength); 1.1466 + rv = Base64Encode(randomData, tempLeafName); 1.1467 + NS_Free(buffer); 1.1468 + buffer = nullptr; 1.1469 + NS_ENSURE_SUCCESS(rv, rv); 1.1470 + 1.1471 + tempLeafName.Truncate(wantedFileNameLength); 1.1472 + 1.1473 + // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need 1.1474 + // to replace illegal characters -- notably '/' 1.1475 + tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); 1.1476 + 1.1477 + // now append our extension. 1.1478 + nsAutoCString ext; 1.1479 + mMimeInfo->GetPrimaryExtension(ext); 1.1480 + if (!ext.IsEmpty()) { 1.1481 + ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); 1.1482 + if (ext.First() != '.') 1.1483 + tempLeafName.Append('.'); 1.1484 + tempLeafName.Append(ext); 1.1485 + } 1.1486 + 1.1487 + // We need to temporarily create a dummy file with the correct 1.1488 + // file extension to determine the executable-ness, so do this before adding 1.1489 + // the extra .part extension. 1.1490 + nsCOMPtr<nsIFile> dummyFile; 1.1491 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile)); 1.1492 + NS_ENSURE_SUCCESS(rv, rv); 1.1493 + 1.1494 + // Set the file name without .part 1.1495 + rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName)); 1.1496 + NS_ENSURE_SUCCESS(rv, rv); 1.1497 + rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); 1.1498 + NS_ENSURE_SUCCESS(rv, rv); 1.1499 + 1.1500 + // Store executable-ness then delete 1.1501 + dummyFile->IsExecutable(&mTempFileIsExecutable); 1.1502 + dummyFile->Remove(false); 1.1503 + 1.1504 + // Add an additional .part to prevent the OS from running this file in the 1.1505 + // default application. 1.1506 + tempLeafName.Append(NS_LITERAL_CSTRING(".part")); 1.1507 + 1.1508 + rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName)); 1.1509 + // make this file unique!!! 1.1510 + NS_ENSURE_SUCCESS(rv, rv); 1.1511 + rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); 1.1512 + NS_ENSURE_SUCCESS(rv, rv); 1.1513 + 1.1514 + // Now save the temp leaf name, minus the ".part" bit, so we can use it later. 1.1515 + // This is a bit broken in the case when createUnique actually had to append 1.1516 + // some numbers, because then we now have a filename like foo.bar-1.part and 1.1517 + // we'll end up with foo.bar-1.bar as our final filename if we end up using 1.1518 + // this. But the other options are all bad too.... Ideally we'd have a way 1.1519 + // to tell createUnique to put its unique marker before the extension that 1.1520 + // comes before ".part" or something. 1.1521 + rv = mTempFile->GetLeafName(mTempLeafName); 1.1522 + NS_ENSURE_SUCCESS(rv, rv); 1.1523 + 1.1524 + NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")), 1.1525 + NS_ERROR_UNEXPECTED); 1.1526 + 1.1527 + // Strip off the ".part" from mTempLeafName 1.1528 + mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1); 1.1529 + 1.1530 + MOZ_ASSERT(!mSaver, "Output file initialization called more than once!"); 1.1531 + mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, 1.1532 + &rv); 1.1533 + NS_ENSURE_SUCCESS(rv, rv); 1.1534 + 1.1535 + rv = mSaver->SetObserver(this); 1.1536 + if (NS_FAILED(rv)) { 1.1537 + mSaver = nullptr; 1.1538 + return rv; 1.1539 + } 1.1540 + 1.1541 + rv = mSaver->EnableSha256(); 1.1542 + NS_ENSURE_SUCCESS(rv, rv); 1.1543 + 1.1544 + rv = mSaver->EnableSignatureInfo(); 1.1545 + NS_ENSURE_SUCCESS(rv, rv); 1.1546 + LOG(("Enabled hashing and signature verification")); 1.1547 + 1.1548 + rv = mSaver->SetTarget(mTempFile, false); 1.1549 + NS_ENSURE_SUCCESS(rv, rv); 1.1550 + 1.1551 + return rv; 1.1552 +} 1.1553 + 1.1554 +NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt) 1.1555 +{ 1.1556 + NS_PRECONDITION(request, "OnStartRequest without request?"); 1.1557 + 1.1558 + // Set mTimeDownloadStarted here as the download has already started and 1.1559 + // we want to record the start time before showing the filepicker. 1.1560 + mTimeDownloadStarted = PR_Now(); 1.1561 + 1.1562 + mRequest = request; 1.1563 + 1.1564 + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); 1.1565 + 1.1566 + nsresult rv; 1.1567 + 1.1568 + nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request)); 1.1569 + mIsFileChannel = fileChan != nullptr; 1.1570 + 1.1571 + // Get content length 1.1572 + if (aChannel) { 1.1573 + aChannel->GetContentLength(&mContentLength); 1.1574 + } 1.1575 + 1.1576 + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv)); 1.1577 + // Determine whether a new window was opened specifically for this request 1.1578 + if (props) { 1.1579 + bool tmp = false; 1.1580 + props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"), 1.1581 + &tmp); 1.1582 + mShouldCloseWindow = tmp; 1.1583 + } 1.1584 + 1.1585 + // Now get the URI 1.1586 + if (aChannel) 1.1587 + { 1.1588 + aChannel->GetURI(getter_AddRefs(mSourceUrl)); 1.1589 + } 1.1590 + 1.1591 + // retarget all load notifications to our docloader instead of the original window's docloader... 1.1592 + RetargetLoadNotifications(request); 1.1593 + 1.1594 + // Check to see if there is a refresh header on the original channel. 1.1595 + if (mOriginalChannel) { 1.1596 + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel)); 1.1597 + if (httpChannel) { 1.1598 + nsAutoCString refreshHeader; 1.1599 + httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"), 1.1600 + refreshHeader); 1.1601 + if (!refreshHeader.IsEmpty()) { 1.1602 + mShouldCloseWindow = false; 1.1603 + } 1.1604 + } 1.1605 + } 1.1606 + 1.1607 + // Close the underlying DOMWindow if there is no refresh header 1.1608 + // and it was opened specifically for the download 1.1609 + MaybeCloseWindow(); 1.1610 + 1.1611 + // In an IPC setting, we're allowing the child process, here, to make 1.1612 + // decisions about decoding the channel (e.g. decompression). It will 1.1613 + // still forward the decoded (uncompressed) data back to the parent. 1.1614 + // Con: Uncompressed data means more IPC overhead. 1.1615 + // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel. 1.1616 + // Parent process doesn't need to expect CPU time on decompression. 1.1617 + nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel ); 1.1618 + if (encChannel) 1.1619 + { 1.1620 + // Turn off content encoding conversions if needed 1.1621 + bool applyConversion = true; 1.1622 + 1.1623 + nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl)); 1.1624 + if (sourceURL) 1.1625 + { 1.1626 + nsAutoCString extension; 1.1627 + sourceURL->GetFileExtension(extension); 1.1628 + if (!extension.IsEmpty()) 1.1629 + { 1.1630 + nsCOMPtr<nsIUTF8StringEnumerator> encEnum; 1.1631 + encChannel->GetContentEncodings(getter_AddRefs(encEnum)); 1.1632 + if (encEnum) 1.1633 + { 1.1634 + bool hasMore; 1.1635 + rv = encEnum->HasMore(&hasMore); 1.1636 + if (NS_SUCCEEDED(rv) && hasMore) 1.1637 + { 1.1638 + nsAutoCString encType; 1.1639 + rv = encEnum->GetNext(encType); 1.1640 + if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) 1.1641 + { 1.1642 + mExtProtSvc->ApplyDecodingForExtension(extension, encType, 1.1643 + &applyConversion); 1.1644 + } 1.1645 + } 1.1646 + } 1.1647 + } 1.1648 + } 1.1649 + 1.1650 + encChannel->SetApplyConversion( applyConversion ); 1.1651 + } 1.1652 + 1.1653 + // At this point, the child process has done everything it can usefully do 1.1654 + // for OnStartRequest. 1.1655 + if (XRE_GetProcessType() == GeckoProcessType_Content) 1.1656 + return NS_OK; 1.1657 + 1.1658 + rv = SetUpTempFile(aChannel); 1.1659 + if (NS_FAILED(rv)) { 1.1660 + nsresult transferError = rv; 1.1661 + 1.1662 + rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel)); 1.1663 +#ifdef PR_LOGGING 1.1664 + if (NS_FAILED(rv)) { 1.1665 + LOG(("Failed to create transfer to report failure." 1.1666 + "Will fallback to prompter!")); 1.1667 + } 1.1668 +#endif 1.1669 + 1.1670 + mCanceled = true; 1.1671 + request->Cancel(transferError); 1.1672 + 1.1673 + nsAutoString path; 1.1674 + if (mTempFile) 1.1675 + mTempFile->GetPath(path); 1.1676 + 1.1677 + SendStatusChange(kWriteError, transferError, request, path); 1.1678 + 1.1679 + return NS_OK; 1.1680 + } 1.1681 + 1.1682 + // Inform channel it is open on behalf of a download to prevent caching. 1.1683 + nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel); 1.1684 + if (httpInternal) { 1.1685 + httpInternal->SetChannelIsForDownload(true); 1.1686 + } 1.1687 + 1.1688 + // now that the temp file is set up, find out if we need to invoke a dialog 1.1689 + // asking the user what they want us to do with this content... 1.1690 + 1.1691 + // We can get here for three reasons: "can't handle", "sniffed type", or 1.1692 + // "server sent content-disposition:attachment". In the first case we want 1.1693 + // to honor the user's "always ask" pref; in the other two cases we want to 1.1694 + // honor it only if the default action is "save". Opening attachments in 1.1695 + // helper apps by default breaks some websites (especially if the attachment 1.1696 + // is one part of a multipart document). Opening sniffed content in helper 1.1697 + // apps by default introduces security holes that we'd rather not have. 1.1698 + 1.1699 + // So let's find out whether the user wants to be prompted. If he does not, 1.1700 + // check mReason and the preferred action to see what we should do. 1.1701 + 1.1702 + bool alwaysAsk = true; 1.1703 + mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); 1.1704 + if (alwaysAsk) 1.1705 + { 1.1706 + // But we *don't* ask if this mimeInfo didn't come from 1.1707 + // our user configuration datastore and the user has said 1.1708 + // at some point in the distant past that they don't 1.1709 + // want to be asked. The latter fact would have been 1.1710 + // stored in pref strings back in the old days. 1.1711 + 1.1712 + bool mimeTypeIsInDatastore = false; 1.1713 + nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); 1.1714 + if (handlerSvc) 1.1715 + handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore); 1.1716 + if (!handlerSvc || !mimeTypeIsInDatastore) 1.1717 + { 1.1718 + nsAutoCString MIMEType; 1.1719 + mMimeInfo->GetMIMEType(MIMEType); 1.1720 + 1.1721 + if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get())) 1.1722 + { 1.1723 + // Don't need to ask after all. 1.1724 + alwaysAsk = false; 1.1725 + // Make sure action matches pref (save to disk). 1.1726 + mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); 1.1727 + } 1.1728 + else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get())) 1.1729 + { 1.1730 + // Don't need to ask after all. 1.1731 + alwaysAsk = false; 1.1732 + } 1.1733 + } 1.1734 + } 1.1735 + 1.1736 + int32_t action = nsIMIMEInfo::saveToDisk; 1.1737 + mMimeInfo->GetPreferredAction( &action ); 1.1738 + 1.1739 + // OK, now check why we're here 1.1740 + if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) { 1.1741 + // Force asking if we're not saving. See comment back when we fetched the 1.1742 + // alwaysAsk boolean for details. 1.1743 + alwaysAsk = (action != nsIMIMEInfo::saveToDisk); 1.1744 + } 1.1745 + 1.1746 + // if we were told that we _must_ save to disk without asking, all the stuff 1.1747 + // before this is irrelevant; override it 1.1748 + if (mForceSave) { 1.1749 + alwaysAsk = false; 1.1750 + action = nsIMIMEInfo::saveToDisk; 1.1751 + } 1.1752 + 1.1753 + if (alwaysAsk) 1.1754 + { 1.1755 + // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request 1.1756 + mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); 1.1757 + NS_ENSURE_SUCCESS(rv, rv); 1.1758 + 1.1759 + // this will create a reference cycle (the dialog holds a reference to us as 1.1760 + // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer. 1.1761 + rv = mDialog->Show( this, mWindowContext, mReason ); 1.1762 + 1.1763 + // what do we do if the dialog failed? I guess we should call Cancel and abort the load.... 1.1764 + } 1.1765 + else 1.1766 + { 1.1767 + 1.1768 + // We need to do the save/open immediately, then. 1.1769 +#ifdef XP_WIN 1.1770 + /* We need to see whether the file we've got here could be 1.1771 + * executable. If it could, we had better not try to open it! 1.1772 + * We can skip this check, though, if we have a setting to open in a 1.1773 + * helper app. 1.1774 + * This code mirrors the code in 1.1775 + * nsExternalAppHandler::LaunchWithApplication so that what we 1.1776 + * test here is as close as possible to what will really be 1.1777 + * happening if we decide to execute 1.1778 + */ 1.1779 + nsCOMPtr<nsIHandlerApp> prefApp; 1.1780 + mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp)); 1.1781 + if (action != nsIMIMEInfo::useHelperApp || !prefApp) { 1.1782 + nsCOMPtr<nsIFile> fileToTest; 1.1783 + GetTargetFile(getter_AddRefs(fileToTest)); 1.1784 + if (fileToTest) { 1.1785 + bool isExecutable; 1.1786 + rv = fileToTest->IsExecutable(&isExecutable); 1.1787 + if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good 1.1788 + action = nsIMIMEInfo::saveToDisk; 1.1789 + } 1.1790 + } else { // Paranoia is good here too, though this really should not happen 1.1791 + NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! "); 1.1792 + action = nsIMIMEInfo::saveToDisk; 1.1793 + } 1.1794 + } 1.1795 + 1.1796 +#endif 1.1797 + if (action == nsIMIMEInfo::useHelperApp || 1.1798 + action == nsIMIMEInfo::useSystemDefault) 1.1799 + { 1.1800 + rv = LaunchWithApplication(nullptr, false); 1.1801 + } 1.1802 + else // Various unknown actions go here too 1.1803 + { 1.1804 + rv = SaveToDisk(nullptr, false); 1.1805 + } 1.1806 + } 1.1807 + 1.1808 + return NS_OK; 1.1809 +} 1.1810 + 1.1811 +// Convert error info into proper message text and send OnStatusChange 1.1812 +// notification to the dialog progress listener or nsITransfer implementation. 1.1813 +void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path) 1.1814 +{ 1.1815 + nsAutoString msgId; 1.1816 + switch(rv) 1.1817 + { 1.1818 + case NS_ERROR_OUT_OF_MEMORY: 1.1819 + // No memory 1.1820 + msgId.AssignLiteral("noMemory"); 1.1821 + break; 1.1822 + 1.1823 + case NS_ERROR_FILE_DISK_FULL: 1.1824 + case NS_ERROR_FILE_NO_DEVICE_SPACE: 1.1825 + // Out of space on target volume. 1.1826 + msgId.AssignLiteral("diskFull"); 1.1827 + break; 1.1828 + 1.1829 + case NS_ERROR_FILE_READ_ONLY: 1.1830 + // Attempt to write to read/only file. 1.1831 + msgId.AssignLiteral("readOnly"); 1.1832 + break; 1.1833 + 1.1834 + case NS_ERROR_FILE_ACCESS_DENIED: 1.1835 + if (type == kWriteError) { 1.1836 + // Attempt to write without sufficient permissions. 1.1837 +#if defined(ANDROID) 1.1838 + // On Android (and Gonk), this means the SD card is present but 1.1839 + // unavailable (read-only). 1.1840 + msgId.AssignLiteral("SDAccessErrorCardReadOnly"); 1.1841 +#else 1.1842 + msgId.AssignLiteral("accessError"); 1.1843 +#endif 1.1844 + } 1.1845 + else 1.1846 + { 1.1847 + msgId.AssignLiteral("launchError"); 1.1848 + } 1.1849 + break; 1.1850 + 1.1851 + case NS_ERROR_FILE_NOT_FOUND: 1.1852 + case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST: 1.1853 + case NS_ERROR_FILE_UNRECOGNIZED_PATH: 1.1854 + // Helper app not found, let's verify this happened on launch 1.1855 + if (type == kLaunchError) { 1.1856 + msgId.AssignLiteral("helperAppNotFound"); 1.1857 + break; 1.1858 + } 1.1859 +#if defined(ANDROID) 1.1860 + else if (type == kWriteError) { 1.1861 + // On Android (and Gonk), this means the SD card is missing (not in 1.1862 + // SD slot). 1.1863 + msgId.AssignLiteral("SDAccessErrorCardMissing"); 1.1864 + break; 1.1865 + } 1.1866 +#endif 1.1867 + // fall through 1.1868 + 1.1869 + default: 1.1870 + // Generic read/write/launch error message. 1.1871 + switch(type) 1.1872 + { 1.1873 + case kReadError: 1.1874 + msgId.AssignLiteral("readError"); 1.1875 + break; 1.1876 + case kWriteError: 1.1877 + msgId.AssignLiteral("writeError"); 1.1878 + break; 1.1879 + case kLaunchError: 1.1880 + msgId.AssignLiteral("launchError"); 1.1881 + break; 1.1882 + } 1.1883 + break; 1.1884 + } 1.1885 + PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, 1.1886 + ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n", 1.1887 + NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv)); 1.1888 + PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, 1.1889 + (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get())); 1.1890 + 1.1891 + // Get properties file bundle and extract status string. 1.1892 + nsCOMPtr<nsIStringBundleService> stringService = 1.1893 + mozilla::services::GetStringBundleService(); 1.1894 + if (stringService) 1.1895 + { 1.1896 + nsCOMPtr<nsIStringBundle> bundle; 1.1897 + if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle)))) 1.1898 + { 1.1899 + nsXPIDLString msgText; 1.1900 + const char16_t *strings[] = { path.get() }; 1.1901 + if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)))) 1.1902 + { 1.1903 + if (mDialogProgressListener) 1.1904 + { 1.1905 + // We have a listener, let it handle the error. 1.1906 + mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText); 1.1907 + } else if (mTransfer) { 1.1908 + mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText); 1.1909 + } 1.1910 + else 1.1911 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.1912 + // We don't have a listener. Simply show the alert ourselves. 1.1913 + nsresult qiRv; 1.1914 + nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext, &qiRv)); 1.1915 + nsXPIDLString title; 1.1916 + bundle->FormatStringFromName(MOZ_UTF16("title"), 1.1917 + strings, 1.1918 + 1, 1.1919 + getter_Copies(title)); 1.1920 + 1.1921 + PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG, 1.1922 + ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'", 1.1923 + mWindowContext.get(), 1.1924 + prompter.get(), 1.1925 + qiRv, 1.1926 + NS_ConvertUTF16toUTF8(title).get(), 1.1927 + NS_ConvertUTF16toUTF8(msgText).get())); 1.1928 + 1.1929 + // If we didn't have a prompter we will try and get a window 1.1930 + // instead, get it's docshell and use it to alert the user. 1.1931 + if (!prompter) 1.1932 + { 1.1933 + nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(mWindowContext)); 1.1934 + if (!window || !window->GetDocShell()) 1.1935 + { 1.1936 + return; 1.1937 + } 1.1938 + 1.1939 + prompter = do_GetInterface(window->GetDocShell(), &qiRv); 1.1940 + 1.1941 + PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG, 1.1942 + ("No prompter from mWindowContext, using DocShell, " \ 1.1943 + "window=0x%p, docShell=0x%p, " \ 1.1944 + "prompter=0x%p, qi rv=0x%08X", 1.1945 + window.get(), 1.1946 + window->GetDocShell(), 1.1947 + prompter.get(), 1.1948 + qiRv)); 1.1949 + 1.1950 + // If we still don't have a prompter, there's nothing else we 1.1951 + // can do so just return. 1.1952 + if (!prompter) 1.1953 + { 1.1954 + PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR, 1.1955 + ("No prompter from DocShell, no way to alert user")); 1.1956 + return; 1.1957 + } 1.1958 + } 1.1959 + 1.1960 + // We should always have a prompter at this point. 1.1961 + prompter->Alert(title, msgText); 1.1962 + } 1.1963 + } 1.1964 + } 1.1965 + } 1.1966 +} 1.1967 + 1.1968 +NS_IMETHODIMP 1.1969 +nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, 1.1970 + nsIInputStream * inStr, 1.1971 + uint64_t sourceOffset, uint32_t count) 1.1972 +{ 1.1973 + nsresult rv = NS_OK; 1.1974 + // first, check to see if we've been canceled.... 1.1975 + if (mCanceled || !mSaver) // then go cancel our underlying channel too 1.1976 + return request->Cancel(NS_BINDING_ABORTED); 1.1977 + 1.1978 + // read the data out of the stream and write it to the temp file. 1.1979 + if (count > 0) 1.1980 + { 1.1981 + mProgress += count; 1.1982 + 1.1983 + nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver); 1.1984 + rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count); 1.1985 + if (NS_SUCCEEDED(rv)) 1.1986 + { 1.1987 + // Send progress notification. 1.1988 + if (mTransfer) { 1.1989 + mTransfer->OnProgressChange64(nullptr, request, mProgress, 1.1990 + mContentLength, mProgress, 1.1991 + mContentLength); 1.1992 + } 1.1993 + } 1.1994 + else 1.1995 + { 1.1996 + // An error occurred, notify listener. 1.1997 + nsAutoString tempFilePath; 1.1998 + if (mTempFile) 1.1999 + mTempFile->GetPath(tempFilePath); 1.2000 + SendStatusChange(kReadError, rv, request, tempFilePath); 1.2001 + 1.2002 + // Cancel the download. 1.2003 + Cancel(rv); 1.2004 + } 1.2005 + } 1.2006 + return rv; 1.2007 +} 1.2008 + 1.2009 +NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 1.2010 + nsresult aStatus) 1.2011 +{ 1.2012 + LOG(("nsExternalAppHandler::OnStopRequest\n" 1.2013 + " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n", 1.2014 + mCanceled, mTransfer.get(), aStatus)); 1.2015 + 1.2016 + mStopRequestIssued = true; 1.2017 + 1.2018 + // Cancel if the request did not complete successfully. 1.2019 + if (!mCanceled && NS_FAILED(aStatus)) 1.2020 + { 1.2021 + // Send error notification. 1.2022 + nsAutoString tempFilePath; 1.2023 + if (mTempFile) 1.2024 + mTempFile->GetPath(tempFilePath); 1.2025 + SendStatusChange( kReadError, aStatus, request, tempFilePath ); 1.2026 + 1.2027 + Cancel(aStatus); 1.2028 + } 1.2029 + 1.2030 + // first, check to see if we've been canceled.... 1.2031 + if (mCanceled || !mSaver) 1.2032 + return NS_OK; 1.2033 + 1.2034 + return mSaver->Finish(NS_OK); 1.2035 +} 1.2036 + 1.2037 +NS_IMETHODIMP 1.2038 +nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver, 1.2039 + nsIFile *aTarget) 1.2040 +{ 1.2041 + return NS_OK; 1.2042 +} 1.2043 + 1.2044 +NS_IMETHODIMP 1.2045 +nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver, 1.2046 + nsresult aStatus) 1.2047 +{ 1.2048 + LOG(("nsExternalAppHandler::OnSaveComplete\n" 1.2049 + " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n", 1.2050 + aSaver, aStatus, mCanceled, mTransfer.get())); 1.2051 + 1.2052 + if (!mCanceled) { 1.2053 + // Save the hash 1.2054 + (void)mSaver->GetSha256Hash(mHash); 1.2055 + (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo)); 1.2056 + // Free the reference that the saver keeps on us, even if we couldn't get 1.2057 + // the hash. 1.2058 + mSaver = nullptr; 1.2059 + 1.2060 + if (NS_FAILED(aStatus)) { 1.2061 + nsAutoString path; 1.2062 + mTempFile->GetPath(path); 1.2063 + 1.2064 + // It may happen when e10s is enabled that there will be no transfer 1.2065 + // object available to communicate status as expected by the system. 1.2066 + // Let's try and create a temporary transfer object to take care of this 1.2067 + // for us, we'll fall back to using the prompt service if we absolutely 1.2068 + // have to. 1.2069 + if (!mTransfer) { 1.2070 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); 1.2071 + // We don't care if this fails. 1.2072 + CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel)); 1.2073 + } 1.2074 + 1.2075 + SendStatusChange(kWriteError, aStatus, nullptr, path); 1.2076 + if (!mCanceled) 1.2077 + Cancel(aStatus); 1.2078 + return NS_OK; 1.2079 + } 1.2080 + } 1.2081 + 1.2082 + // Notify the transfer object that we are done if the user has chosen an 1.2083 + // action. If the user hasn't chosen an action, the progress listener 1.2084 + // (nsITransfer) will be notified in CreateTransfer. 1.2085 + if (mTransfer) { 1.2086 + NotifyTransfer(aStatus); 1.2087 + } 1.2088 + 1.2089 + return NS_OK; 1.2090 +} 1.2091 + 1.2092 +void nsExternalAppHandler::NotifyTransfer(nsresult aStatus) 1.2093 +{ 1.2094 + MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread"); 1.2095 + MOZ_ASSERT(mTransfer, "We must have an nsITransfer"); 1.2096 + 1.2097 + LOG(("Notifying progress listener")); 1.2098 + 1.2099 + if (NS_SUCCEEDED(aStatus)) { 1.2100 + (void)mTransfer->SetSha256Hash(mHash); 1.2101 + (void)mTransfer->SetSignatureInfo(mSignatureInfo); 1.2102 + (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress, 1.2103 + mContentLength, mProgress, mContentLength); 1.2104 + } 1.2105 + 1.2106 + (void)mTransfer->OnStateChange(nullptr, nullptr, 1.2107 + nsIWebProgressListener::STATE_STOP | 1.2108 + nsIWebProgressListener::STATE_IS_REQUEST | 1.2109 + nsIWebProgressListener::STATE_IS_NETWORK, aStatus); 1.2110 + 1.2111 + // This nsITransfer object holds a reference to us (we are its observer), so 1.2112 + // we need to release the reference to break a reference cycle (and therefore 1.2113 + // to prevent leaking). We do this even if the previous calls failed. 1.2114 + mTransfer = nullptr; 1.2115 +} 1.2116 + 1.2117 +NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo) 1.2118 +{ 1.2119 + *aMIMEInfo = mMimeInfo; 1.2120 + NS_ADDREF(*aMIMEInfo); 1.2121 + return NS_OK; 1.2122 +} 1.2123 + 1.2124 +NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI) 1.2125 +{ 1.2126 + NS_ENSURE_ARG(aSourceURI); 1.2127 + *aSourceURI = mSourceUrl; 1.2128 + NS_IF_ADDREF(*aSourceURI); 1.2129 + return NS_OK; 1.2130 +} 1.2131 + 1.2132 +NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName) 1.2133 +{ 1.2134 + aSuggestedFileName = mSuggestedFileName; 1.2135 + return NS_OK; 1.2136 +} 1.2137 + 1.2138 +nsresult nsExternalAppHandler::CreateTransfer() 1.2139 +{ 1.2140 + LOG(("nsExternalAppHandler::CreateTransfer")); 1.2141 + 1.2142 + MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread"); 1.2143 + // We are back from the helper app dialog (where the user chooses to save or 1.2144 + // open), but we aren't done processing the load. in this case, throw up a 1.2145 + // progress dialog so the user can see what's going on. 1.2146 + // Also, release our reference to mDialog. We don't need it anymore, and we 1.2147 + // need to break the reference cycle. 1.2148 + mDialog = nullptr; 1.2149 + if (!mDialogProgressListener) { 1.2150 + NS_WARNING("The dialog should nullify the dialog progress listener"); 1.2151 + } 1.2152 + nsresult rv; 1.2153 + 1.2154 + // We must be able to create an nsITransfer object. If not, it doesn't matter 1.2155 + // much that we can't launch the helper application or save to disk. Work on 1.2156 + // a local copy rather than mTransfer until we know we succeeded, to make it 1.2157 + // clearer that this function is re-entrant. 1.2158 + nsCOMPtr<nsITransfer> transfer = do_CreateInstance( 1.2159 + NS_TRANSFER_CONTRACTID, &rv); 1.2160 + NS_ENSURE_SUCCESS(rv, rv); 1.2161 + 1.2162 + // Initialize the download 1.2163 + nsCOMPtr<nsIURI> target; 1.2164 + rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination); 1.2165 + NS_ENSURE_SUCCESS(rv, rv); 1.2166 + 1.2167 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); 1.2168 + 1.2169 + rv = transfer->Init(mSourceUrl, target, EmptyString(), 1.2170 + mMimeInfo, mTimeDownloadStarted, mTempFile, this, 1.2171 + channel && NS_UsePrivateBrowsing(channel)); 1.2172 + NS_ENSURE_SUCCESS(rv, rv); 1.2173 + 1.2174 + // Now let's add the download to history 1.2175 + nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID)); 1.2176 + if (dh) { 1.2177 + nsCOMPtr<nsIURI> referrer; 1.2178 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); 1.2179 + if (channel) { 1.2180 + NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer)); 1.2181 + } 1.2182 + 1.2183 + if (channel && !NS_UsePrivateBrowsing(channel)) { 1.2184 + dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target); 1.2185 + } 1.2186 + } 1.2187 + 1.2188 + // If we were cancelled since creating the transfer, just return. It is 1.2189 + // always ok to return NS_OK if we are cancelled. Callers of this function 1.2190 + // must call Cancel if CreateTransfer fails, but there's no need to cancel 1.2191 + // twice. 1.2192 + if (mCanceled) { 1.2193 + return NS_OK; 1.2194 + } 1.2195 + rv = transfer->OnStateChange(nullptr, mRequest, 1.2196 + nsIWebProgressListener::STATE_START | 1.2197 + nsIWebProgressListener::STATE_IS_REQUEST | 1.2198 + nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); 1.2199 + NS_ENSURE_SUCCESS(rv, rv); 1.2200 + 1.2201 + if (mCanceled) { 1.2202 + return NS_OK; 1.2203 + } 1.2204 + 1.2205 + mRequest = nullptr; 1.2206 + // Finally, save the transfer to mTransfer. 1.2207 + mTransfer = transfer; 1.2208 + transfer = nullptr; 1.2209 + 1.2210 + // While we were bringing up the progress dialog, we actually finished 1.2211 + // processing the url. If that's the case then mStopRequestIssued will be 1.2212 + // true and OnSaveComplete has been called. 1.2213 + if (mStopRequestIssued && !mSaver && mTransfer) { 1.2214 + NotifyTransfer(NS_OK); 1.2215 + } 1.2216 + 1.2217 + return rv; 1.2218 +} 1.2219 + 1.2220 +nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing) 1.2221 +{ 1.2222 + nsresult rv; 1.2223 + nsCOMPtr<nsITransfer> transfer = 1.2224 + do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv); 1.2225 + NS_ENSURE_SUCCESS(rv, rv); 1.2226 + 1.2227 + // If we don't have a download directory we're kinda screwed but it's OK 1.2228 + // we'll still report the error via the prompter. 1.2229 + nsCOMPtr<nsIFile> pseudoFile; 1.2230 + rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true); 1.2231 + NS_ENSURE_SUCCESS(rv, rv); 1.2232 + 1.2233 + // Append the default suggested filename. If the user restarts the transfer 1.2234 + // we will re-trigger a filename check anyway to ensure that it is unique. 1.2235 + rv = pseudoFile->Append(mSuggestedFileName); 1.2236 + NS_ENSURE_SUCCESS(rv, rv); 1.2237 + 1.2238 + nsCOMPtr<nsIURI> pseudoTarget; 1.2239 + rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile); 1.2240 + NS_ENSURE_SUCCESS(rv, rv); 1.2241 + 1.2242 + rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(), 1.2243 + mMimeInfo, mTimeDownloadStarted, nullptr, this, 1.2244 + aIsPrivateBrowsing); 1.2245 + NS_ENSURE_SUCCESS(rv, rv); 1.2246 + 1.2247 + // Our failed transfer is ready. 1.2248 + mTransfer = transfer.forget(); 1.2249 + 1.2250 + return NS_OK; 1.2251 +} 1.2252 + 1.2253 +nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile) 1.2254 +{ 1.2255 + if (aFile) 1.2256 + ContinueSave(aFile); 1.2257 + else 1.2258 + Cancel(NS_BINDING_ABORTED); 1.2259 + 1.2260 + return NS_OK; 1.2261 +} 1.2262 + 1.2263 +void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension) 1.2264 +{ 1.2265 + // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request 1.2266 + // Convert to use file picker? No, then embeddors could not do any sort of 1.2267 + // "AutoDownload" w/o showing a prompt 1.2268 + nsresult rv = NS_OK; 1.2269 + if (!mDialog) 1.2270 + { 1.2271 + // Get helper app launcher dialog. 1.2272 + mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); 1.2273 + if (rv != NS_OK) { 1.2274 + Cancel(NS_BINDING_ABORTED); 1.2275 + return; 1.2276 + } 1.2277 + } 1.2278 + 1.2279 + // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape 1.2280 + // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined... 1.2281 + 1.2282 + // Now, be sure to keep |this| alive, and the dialog 1.2283 + // If we don't do this, users that close the helper app dialog while the file 1.2284 + // picker is up would cause Cancel() to be called, and the dialog would be 1.2285 + // released, which would release this object too, which would crash. 1.2286 + // See Bug 249143 1.2287 + nsIFile* fileToUse; 1.2288 + nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this); 1.2289 + nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog); 1.2290 + rv = mDialog->PromptForSaveToFile(this, 1.2291 + mWindowContext, 1.2292 + aDefaultFile.get(), 1.2293 + aFileExtension.get(), 1.2294 + mForceSave, &fileToUse); 1.2295 + 1.2296 + if (rv == NS_ERROR_NOT_AVAILABLE) { 1.2297 + // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync. 1.2298 + rv = mDialog->PromptForSaveToFileAsync(this, 1.2299 + mWindowContext, 1.2300 + aDefaultFile.get(), 1.2301 + aFileExtension.get(), 1.2302 + mForceSave); 1.2303 + } else { 1.2304 + SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr); 1.2305 + } 1.2306 +} 1.2307 + 1.2308 +// SaveToDisk should only be called by the helper app dialog which allows 1.2309 +// the user to say launch with application or save to disk. It doesn't actually 1.2310 +// perform the save, it just prompts for the destination file name. 1.2311 +NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference) 1.2312 +{ 1.2313 + if (mCanceled) 1.2314 + return NS_OK; 1.2315 + 1.2316 + mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); 1.2317 + 1.2318 + if (!aNewFileLocation) { 1.2319 + if (mSuggestedFileName.IsEmpty()) 1.2320 + RequestSaveDestination(mTempLeafName, mTempFileExtension); 1.2321 + else 1.2322 + { 1.2323 + nsAutoString fileExt; 1.2324 + int32_t pos = mSuggestedFileName.RFindChar('.'); 1.2325 + if (pos >= 0) 1.2326 + mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos); 1.2327 + if (fileExt.IsEmpty()) 1.2328 + fileExt = mTempFileExtension; 1.2329 + 1.2330 + RequestSaveDestination(mSuggestedFileName, fileExt); 1.2331 + } 1.2332 + } else { 1.2333 + ContinueSave(aNewFileLocation); 1.2334 + } 1.2335 + 1.2336 + return NS_OK; 1.2337 +} 1.2338 +nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation) 1.2339 +{ 1.2340 + if (mCanceled) 1.2341 + return NS_OK; 1.2342 + 1.2343 + NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file"); 1.2344 + 1.2345 + nsresult rv = NS_OK; 1.2346 + nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation); 1.2347 + mFinalFileDestination = do_QueryInterface(fileToUse); 1.2348 + 1.2349 + // Move what we have in the final directory, but append .part 1.2350 + // to it, to indicate that it's unfinished. Do not call SetTarget on the 1.2351 + // saver if we are done (Finish has been called) but OnSaverComplete has not 1.2352 + // been called. 1.2353 + if (mFinalFileDestination && mSaver && !mStopRequestIssued) 1.2354 + { 1.2355 + nsCOMPtr<nsIFile> movedFile; 1.2356 + mFinalFileDestination->Clone(getter_AddRefs(movedFile)); 1.2357 + if (movedFile) { 1.2358 + // Get the old leaf name and append .part to it 1.2359 + nsAutoString name; 1.2360 + mFinalFileDestination->GetLeafName(name); 1.2361 + name.AppendLiteral(".part"); 1.2362 + movedFile->SetLeafName(name); 1.2363 + 1.2364 + rv = mSaver->SetTarget(movedFile, true); 1.2365 + if (NS_FAILED(rv)) { 1.2366 + nsAutoString path; 1.2367 + mTempFile->GetPath(path); 1.2368 + SendStatusChange(kWriteError, rv, nullptr, path); 1.2369 + Cancel(rv); 1.2370 + return NS_OK; 1.2371 + } 1.2372 + 1.2373 + mTempFile = movedFile; 1.2374 + } 1.2375 + } 1.2376 + 1.2377 + // The helper app dialog has told us what to do and we have a final file 1.2378 + // destination. 1.2379 + rv = CreateTransfer(); 1.2380 + // If we fail to create the transfer, Cancel. 1.2381 + if (NS_FAILED(rv)) { 1.2382 + Cancel(rv); 1.2383 + return rv; 1.2384 + } 1.2385 + 1.2386 + // now that the user has chosen the file location to save to, it's okay to fire the refresh tag 1.2387 + // if there is one. We don't want to do this before the save as dialog goes away because this dialog 1.2388 + // is modal and we do bad things if you try to load a web page in the underlying window while a modal 1.2389 + // dialog is still up. 1.2390 + ProcessAnyRefreshTags(); 1.2391 + 1.2392 + return NS_OK; 1.2393 +} 1.2394 + 1.2395 + 1.2396 +// LaunchWithApplication should only be called by the helper app dialog which 1.2397 +// allows the user to say launch with application or save to disk. It doesn't 1.2398 +// actually perform launch with application. 1.2399 +NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference) 1.2400 +{ 1.2401 + if (mCanceled) 1.2402 + return NS_OK; 1.2403 + 1.2404 + // user has chosen to launch using an application, fire any refresh tags now... 1.2405 + ProcessAnyRefreshTags(); 1.2406 + 1.2407 + if (mMimeInfo && aApplication) { 1.2408 + PlatformLocalHandlerApp_t *handlerApp = 1.2409 + new PlatformLocalHandlerApp_t(EmptyString(), aApplication); 1.2410 + mMimeInfo->SetPreferredApplicationHandler(handlerApp); 1.2411 + } 1.2412 + 1.2413 + // Now check if the file is local, in which case we won't bother with saving 1.2414 + // it to a temporary directory and just launch it from where it is 1.2415 + nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl)); 1.2416 + if (fileUrl && mIsFileChannel) 1.2417 + { 1.2418 + Cancel(NS_BINDING_ABORTED); 1.2419 + nsCOMPtr<nsIFile> file; 1.2420 + nsresult rv = fileUrl->GetFile(getter_AddRefs(file)); 1.2421 + 1.2422 + if (NS_SUCCEEDED(rv)) 1.2423 + { 1.2424 + rv = mMimeInfo->LaunchWithFile(file); 1.2425 + if (NS_SUCCEEDED(rv)) 1.2426 + return NS_OK; 1.2427 + } 1.2428 + nsAutoString path; 1.2429 + if (file) 1.2430 + file->GetPath(path); 1.2431 + // If we get here, an error happened 1.2432 + SendStatusChange(kLaunchError, rv, nullptr, path); 1.2433 + return rv; 1.2434 + } 1.2435 + 1.2436 + // Now that the user has elected to launch the downloaded file with a helper 1.2437 + // app, we're justified in removing the 'salted' name. We'll rename to what 1.2438 + // was specified in mSuggestedFileName after the download is done prior to 1.2439 + // launching the helper app. So that any existing file of that name won't be 1.2440 + // overwritten we call CreateUnique(). Also note that we use the same 1.2441 + // directory as originally downloaded so nsDownload can rename in place 1.2442 + // later. 1.2443 + nsCOMPtr<nsIFile> fileToUse; 1.2444 + (void) GetDownloadDirectory(getter_AddRefs(fileToUse)); 1.2445 + 1.2446 + if (mSuggestedFileName.IsEmpty()) 1.2447 + { 1.2448 + // Keep using the leafname of the temp file, since we're just starting a helper 1.2449 + mSuggestedFileName = mTempLeafName; 1.2450 + } 1.2451 + 1.2452 +#ifdef XP_WIN 1.2453 + fileToUse->Append(mSuggestedFileName + mTempFileExtension); 1.2454 +#else 1.2455 + fileToUse->Append(mSuggestedFileName); 1.2456 +#endif 1.2457 + 1.2458 + nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); 1.2459 + if(NS_SUCCEEDED(rv)) 1.2460 + { 1.2461 + mFinalFileDestination = do_QueryInterface(fileToUse); 1.2462 + // launch the progress window now that the user has picked the desired action. 1.2463 + rv = CreateTransfer(); 1.2464 + if (NS_FAILED(rv)) { 1.2465 + Cancel(rv); 1.2466 + } 1.2467 + } 1.2468 + else 1.2469 + { 1.2470 + // Cancel the download and report an error. We do not want to end up in 1.2471 + // a state where it appears that we have a normal download that is 1.2472 + // pointing to a file that we did not actually create. 1.2473 + nsAutoString path; 1.2474 + mTempFile->GetPath(path); 1.2475 + SendStatusChange(kWriteError, rv, nullptr, path); 1.2476 + Cancel(rv); 1.2477 + } 1.2478 + return rv; 1.2479 +} 1.2480 + 1.2481 +NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) 1.2482 +{ 1.2483 + NS_ENSURE_ARG(NS_FAILED(aReason)); 1.2484 + 1.2485 + if (mCanceled) { 1.2486 + return NS_OK; 1.2487 + } 1.2488 + mCanceled = true; 1.2489 + 1.2490 + if (mSaver) { 1.2491 + // We are still writing to the target file. Give the saver a chance to 1.2492 + // close the target file, then notify the transfer object if necessary in 1.2493 + // the OnSaveComplete callback. 1.2494 + mSaver->Finish(aReason); 1.2495 + mSaver = nullptr; 1.2496 + } else { 1.2497 + if (mStopRequestIssued && mTempFile) { 1.2498 + // This branch can only happen when the user cancels the helper app dialog 1.2499 + // when the request has completed. The temp file has to be removed here, 1.2500 + // because mSaver has been released at that time with the temp file left. 1.2501 + (void)mTempFile->Remove(false); 1.2502 + } 1.2503 + 1.2504 + // Notify the transfer object that the download has been canceled, if the 1.2505 + // user has already chosen an action and we didn't notify already. 1.2506 + if (mTransfer) { 1.2507 + NotifyTransfer(aReason); 1.2508 + } 1.2509 + } 1.2510 + 1.2511 + // Break our reference cycle with the helper app dialog (set up in 1.2512 + // OnStartRequest) 1.2513 + mDialog = nullptr; 1.2514 + 1.2515 + mRequest = nullptr; 1.2516 + 1.2517 + // Release the listener, to break the reference cycle with it (we are the 1.2518 + // observer of the listener). 1.2519 + mDialogProgressListener = nullptr; 1.2520 + 1.2521 + return NS_OK; 1.2522 +} 1.2523 + 1.2524 +void nsExternalAppHandler::ProcessAnyRefreshTags() 1.2525 +{ 1.2526 + // one last thing, try to see if the original window context supports a refresh interface... 1.2527 + // Sometimes, when you download content that requires an external handler, there is 1.2528 + // a refresh header associated with the download. This refresh header points to a page 1.2529 + // the content provider wants the user to see after they download the content. How do we 1.2530 + // pass this refresh information back to the caller? For now, try to get the refresh URI 1.2531 + // interface. If the window context where the request originated came from supports this 1.2532 + // then we can force it to process the refresh information (if there is any) from this channel. 1.2533 + if (mWindowContext && mOriginalChannel) 1.2534 + { 1.2535 + nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext)); 1.2536 + if (refreshHandler) { 1.2537 + refreshHandler->SetupRefreshURI(mOriginalChannel); 1.2538 + } 1.2539 + mOriginalChannel = nullptr; 1.2540 + } 1.2541 +} 1.2542 + 1.2543 +bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType) 1.2544 +{ 1.2545 + // Search the obsolete pref strings. 1.2546 + nsAdoptingCString prefCString = Preferences::GetCString(prefName); 1.2547 + if (prefCString.IsEmpty()) { 1.2548 + // Default is true, if not found in the pref string. 1.2549 + return true; 1.2550 + } 1.2551 + 1.2552 + NS_UnescapeURL(prefCString); 1.2553 + nsACString::const_iterator start, end; 1.2554 + prefCString.BeginReading(start); 1.2555 + prefCString.EndReading(end); 1.2556 + return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType), 1.2557 + start, end); 1.2558 +} 1.2559 + 1.2560 +nsresult nsExternalAppHandler::MaybeCloseWindow() 1.2561 +{ 1.2562 + nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWindowContext); 1.2563 + NS_ENSURE_STATE(window); 1.2564 + 1.2565 + if (mShouldCloseWindow) { 1.2566 + // Reset the window context to the opener window so that the dependent 1.2567 + // dialogs have a parent 1.2568 + nsCOMPtr<nsIDOMWindow> opener; 1.2569 + window->GetOpener(getter_AddRefs(opener)); 1.2570 + 1.2571 + bool isClosed; 1.2572 + if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) { 1.2573 + mWindowContext = do_GetInterface(opener); 1.2574 + 1.2575 + // Now close the old window. Do it on a timer so that we don't run 1.2576 + // into issues trying to close the window before it has fully opened. 1.2577 + NS_ASSERTION(!mTimer, "mTimer was already initialized once!"); 1.2578 + mTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.2579 + if (!mTimer) { 1.2580 + return NS_ERROR_FAILURE; 1.2581 + } 1.2582 + 1.2583 + mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT); 1.2584 + mWindowToClose = window; 1.2585 + } 1.2586 + } 1.2587 + 1.2588 + return NS_OK; 1.2589 +} 1.2590 + 1.2591 +NS_IMETHODIMP 1.2592 +nsExternalAppHandler::Notify(nsITimer* timer) 1.2593 +{ 1.2594 + NS_ASSERTION(mWindowToClose, "No window to close after timer fired"); 1.2595 + 1.2596 + mWindowToClose->Close(); 1.2597 + mWindowToClose = nullptr; 1.2598 + mTimer = nullptr; 1.2599 + 1.2600 + return NS_OK; 1.2601 +} 1.2602 +////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1.2603 +// The following section contains our nsIMIMEService implementation and related methods. 1.2604 +// 1.2605 +////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1.2606 + 1.2607 +// nsIMIMEService methods 1.2608 +NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval) 1.2609 +{ 1.2610 + NS_PRECONDITION(!aMIMEType.IsEmpty() || 1.2611 + !aFileExt.IsEmpty(), 1.2612 + "Give me something to work with"); 1.2613 + LOG(("Getting mimeinfo from type '%s' ext '%s'\n", 1.2614 + PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get())); 1.2615 + 1.2616 + *_retval = nullptr; 1.2617 + 1.2618 + // OK... we need a type. Get one. 1.2619 + nsAutoCString typeToUse(aMIMEType); 1.2620 + if (typeToUse.IsEmpty()) { 1.2621 + nsresult rv = GetTypeFromExtension(aFileExt, typeToUse); 1.2622 + if (NS_FAILED(rv)) 1.2623 + return NS_ERROR_NOT_AVAILABLE; 1.2624 + } 1.2625 + 1.2626 + // We promise to only send lower case mime types to the OS 1.2627 + ToLowerCase(typeToUse); 1.2628 + 1.2629 + // (1) Ask the OS for a mime info 1.2630 + bool found; 1.2631 + *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take(); 1.2632 + LOG(("OS gave back 0x%p - found: %i\n", *_retval, found)); 1.2633 + // If we got no mimeinfo, something went wrong. Probably lack of memory. 1.2634 + if (!*_retval) 1.2635 + return NS_ERROR_OUT_OF_MEMORY; 1.2636 + 1.2637 + // (2) Now, let's see if we can find something in our datastore 1.2638 + // This will not overwrite the OS information that interests us 1.2639 + // (i.e. default application, default app. description) 1.2640 + nsresult rv; 1.2641 + nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); 1.2642 + if (handlerSvc) { 1.2643 + bool hasHandler = false; 1.2644 + (void) handlerSvc->Exists(*_retval, &hasHandler); 1.2645 + if (hasHandler) { 1.2646 + rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString()); 1.2647 + LOG(("Data source: Via type: retval 0x%08x\n", rv)); 1.2648 + } else { 1.2649 + rv = NS_ERROR_NOT_AVAILABLE; 1.2650 + } 1.2651 + 1.2652 + found = found || NS_SUCCEEDED(rv); 1.2653 + 1.2654 + if (!found || NS_FAILED(rv)) { 1.2655 + // No type match, try extension match 1.2656 + if (!aFileExt.IsEmpty()) { 1.2657 + nsAutoCString overrideType; 1.2658 + rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType); 1.2659 + if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) { 1.2660 + // We can't check handlerSvc->Exists() here, because we have a 1.2661 + // overideType. That's ok, it just results in some console noise. 1.2662 + // (If there's no handler for the override type, it throws) 1.2663 + rv = handlerSvc->FillHandlerInfo(*_retval, overrideType); 1.2664 + LOG(("Data source: Via ext: retval 0x%08x\n", rv)); 1.2665 + found = found || NS_SUCCEEDED(rv); 1.2666 + } 1.2667 + } 1.2668 + } 1.2669 + } 1.2670 + 1.2671 + // (3) No match yet. Ask extras. 1.2672 + if (!found) { 1.2673 + rv = NS_ERROR_FAILURE; 1.2674 +#ifdef XP_WIN 1.2675 + /* XXX Gross hack to wallpaper over the most common Win32 1.2676 + * extension issues caused by the fix for bug 116938. See bug 1.2677 + * 120327, comment 271 for why this is needed. Not even sure we 1.2678 + * want to remove this once we have fixed all this stuff to work 1.2679 + * right; any info we get from extras on this type is pretty much 1.2680 + * useless.... 1.2681 + */ 1.2682 + if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator())) 1.2683 +#endif 1.2684 + rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval); 1.2685 + LOG(("Searched extras (by type), rv 0x%08X\n", rv)); 1.2686 + // If that didn't work out, try file extension from extras 1.2687 + if (NS_FAILED(rv) && !aFileExt.IsEmpty()) { 1.2688 + rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval); 1.2689 + LOG(("Searched extras (by ext), rv 0x%08X\n", rv)); 1.2690 + } 1.2691 + // If that still didn't work, set the file description to "ext File" 1.2692 + if (NS_FAILED(rv) && !aFileExt.IsEmpty()) { 1.2693 + // XXXzpao This should probably be localized 1.2694 + nsAutoCString desc(aFileExt); 1.2695 + desc.Append(" File"); 1.2696 + (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc)); 1.2697 + LOG(("Falling back to 'File' file description\n")); 1.2698 + } 1.2699 + } 1.2700 + 1.2701 + // Finally, check if we got a file extension and if yes, if it is an 1.2702 + // extension on the mimeinfo, in which case we want it to be the primary one 1.2703 + if (!aFileExt.IsEmpty()) { 1.2704 + bool matches = false; 1.2705 + (*_retval)->ExtensionExists(aFileExt, &matches); 1.2706 + LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches)); 1.2707 + if (matches) 1.2708 + (*_retval)->SetPrimaryExtension(aFileExt); 1.2709 + } 1.2710 + 1.2711 +#ifdef PR_LOGGING 1.2712 + if (LOG_ENABLED()) { 1.2713 + nsAutoCString type; 1.2714 + (*_retval)->GetMIMEType(type); 1.2715 + 1.2716 + nsAutoCString ext; 1.2717 + (*_retval)->GetPrimaryExtension(ext); 1.2718 + LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get())); 1.2719 + } 1.2720 +#endif 1.2721 + 1.2722 + return NS_OK; 1.2723 +} 1.2724 + 1.2725 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType) 1.2726 +{ 1.2727 + // OK. We want to try the following sources of mimetype information, in this order: 1.2728 + // 1. defaultMimeEntries array 1.2729 + // 2. User-set preferences (managed by the handler service) 1.2730 + // 3. OS-provided information 1.2731 + // 4. our "extras" array 1.2732 + // 5. Information from plugins 1.2733 + // 6. The "ext-to-type-mapping" category 1.2734 + 1.2735 + // Early return if called with an empty extension parameter 1.2736 + if (aFileExt.IsEmpty()) 1.2737 + return NS_ERROR_NOT_AVAILABLE; 1.2738 + 1.2739 + nsresult rv = NS_OK; 1.2740 + // First of all, check our default entries 1.2741 + for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++) 1.2742 + { 1.2743 + if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) { 1.2744 + aContentType = defaultMimeEntries[i].mMimeType; 1.2745 + return rv; 1.2746 + } 1.2747 + } 1.2748 + 1.2749 + // Check user-set prefs 1.2750 + nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID); 1.2751 + if (handlerSvc) 1.2752 + rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType); 1.2753 + if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty()) 1.2754 + return NS_OK; 1.2755 + 1.2756 + // Ask OS. 1.2757 + bool found = false; 1.2758 + nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found); 1.2759 + if (mi && found) 1.2760 + return mi->GetMIMEType(aContentType); 1.2761 + 1.2762 + // Check extras array. 1.2763 + found = GetTypeFromExtras(aFileExt, aContentType); 1.2764 + if (found) 1.2765 + return NS_OK; 1.2766 + 1.2767 + const nsCString& flatExt = PromiseFlatCString(aFileExt); 1.2768 + // Try the plugins 1.2769 + const char* mimeType; 1.2770 + nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv)); 1.2771 + nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); 1.2772 + if (NS_SUCCEEDED(rv)) { 1.2773 + if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) { 1.2774 + aContentType = mimeType; 1.2775 + return NS_OK; 1.2776 + } 1.2777 + } 1.2778 + 1.2779 + rv = NS_OK; 1.2780 + // Let's see if an extension added something 1.2781 + nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1")); 1.2782 + if (catMan) { 1.2783 + // The extension in the category entry is always stored as lowercase 1.2784 + nsAutoCString lowercaseFileExt(aFileExt); 1.2785 + ToLowerCase(lowercaseFileExt); 1.2786 + // Read the MIME type from the category entry, if available 1.2787 + nsXPIDLCString type; 1.2788 + rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(), 1.2789 + getter_Copies(type)); 1.2790 + aContentType = type; 1.2791 + } 1.2792 + else { 1.2793 + rv = NS_ERROR_NOT_AVAILABLE; 1.2794 + } 1.2795 + 1.2796 + return rv; 1.2797 +} 1.2798 + 1.2799 +NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval) 1.2800 +{ 1.2801 + NS_ENSURE_ARG(!aMIMEType.IsEmpty()); 1.2802 + 1.2803 + nsCOMPtr<nsIMIMEInfo> mi; 1.2804 + nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi)); 1.2805 + if (NS_FAILED(rv)) 1.2806 + return rv; 1.2807 + 1.2808 + return mi->GetPrimaryExtension(_retval); 1.2809 +} 1.2810 + 1.2811 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType) 1.2812 +{ 1.2813 + NS_ENSURE_ARG_POINTER(aURI); 1.2814 + nsresult rv = NS_ERROR_NOT_AVAILABLE; 1.2815 + aContentType.Truncate(); 1.2816 + 1.2817 + // First look for a file to use. If we have one, we just use that. 1.2818 + nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI); 1.2819 + if (fileUrl) { 1.2820 + nsCOMPtr<nsIFile> file; 1.2821 + rv = fileUrl->GetFile(getter_AddRefs(file)); 1.2822 + if (NS_SUCCEEDED(rv)) { 1.2823 + rv = GetTypeFromFile(file, aContentType); 1.2824 + if (NS_SUCCEEDED(rv)) { 1.2825 + // we got something! 1.2826 + return rv; 1.2827 + } 1.2828 + } 1.2829 + } 1.2830 + 1.2831 + // Now try to get an nsIURL so we don't have to do our own parsing 1.2832 + nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); 1.2833 + if (url) { 1.2834 + nsAutoCString ext; 1.2835 + rv = url->GetFileExtension(ext); 1.2836 + if (NS_FAILED(rv)) 1.2837 + return rv; 1.2838 + if (ext.IsEmpty()) 1.2839 + return NS_ERROR_NOT_AVAILABLE; 1.2840 + 1.2841 + UnescapeFragment(ext, url, ext); 1.2842 + 1.2843 + return GetTypeFromExtension(ext, aContentType); 1.2844 + } 1.2845 + 1.2846 + // no url, let's give the raw spec a shot 1.2847 + nsAutoCString specStr; 1.2848 + rv = aURI->GetSpec(specStr); 1.2849 + if (NS_FAILED(rv)) 1.2850 + return rv; 1.2851 + UnescapeFragment(specStr, aURI, specStr); 1.2852 + 1.2853 + // find the file extension (if any) 1.2854 + int32_t extLoc = specStr.RFindChar('.'); 1.2855 + int32_t specLength = specStr.Length(); 1.2856 + if (-1 != extLoc && 1.2857 + extLoc != specLength - 1 && 1.2858 + // nothing over 20 chars long can be sanely considered an 1.2859 + // extension.... Dat dere would be just data. 1.2860 + specLength - extLoc < 20) 1.2861 + { 1.2862 + return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType); 1.2863 + } 1.2864 + 1.2865 + // We found no information; say so. 1.2866 + return NS_ERROR_NOT_AVAILABLE; 1.2867 +} 1.2868 + 1.2869 +NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType) 1.2870 +{ 1.2871 + NS_ENSURE_ARG_POINTER(aFile); 1.2872 + nsresult rv; 1.2873 + nsCOMPtr<nsIMIMEInfo> info; 1.2874 + 1.2875 + // Get the Extension 1.2876 + nsAutoString fileName; 1.2877 + rv = aFile->GetLeafName(fileName); 1.2878 + if (NS_FAILED(rv)) return rv; 1.2879 + 1.2880 + nsAutoCString fileExt; 1.2881 + if (!fileName.IsEmpty()) 1.2882 + { 1.2883 + int32_t len = fileName.Length(); 1.2884 + for (int32_t i = len; i >= 0; i--) 1.2885 + { 1.2886 + if (fileName[i] == char16_t('.')) 1.2887 + { 1.2888 + CopyUTF16toUTF8(fileName.get() + i + 1, fileExt); 1.2889 + break; 1.2890 + } 1.2891 + } 1.2892 + } 1.2893 + 1.2894 + if (fileExt.IsEmpty()) 1.2895 + return NS_ERROR_FAILURE; 1.2896 + 1.2897 + return GetTypeFromExtension(fileExt, aContentType); 1.2898 +} 1.2899 + 1.2900 +nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras( 1.2901 + const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo) 1.2902 +{ 1.2903 + NS_ENSURE_ARG( aMIMEInfo ); 1.2904 + 1.2905 + NS_ENSURE_ARG( !aContentType.IsEmpty() ); 1.2906 + 1.2907 + // Look for default entry with matching mime type. 1.2908 + nsAutoCString MIMEType(aContentType); 1.2909 + ToLowerCase(MIMEType); 1.2910 + int32_t numEntries = ArrayLength(extraMimeEntries); 1.2911 + for (int32_t index = 0; index < numEntries; index++) 1.2912 + { 1.2913 + if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) ) 1.2914 + { 1.2915 + // This is the one. Set attributes appropriately. 1.2916 + aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions)); 1.2917 + aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription)); 1.2918 + return NS_OK; 1.2919 + } 1.2920 + } 1.2921 + 1.2922 + return NS_ERROR_NOT_AVAILABLE; 1.2923 +} 1.2924 + 1.2925 +nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras( 1.2926 + const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo) 1.2927 +{ 1.2928 + nsAutoCString type; 1.2929 + bool found = GetTypeFromExtras(aExtension, type); 1.2930 + if (!found) 1.2931 + return NS_ERROR_NOT_AVAILABLE; 1.2932 + return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo); 1.2933 +} 1.2934 + 1.2935 +bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType) 1.2936 +{ 1.2937 + NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!"); 1.2938 + 1.2939 + // Look for default entry with matching extension. 1.2940 + nsDependentCString::const_iterator start, end, iter; 1.2941 + int32_t numEntries = ArrayLength(extraMimeEntries); 1.2942 + for (int32_t index = 0; index < numEntries; index++) 1.2943 + { 1.2944 + nsDependentCString extList(extraMimeEntries[index].mFileExtensions); 1.2945 + extList.BeginReading(start); 1.2946 + extList.EndReading(end); 1.2947 + iter = start; 1.2948 + while (start != end) 1.2949 + { 1.2950 + FindCharInReadable(',', iter, end); 1.2951 + if (Substring(start, iter).Equals(aExtension, 1.2952 + nsCaseInsensitiveCStringComparator())) 1.2953 + { 1.2954 + aMIMEType = extraMimeEntries[index].mMimeType; 1.2955 + return true; 1.2956 + } 1.2957 + if (iter != end) { 1.2958 + ++iter; 1.2959 + } 1.2960 + start = iter; 1.2961 + } 1.2962 + } 1.2963 + 1.2964 + return false; 1.2965 +}