uriloader/exthandler/nsExternalHelperAppService.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * vim:expandtab:shiftwidth=2:tabstop=2:cin:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #ifdef MOZ_LOGGING
michael@0 8 #define FORCE_PR_LOG
michael@0 9 #endif
michael@0 10
michael@0 11 #include "base/basictypes.h"
michael@0 12
michael@0 13 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
michael@0 14 #include "mozilla/ArrayUtils.h"
michael@0 15 #include "mozilla/Base64.h"
michael@0 16
michael@0 17 #include "mozilla/dom/ContentChild.h"
michael@0 18 #include "mozilla/dom/TabChild.h"
michael@0 19 #include "nsXULAppAPI.h"
michael@0 20
michael@0 21 #include "nsExternalHelperAppService.h"
michael@0 22 #include "nsCExternalHandlerService.h"
michael@0 23 #include "nsIURI.h"
michael@0 24 #include "nsIURL.h"
michael@0 25 #include "nsIFile.h"
michael@0 26 #include "nsIFileURL.h"
michael@0 27 #include "nsIChannel.h"
michael@0 28 #include "nsIDirectoryService.h"
michael@0 29 #include "nsAppDirectoryServiceDefs.h"
michael@0 30 #include "nsICategoryManager.h"
michael@0 31 #include "nsDependentSubstring.h"
michael@0 32 #include "nsXPIDLString.h"
michael@0 33 #include "nsUnicharUtils.h"
michael@0 34 #include "nsIStringEnumerator.h"
michael@0 35 #include "nsMemory.h"
michael@0 36 #include "nsIStreamListener.h"
michael@0 37 #include "nsIMIMEService.h"
michael@0 38 #include "nsILoadGroup.h"
michael@0 39 #include "nsIWebProgressListener.h"
michael@0 40 #include "nsITransfer.h"
michael@0 41 #include "nsReadableUtils.h"
michael@0 42 #include "nsIRequest.h"
michael@0 43 #include "nsDirectoryServiceDefs.h"
michael@0 44 #include "nsIInterfaceRequestor.h"
michael@0 45 #include "nsThreadUtils.h"
michael@0 46 #include "nsAutoPtr.h"
michael@0 47 #include "nsIMutableArray.h"
michael@0 48
michael@0 49 // used to access our datastore of user-configured helper applications
michael@0 50 #include "nsIHandlerService.h"
michael@0 51 #include "nsIMIMEInfo.h"
michael@0 52 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
michael@0 53 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
michael@0 54 #include "nsIHelperAppLauncherDialog.h"
michael@0 55 #include "nsIContentDispatchChooser.h"
michael@0 56 #include "nsNetUtil.h"
michael@0 57 #include "nsIIOService.h"
michael@0 58 #include "nsNetCID.h"
michael@0 59 #include "nsChannelProperties.h"
michael@0 60
michael@0 61 #include "nsMimeTypes.h"
michael@0 62 // used for header disposition information.
michael@0 63 #include "nsIHttpChannel.h"
michael@0 64 #include "nsIHttpChannelInternal.h"
michael@0 65 #include "nsIEncodedChannel.h"
michael@0 66 #include "nsIMultiPartChannel.h"
michael@0 67 #include "nsIFileChannel.h"
michael@0 68 #include "nsIObserverService.h" // so we can be a profile change observer
michael@0 69 #include "nsIPropertyBag2.h" // for the 64-bit content length
michael@0 70
michael@0 71 #ifdef XP_MACOSX
michael@0 72 #include "nsILocalFileMac.h"
michael@0 73 #endif
michael@0 74
michael@0 75 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
michael@0 76 #include "nsPluginHost.h"
michael@0 77 #include "nsEscape.h"
michael@0 78
michael@0 79 #include "nsIStringBundle.h" // XXX needed to localize error msgs
michael@0 80 #include "nsIPrompt.h"
michael@0 81
michael@0 82 #include "nsITextToSubURI.h" // to unescape the filename
michael@0 83 #include "nsIMIMEHeaderParam.h"
michael@0 84
michael@0 85 #include "nsIWindowWatcher.h"
michael@0 86
michael@0 87 #include "nsIDownloadHistory.h" // to mark downloads as visited
michael@0 88 #include "nsDocShellCID.h"
michael@0 89
michael@0 90 #include "nsCRT.h"
michael@0 91 #include "nsLocalHandlerApp.h"
michael@0 92
michael@0 93 #include "nsIRandomGenerator.h"
michael@0 94
michael@0 95 #include "ContentChild.h"
michael@0 96 #include "nsXULAppAPI.h"
michael@0 97 #include "nsPIDOMWindow.h"
michael@0 98 #include "nsIDocShellTreeOwner.h"
michael@0 99 #include "nsIDocShellTreeItem.h"
michael@0 100 #include "ExternalHelperAppChild.h"
michael@0 101
michael@0 102 #ifdef XP_WIN
michael@0 103 #include "nsWindowsHelpers.h"
michael@0 104 #endif
michael@0 105
michael@0 106 #ifdef MOZ_WIDGET_ANDROID
michael@0 107 #include "AndroidBridge.h"
michael@0 108 #endif
michael@0 109
michael@0 110 #include "mozilla/Preferences.h"
michael@0 111 #include "mozilla/ipc/URIUtils.h"
michael@0 112
michael@0 113 #ifdef MOZ_WIDGET_GONK
michael@0 114 #include "nsDeviceStorage.h"
michael@0 115 #endif
michael@0 116
michael@0 117 #ifdef NECKO_PROTOCOL_rtsp
michael@0 118 #include "nsIScriptSecurityManager.h"
michael@0 119 #include "nsIMessageManager.h"
michael@0 120 #endif
michael@0 121
michael@0 122 using namespace mozilla;
michael@0 123 using namespace mozilla::ipc;
michael@0 124
michael@0 125 // Download Folder location constants
michael@0 126 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
michael@0 127 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
michael@0 128 enum {
michael@0 129 NS_FOLDER_VALUE_DESKTOP = 0
michael@0 130 , NS_FOLDER_VALUE_DOWNLOADS = 1
michael@0 131 , NS_FOLDER_VALUE_CUSTOM = 2
michael@0 132 };
michael@0 133
michael@0 134 #ifdef PR_LOGGING
michael@0 135 PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr;
michael@0 136 #endif
michael@0 137
michael@0 138 // Using level 3 here because the OSHelperAppServices use a log level
michael@0 139 // of PR_LOG_DEBUG (4), and we want less detailed output here
michael@0 140 // Using 3 instead of PR_LOG_WARN because we don't output warnings
michael@0 141 #undef LOG
michael@0 142 #define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
michael@0 143 #define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
michael@0 144
michael@0 145 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
michael@0 146 "browser.helperApps.neverAsk.saveToDisk";
michael@0 147 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
michael@0 148 "browser.helperApps.neverAsk.openFile";
michael@0 149
michael@0 150 // Helper functions for Content-Disposition headers
michael@0 151
michael@0 152 /**
michael@0 153 * Given a URI fragment, unescape it
michael@0 154 * @param aFragment The string to unescape
michael@0 155 * @param aURI The URI from which this fragment is taken. Only its character set
michael@0 156 * will be used.
michael@0 157 * @param aResult [out] Unescaped string.
michael@0 158 */
michael@0 159 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
michael@0 160 nsAString& aResult)
michael@0 161 {
michael@0 162 // First, we need a charset
michael@0 163 nsAutoCString originCharset;
michael@0 164 nsresult rv = aURI->GetOriginCharset(originCharset);
michael@0 165 NS_ENSURE_SUCCESS(rv, rv);
michael@0 166
michael@0 167 // Now, we need the unescaper
michael@0 168 nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
michael@0 169 NS_ENSURE_SUCCESS(rv, rv);
michael@0 170
michael@0 171 return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
michael@0 172 }
michael@0 173
michael@0 174 /**
michael@0 175 * UTF-8 version of UnescapeFragment.
michael@0 176 * @param aFragment The string to unescape
michael@0 177 * @param aURI The URI from which this fragment is taken. Only its character set
michael@0 178 * will be used.
michael@0 179 * @param aResult [out] Unescaped string, UTF-8 encoded.
michael@0 180 * @note It is safe to pass the same string for aFragment and aResult.
michael@0 181 * @note When this function fails, aResult will not be modified.
michael@0 182 */
michael@0 183 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
michael@0 184 nsACString& aResult)
michael@0 185 {
michael@0 186 nsAutoString result;
michael@0 187 nsresult rv = UnescapeFragment(aFragment, aURI, result);
michael@0 188 if (NS_SUCCEEDED(rv))
michael@0 189 CopyUTF16toUTF8(result, aResult);
michael@0 190 return rv;
michael@0 191 }
michael@0 192
michael@0 193 /**
michael@0 194 * Given a channel, returns the filename and extension the channel has.
michael@0 195 * This uses the URL and other sources (nsIMultiPartChannel).
michael@0 196 * Also gives back whether the channel requested external handling (i.e.
michael@0 197 * whether Content-Disposition: attachment was sent)
michael@0 198 * @param aChannel The channel to extract the filename/extension from
michael@0 199 * @param aFileName [out] Reference to the string where the filename should be
michael@0 200 * stored. Empty if it could not be retrieved.
michael@0 201 * WARNING - this filename may contain characters which the OS does not
michael@0 202 * allow as part of filenames!
michael@0 203 * @param aExtension [out] Reference to the string where the extension should
michael@0 204 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
michael@0 205 * @param aAllowURLExtension (optional) Get the extension from the URL if no
michael@0 206 * Content-Disposition header is present. Default is true.
michael@0 207 * @retval true The server sent Content-Disposition:attachment or equivalent
michael@0 208 * @retval false Content-Disposition: inline or no content-disposition header
michael@0 209 * was sent.
michael@0 210 */
michael@0 211 static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
michael@0 212 nsString& aFileName,
michael@0 213 nsCString& aExtension,
michael@0 214 bool aAllowURLExtension = true)
michael@0 215 {
michael@0 216 aExtension.Truncate();
michael@0 217 /*
michael@0 218 * If the channel is an http or part of a multipart channel and we
michael@0 219 * have a content disposition header set, then use the file name
michael@0 220 * suggested there as the preferred file name to SUGGEST to the
michael@0 221 * user. we shouldn't actually use that without their
michael@0 222 * permission... otherwise just use our temp file
michael@0 223 */
michael@0 224 bool handleExternally = false;
michael@0 225 uint32_t disp;
michael@0 226 nsresult rv = aChannel->GetContentDisposition(&disp);
michael@0 227 if (NS_SUCCEEDED(rv))
michael@0 228 {
michael@0 229 aChannel->GetContentDispositionFilename(aFileName);
michael@0 230 if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
michael@0 231 handleExternally = true;
michael@0 232 }
michael@0 233
michael@0 234 // If the disposition header didn't work, try the filename from nsIURL
michael@0 235 nsCOMPtr<nsIURI> uri;
michael@0 236 aChannel->GetURI(getter_AddRefs(uri));
michael@0 237 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
michael@0 238 if (url && aFileName.IsEmpty())
michael@0 239 {
michael@0 240 if (aAllowURLExtension) {
michael@0 241 url->GetFileExtension(aExtension);
michael@0 242 UnescapeFragment(aExtension, url, aExtension);
michael@0 243
michael@0 244 // Windows ignores terminating dots. So we have to as well, so
michael@0 245 // that our security checks do "the right thing"
michael@0 246 // In case the aExtension consisted only of the dot, the code below will
michael@0 247 // extract an aExtension from the filename
michael@0 248 aExtension.Trim(".", false);
michael@0 249 }
michael@0 250
michael@0 251 // try to extract the file name from the url and use that as a first pass as the
michael@0 252 // leaf name of our temp file...
michael@0 253 nsAutoCString leafName;
michael@0 254 url->GetFileName(leafName);
michael@0 255 if (!leafName.IsEmpty())
michael@0 256 {
michael@0 257 rv = UnescapeFragment(leafName, url, aFileName);
michael@0 258 if (NS_FAILED(rv))
michael@0 259 {
michael@0 260 CopyUTF8toUTF16(leafName, aFileName); // use escaped name
michael@0 261 }
michael@0 262 }
michael@0 263 }
michael@0 264
michael@0 265 // Extract Extension, if we have a filename; otherwise,
michael@0 266 // truncate the string
michael@0 267 if (aExtension.IsEmpty()) {
michael@0 268 if (!aFileName.IsEmpty())
michael@0 269 {
michael@0 270 // Windows ignores terminating dots. So we have to as well, so
michael@0 271 // that our security checks do "the right thing"
michael@0 272 aFileName.Trim(".", false);
michael@0 273
michael@0 274 // XXX RFindCharInReadable!!
michael@0 275 nsAutoString fileNameStr(aFileName);
michael@0 276 int32_t idx = fileNameStr.RFindChar(char16_t('.'));
michael@0 277 if (idx != kNotFound)
michael@0 278 CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
michael@0 279 }
michael@0 280 }
michael@0 281
michael@0 282
michael@0 283 return handleExternally;
michael@0 284 }
michael@0 285
michael@0 286 /**
michael@0 287 * Obtains the directory to use. This tends to vary per platform, and
michael@0 288 * needs to be consistent throughout our codepaths. For platforms where
michael@0 289 * helper apps use the downloads directory, this should be kept in
michael@0 290 * sync with nsDownloadManager.cpp
michael@0 291 *
michael@0 292 * Optionally skip availability of the directory and storage.
michael@0 293 */
michael@0 294 static nsresult GetDownloadDirectory(nsIFile **_directory,
michael@0 295 bool aSkipChecks = false)
michael@0 296 {
michael@0 297 nsCOMPtr<nsIFile> dir;
michael@0 298 #ifdef XP_MACOSX
michael@0 299 // On OS X, we first try to get the users download location, if it's set.
michael@0 300 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
michael@0 301 case NS_FOLDER_VALUE_DESKTOP:
michael@0 302 (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
michael@0 303 break;
michael@0 304 case NS_FOLDER_VALUE_CUSTOM:
michael@0 305 {
michael@0 306 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
michael@0 307 NS_GET_IID(nsIFile),
michael@0 308 getter_AddRefs(dir));
michael@0 309 if (!dir) break;
michael@0 310
michael@0 311 // If we're not checking for availability we're done.
michael@0 312 if (aSkipChecks) {
michael@0 313 dir.forget(_directory);
michael@0 314 return NS_OK;
michael@0 315 }
michael@0 316
michael@0 317 // We have the directory, and now we need to make sure it exists
michael@0 318 bool dirExists = false;
michael@0 319 (void) dir->Exists(&dirExists);
michael@0 320 if (dirExists) break;
michael@0 321
michael@0 322 nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
michael@0 323 if (NS_FAILED(rv)) {
michael@0 324 dir = nullptr;
michael@0 325 break;
michael@0 326 }
michael@0 327 }
michael@0 328 break;
michael@0 329 case NS_FOLDER_VALUE_DOWNLOADS:
michael@0 330 // This is just the OS default location, so fall out
michael@0 331 break;
michael@0 332 }
michael@0 333
michael@0 334 if (!dir) {
michael@0 335 // If not, we default to the OS X default download location.
michael@0 336 nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
michael@0 337 getter_AddRefs(dir));
michael@0 338 NS_ENSURE_SUCCESS(rv, rv);
michael@0 339 }
michael@0 340 #elif defined(MOZ_WIDGET_GONK)
michael@0 341 // On Gonk, store the files on the sdcard in the downloads directory.
michael@0 342 // We need to check with the volume manager which storage point is
michael@0 343 // available.
michael@0 344
michael@0 345 // Pick the default storage in case multiple (internal and external) ones
michael@0 346 // are available.
michael@0 347 nsString storageName;
michael@0 348 nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
michael@0 349 storageName);
michael@0 350
michael@0 351 DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
michael@0 352 storageName,
michael@0 353 NS_LITERAL_STRING("downloads"));
michael@0 354 NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED);
michael@0 355
michael@0 356 // If we're not checking for availability we're done.
michael@0 357 if (aSkipChecks) {
michael@0 358 dsf.mFile.forget(_directory);
michael@0 359 return NS_OK;
michael@0 360 }
michael@0 361
michael@0 362 // Check device storage status before continuing.
michael@0 363 nsString storageStatus;
michael@0 364 dsf.GetStatus(storageStatus);
michael@0 365
michael@0 366 // If we get an "unavailable" status, it means the sd card is not present.
michael@0 367 // We'll also catch internal errors by looking for an empty string and assume
michael@0 368 // the SD card isn't present when this occurs.
michael@0 369 if (storageStatus.EqualsLiteral("unavailable") ||
michael@0 370 storageStatus.IsEmpty()) {
michael@0 371 return NS_ERROR_FILE_NOT_FOUND;
michael@0 372 }
michael@0 373
michael@0 374 // If we get a status other than 'available' here it means the card is busy
michael@0 375 // because it's mounted via USB or it is being formatted.
michael@0 376 if (!storageStatus.EqualsLiteral("available")) {
michael@0 377 return NS_ERROR_FILE_ACCESS_DENIED;
michael@0 378 }
michael@0 379
michael@0 380 bool alreadyThere;
michael@0 381 nsresult rv = dsf.mFile->Exists(&alreadyThere);
michael@0 382 NS_ENSURE_SUCCESS(rv, rv);
michael@0 383 if (!alreadyThere) {
michael@0 384 rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
michael@0 385 NS_ENSURE_SUCCESS(rv, rv);
michael@0 386 }
michael@0 387 dir = dsf.mFile;
michael@0 388 #elif defined(ANDROID)
michael@0 389 // On mobile devices, we are avoiding exposing users to the file
michael@0 390 // system, and don't save downloads to temp directories
michael@0 391
michael@0 392 // On Android we only return something if we have and SD-card
michael@0 393 char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
michael@0 394 nsresult rv;
michael@0 395 if (downloadDir) {
michael@0 396 nsCOMPtr<nsIFile> ldir;
michael@0 397 rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir),
michael@0 398 true, getter_AddRefs(ldir));
michael@0 399 NS_ENSURE_SUCCESS(rv, rv);
michael@0 400 dir = do_QueryInterface(ldir);
michael@0 401
michael@0 402 // If we're not checking for availability we're done.
michael@0 403 if (aSkipChecks) {
michael@0 404 dir.forget(_directory);
michael@0 405 return NS_OK;
michael@0 406 }
michael@0 407 }
michael@0 408 else {
michael@0 409 return NS_ERROR_FAILURE;
michael@0 410 }
michael@0 411 #elif defined(XP_WIN)
michael@0 412 // On metro we want to be able to search opened files and the temp directory
michael@0 413 // is exlcuded in searches.
michael@0 414 nsresult rv;
michael@0 415 if (IsRunningInWindowsMetro()) {
michael@0 416 rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir));
michael@0 417 } else {
michael@0 418 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
michael@0 419 }
michael@0 420 NS_ENSURE_SUCCESS(rv, rv);
michael@0 421 #else
michael@0 422 // On all other platforms, we default to the systems temporary directory.
michael@0 423 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
michael@0 424 NS_ENSURE_SUCCESS(rv, rv);
michael@0 425 #endif
michael@0 426
michael@0 427 NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
michael@0 428 dir.forget(_directory);
michael@0 429 return NS_OK;
michael@0 430 }
michael@0 431
michael@0 432 /**
michael@0 433 * Structure for storing extension->type mappings.
michael@0 434 * @see defaultMimeEntries
michael@0 435 */
michael@0 436 struct nsDefaultMimeTypeEntry {
michael@0 437 const char* mMimeType;
michael@0 438 const char* mFileExtension;
michael@0 439 };
michael@0 440
michael@0 441 /**
michael@0 442 * Default extension->mimetype mappings. These are not overridable.
michael@0 443 * If you add types here, make sure they are lowercase, or you'll regret it.
michael@0 444 */
michael@0 445 static nsDefaultMimeTypeEntry defaultMimeEntries [] =
michael@0 446 {
michael@0 447 // The following are those extensions that we're asked about during startup,
michael@0 448 // sorted by order used
michael@0 449 { IMAGE_GIF, "gif" },
michael@0 450 { TEXT_XML, "xml" },
michael@0 451 { APPLICATION_RDF, "rdf" },
michael@0 452 { TEXT_XUL, "xul" },
michael@0 453 { IMAGE_PNG, "png" },
michael@0 454 // -- end extensions used during startup
michael@0 455 { TEXT_CSS, "css" },
michael@0 456 { IMAGE_JPEG, "jpeg" },
michael@0 457 { IMAGE_JPEG, "jpg" },
michael@0 458 { IMAGE_SVG_XML, "svg" },
michael@0 459 { TEXT_HTML, "html" },
michael@0 460 { TEXT_HTML, "htm" },
michael@0 461 { APPLICATION_XPINSTALL, "xpi" },
michael@0 462 { "application/xhtml+xml", "xhtml" },
michael@0 463 { "application/xhtml+xml", "xht" },
michael@0 464 { TEXT_PLAIN, "txt" },
michael@0 465 { VIDEO_OGG, "ogv" },
michael@0 466 { VIDEO_OGG, "ogg" },
michael@0 467 { APPLICATION_OGG, "ogg" },
michael@0 468 { AUDIO_OGG, "oga" },
michael@0 469 #ifdef MOZ_OPUS
michael@0 470 { AUDIO_OGG, "opus" },
michael@0 471 #endif
michael@0 472 #ifdef MOZ_WEBM
michael@0 473 { VIDEO_WEBM, "webm" },
michael@0 474 { AUDIO_WEBM, "webm" },
michael@0 475 #endif
michael@0 476 #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
michael@0 477 { VIDEO_MP4, "mp4" },
michael@0 478 { AUDIO_MP4, "m4a" },
michael@0 479 { AUDIO_MP3, "mp3" },
michael@0 480 #endif
michael@0 481 #ifdef MOZ_RAW
michael@0 482 { VIDEO_RAW, "yuv" }
michael@0 483 #endif
michael@0 484 };
michael@0 485
michael@0 486 /**
michael@0 487 * This is a small private struct used to help us initialize some
michael@0 488 * default mime types.
michael@0 489 */
michael@0 490 struct nsExtraMimeTypeEntry {
michael@0 491 const char* mMimeType;
michael@0 492 const char* mFileExtensions;
michael@0 493 const char* mDescription;
michael@0 494 };
michael@0 495
michael@0 496 #ifdef XP_MACOSX
michael@0 497 #define MAC_TYPE(x) x
michael@0 498 #else
michael@0 499 #define MAC_TYPE(x) 0
michael@0 500 #endif
michael@0 501
michael@0 502 /**
michael@0 503 * This table lists all of the 'extra' content types that we can deduce from particular
michael@0 504 * file extensions. These entries also ensure that we provide a good descriptive name
michael@0 505 * when we encounter files with these content types and/or extensions. These can be
michael@0 506 * overridden by user helper app prefs.
michael@0 507 * If you add types here, make sure they are lowercase, or you'll regret it.
michael@0 508 */
michael@0 509 static nsExtraMimeTypeEntry extraMimeEntries [] =
michael@0 510 {
michael@0 511 #if defined(VMS)
michael@0 512 { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" },
michael@0 513 #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
michael@0 514 { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
michael@0 515 #else
michael@0 516 { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
michael@0 517 #endif
michael@0 518 { APPLICATION_GZIP2, "gz", "gzip" },
michael@0 519 { "application/x-arj", "arj", "ARJ file" },
michael@0 520 { "application/rtf", "rtf", "Rich Text Format File" },
michael@0 521 { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
michael@0 522 { APPLICATION_PDF, "pdf", "Portable Document Format" },
michael@0 523 { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
michael@0 524 { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
michael@0 525 { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
michael@0 526 #ifdef MOZ_WIDGET_ANDROID
michael@0 527 { "application/vnd.android.package-archive", "apk", "Android Package" },
michael@0 528 #endif
michael@0 529 { IMAGE_ART, "art", "ART Image" },
michael@0 530 { IMAGE_BMP, "bmp", "BMP Image" },
michael@0 531 { IMAGE_GIF, "gif", "GIF Image" },
michael@0 532 { IMAGE_ICO, "ico,cur", "ICO Image" },
michael@0 533 { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
michael@0 534 { IMAGE_PNG, "png", "PNG Image" },
michael@0 535 { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
michael@0 536 { IMAGE_XBM, "xbm", "XBM Image" },
michael@0 537 { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
michael@0 538 { MESSAGE_RFC822, "eml", "RFC-822 data" },
michael@0 539 { TEXT_PLAIN, "txt,text", "Text File" },
michael@0 540 { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
michael@0 541 { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
michael@0 542 { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
michael@0 543 { APPLICATION_RDF, "rdf", "Resource Description Framework" },
michael@0 544 { TEXT_XUL, "xul", "XML-Based User Interface Language" },
michael@0 545 { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
michael@0 546 { TEXT_CSS, "css", "Style Sheet" },
michael@0 547 { TEXT_VCARD, "vcf,vcard", "Contact Information" },
michael@0 548 { VIDEO_OGG, "ogv", "Ogg Video" },
michael@0 549 { VIDEO_OGG, "ogg", "Ogg Video" },
michael@0 550 { APPLICATION_OGG, "ogg", "Ogg Video"},
michael@0 551 { AUDIO_OGG, "oga", "Ogg Audio" },
michael@0 552 { AUDIO_OGG, "opus", "Opus Audio" },
michael@0 553 #ifdef MOZ_WIDGET_GONK
michael@0 554 { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
michael@0 555 #endif
michael@0 556 { VIDEO_WEBM, "webm", "Web Media Video" },
michael@0 557 { AUDIO_WEBM, "webm", "Web Media Audio" },
michael@0 558 { AUDIO_MP3, "mp3", "MPEG Audio" },
michael@0 559 { VIDEO_MP4, "mp4", "MPEG-4 Video" },
michael@0 560 { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
michael@0 561 { VIDEO_RAW, "yuv", "Raw YUV Video" },
michael@0 562 { AUDIO_WAV, "wav", "Waveform Audio" },
michael@0 563 { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
michael@0 564 { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
michael@0 565 };
michael@0 566
michael@0 567 #undef MAC_TYPE
michael@0 568
michael@0 569 /**
michael@0 570 * File extensions for which decoding should be disabled.
michael@0 571 * NOTE: These MUST be lower-case and ASCII.
michael@0 572 */
michael@0 573 static nsDefaultMimeTypeEntry nonDecodableExtensions [] = {
michael@0 574 { APPLICATION_GZIP, "gz" },
michael@0 575 { APPLICATION_GZIP, "tgz" },
michael@0 576 { APPLICATION_ZIP, "zip" },
michael@0 577 { APPLICATION_COMPRESS, "z" },
michael@0 578 { APPLICATION_GZIP, "svgz" }
michael@0 579 };
michael@0 580
michael@0 581 NS_IMPL_ISUPPORTS(
michael@0 582 nsExternalHelperAppService,
michael@0 583 nsIExternalHelperAppService,
michael@0 584 nsPIExternalAppLauncher,
michael@0 585 nsIExternalProtocolService,
michael@0 586 nsIMIMEService,
michael@0 587 nsIObserver,
michael@0 588 nsISupportsWeakReference)
michael@0 589
michael@0 590 nsExternalHelperAppService::nsExternalHelperAppService()
michael@0 591 {
michael@0 592 }
michael@0 593 nsresult nsExternalHelperAppService::Init()
michael@0 594 {
michael@0 595 // Add an observer for profile change
michael@0 596 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 597 if (!obs)
michael@0 598 return NS_ERROR_FAILURE;
michael@0 599
michael@0 600 #ifdef PR_LOGGING
michael@0 601 if (!mLog) {
michael@0 602 mLog = PR_NewLogModule("HelperAppService");
michael@0 603 if (!mLog)
michael@0 604 return NS_ERROR_OUT_OF_MEMORY;
michael@0 605 }
michael@0 606 #endif
michael@0 607
michael@0 608 nsresult rv = obs->AddObserver(this, "profile-before-change", true);
michael@0 609 NS_ENSURE_SUCCESS(rv, rv);
michael@0 610 return obs->AddObserver(this, "last-pb-context-exited", true);
michael@0 611 }
michael@0 612
michael@0 613 nsExternalHelperAppService::~nsExternalHelperAppService()
michael@0 614 {
michael@0 615 }
michael@0 616
michael@0 617 #ifdef NECKO_PROTOCOL_rtsp
michael@0 618 namespace {
michael@0 619 /**
michael@0 620 * A stack helper to clear the currently pending exception in a JS context.
michael@0 621 */
michael@0 622 class AutoClearPendingException {
michael@0 623 public:
michael@0 624 AutoClearPendingException(JSContext* aCx) :
michael@0 625 mCx(aCx) {
michael@0 626 }
michael@0 627 ~AutoClearPendingException() {
michael@0 628 JS_ClearPendingException(mCx);
michael@0 629 }
michael@0 630 private:
michael@0 631 JSContext *mCx;
michael@0 632 };
michael@0 633 } // anonymous namespace
michael@0 634
michael@0 635 /**
michael@0 636 * This function sends a message. This 'content-handler' message is handled in
michael@0 637 * b2g/chrome/content/shell.js where it starts an activity request that will
michael@0 638 * open the video app.
michael@0 639 */
michael@0 640 void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI)
michael@0 641 {
michael@0 642 bool rv;
michael@0 643
michael@0 644 // Get a system principal.
michael@0 645 nsCOMPtr<nsIScriptSecurityManager> securityManager =
michael@0 646 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
michael@0 647 NS_ENSURE_TRUE_VOID(securityManager);
michael@0 648
michael@0 649 nsCOMPtr<nsIPrincipal> principal;
michael@0 650 securityManager->GetSystemPrincipal(getter_AddRefs(principal));
michael@0 651 NS_ENSURE_TRUE_VOID(principal);
michael@0 652
michael@0 653 // Construct the message in jsVal format.
michael@0 654 AutoSafeJSContext cx;
michael@0 655 AutoClearPendingException helper(cx);
michael@0 656 JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
michael@0 657 NS_ENSURE_TRUE_VOID(msgObj);
michael@0 658 JS::Rooted<JS::Value> jsVal(cx);
michael@0 659
michael@0 660 // Set the "type" property of the message. This is a fake MIME type.
michael@0 661 {
michael@0 662 NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp");
michael@0 663 JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length());
michael@0 664 NS_ENSURE_TRUE_VOID(typeStr);
michael@0 665 jsVal.setString(typeStr);
michael@0 666 rv = JS_SetProperty(cx, msgObj, "type", jsVal);
michael@0 667 NS_ENSURE_TRUE_VOID(rv);
michael@0 668 }
michael@0 669 // Set the "url" and "title" properties of the message.
michael@0 670 // They are the same in the case of RTSP streaming.
michael@0 671 {
michael@0 672 nsAutoCString spec;
michael@0 673 aURI->GetSpec(spec);
michael@0 674 JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length());
michael@0 675 NS_ENSURE_TRUE_VOID(urlStr);
michael@0 676 jsVal.setString(urlStr);
michael@0 677 rv = JS_SetProperty(cx, msgObj, "url", jsVal);
michael@0 678 NS_ENSURE_TRUE_VOID(rv);
michael@0 679 rv = JS_SetProperty(cx, msgObj, "title", jsVal);
michael@0 680 }
michael@0 681 jsVal.setObject(*msgObj);
michael@0 682
michael@0 683 // Send the message.
michael@0 684 nsCOMPtr<nsIMessageSender> cpmm =
michael@0 685 do_GetService("@mozilla.org/childprocessmessagemanager;1");
michael@0 686 NS_ENSURE_TRUE_VOID(cpmm);
michael@0 687 cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"),
michael@0 688 jsVal, JS::NullHandleValue, principal, cx, 2);
michael@0 689 }
michael@0 690 #endif
michael@0 691
michael@0 692 NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
michael@0 693 nsIRequest *aRequest,
michael@0 694 nsIInterfaceRequestor *aWindowContext,
michael@0 695 bool aForceSave,
michael@0 696 nsIStreamListener ** aStreamListener)
michael@0 697 {
michael@0 698 nsAutoString fileName;
michael@0 699 nsAutoCString fileExtension;
michael@0 700 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
michael@0 701 uint32_t contentDisposition = -1;
michael@0 702
michael@0 703 nsresult rv;
michael@0 704
michael@0 705 // Get the file extension and name that we will need later
michael@0 706 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
michael@0 707 nsCOMPtr<nsIURI> uri;
michael@0 708 int64_t contentLength = -1;
michael@0 709 if (channel) {
michael@0 710 channel->GetURI(getter_AddRefs(uri));
michael@0 711 channel->GetContentLength(&contentLength);
michael@0 712 channel->GetContentDisposition(&contentDisposition);
michael@0 713 channel->GetContentDispositionFilename(fileName);
michael@0 714 }
michael@0 715
michael@0 716 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 717 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext);
michael@0 718 NS_ENSURE_STATE(window);
michael@0 719
michael@0 720 // We need to get a hold of a ContentChild so that we can begin forwarding
michael@0 721 // this data to the parent. In the HTTP case, this is unfortunate, since
michael@0 722 // we're actually passing data from parent->child->parent wastefully, but
michael@0 723 // the Right Fix will eventually be to short-circuit those channels on the
michael@0 724 // parent side based on some sort of subscription concept.
michael@0 725 using mozilla::dom::ContentChild;
michael@0 726 using mozilla::dom::ExternalHelperAppChild;
michael@0 727 ContentChild *child = ContentChild::GetSingleton();
michael@0 728 if (!child)
michael@0 729 return NS_ERROR_FAILURE;
michael@0 730
michael@0 731 nsCString disp;
michael@0 732 if (channel) {
michael@0 733 channel->GetContentDispositionHeader(disp);
michael@0 734 }
michael@0 735
michael@0 736 nsCOMPtr<nsIURI> referrer;
michael@0 737 rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
michael@0 738
michael@0 739 OptionalURIParams uriParams, referrerParams;
michael@0 740 SerializeURI(uri, uriParams);
michael@0 741 SerializeURI(referrer, referrerParams);
michael@0 742
michael@0 743 // Now we build a protocol for forwarding our data to the parent. The
michael@0 744 // protocol will act as a listener on the child-side and create a "real"
michael@0 745 // helperAppService listener on the parent-side, via another call to
michael@0 746 // DoContent.
michael@0 747 mozilla::dom::PExternalHelperAppChild *pc =
michael@0 748 child->SendPExternalHelperAppConstructor(uriParams,
michael@0 749 nsCString(aMimeContentType),
michael@0 750 disp, contentDisposition,
michael@0 751 fileName, aForceSave,
michael@0 752 contentLength, referrerParams,
michael@0 753 mozilla::dom::TabChild::GetFrom(window));
michael@0 754 ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
michael@0 755
michael@0 756 NS_ADDREF(*aStreamListener = childListener);
michael@0 757
michael@0 758 nsRefPtr<nsExternalAppHandler> handler =
michael@0 759 new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this,
michael@0 760 fileName,
michael@0 761 reason, aForceSave);
michael@0 762 if (!handler)
michael@0 763 return NS_ERROR_OUT_OF_MEMORY;
michael@0 764
michael@0 765 childListener->SetHandler(handler);
michael@0 766 return NS_OK;
michael@0 767 }
michael@0 768
michael@0 769 if (channel) {
michael@0 770 // Check if we have a POST request, in which case we don't want to use
michael@0 771 // the url's extension
michael@0 772 bool allowURLExt = true;
michael@0 773 nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
michael@0 774 if (httpChan) {
michael@0 775 nsAutoCString requestMethod;
michael@0 776 httpChan->GetRequestMethod(requestMethod);
michael@0 777 allowURLExt = !requestMethod.Equals("POST");
michael@0 778 }
michael@0 779
michael@0 780 // Check if we had a query string - we don't want to check the URL
michael@0 781 // extension if a query is present in the URI
michael@0 782 // If we already know we don't want to check the URL extension, don't
michael@0 783 // bother checking the query
michael@0 784 if (uri && allowURLExt) {
michael@0 785 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
michael@0 786
michael@0 787 if (url) {
michael@0 788 nsAutoCString query;
michael@0 789
michael@0 790 // We only care about the query for HTTP and HTTPS URLs
michael@0 791 bool isHTTP, isHTTPS;
michael@0 792 rv = uri->SchemeIs("http", &isHTTP);
michael@0 793 if (NS_FAILED(rv))
michael@0 794 isHTTP = false;
michael@0 795 rv = uri->SchemeIs("https", &isHTTPS);
michael@0 796 if (NS_FAILED(rv))
michael@0 797 isHTTPS = false;
michael@0 798
michael@0 799 if (isHTTP || isHTTPS)
michael@0 800 url->GetQuery(query);
michael@0 801
michael@0 802 // Only get the extension if the query is empty; if it isn't, then the
michael@0 803 // extension likely belongs to a cgi script and isn't helpful
michael@0 804 allowURLExt = query.IsEmpty();
michael@0 805 }
michael@0 806 }
michael@0 807 // Extract name & extension
michael@0 808 bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
michael@0 809 fileExtension,
michael@0 810 allowURLExt);
michael@0 811 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
michael@0 812 fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
michael@0 813 isAttachment));
michael@0 814 if (isAttachment)
michael@0 815 reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
michael@0 816 }
michael@0 817
michael@0 818 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
michael@0 819 PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
michael@0 820
michael@0 821 // we get the mime service here even though we're the default implementation of it,
michael@0 822 // so it's possible to override only the mime service and not need to reimplement the
michael@0 823 // whole external helper app service itself
michael@0 824 nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
michael@0 825 NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
michael@0 826
michael@0 827 // Try to find a mime object by looking at the mime type/extension
michael@0 828 nsCOMPtr<nsIMIMEInfo> mimeInfo;
michael@0 829 if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
michael@0 830 nsAutoCString mimeType;
michael@0 831 if (!fileExtension.IsEmpty()) {
michael@0 832 mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
michael@0 833 if (mimeInfo) {
michael@0 834 mimeInfo->GetMIMEType(mimeType);
michael@0 835
michael@0 836 LOG(("OS-Provided mime type '%s' for extension '%s'\n",
michael@0 837 mimeType.get(), fileExtension.get()));
michael@0 838 }
michael@0 839 }
michael@0 840
michael@0 841 if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
michael@0 842 // Extension lookup gave us no useful match
michael@0 843 mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
michael@0 844 getter_AddRefs(mimeInfo));
michael@0 845 mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
michael@0 846 }
michael@0 847 if (channel)
michael@0 848 channel->SetContentType(mimeType);
michael@0 849 // Don't overwrite SERVERREQUEST
michael@0 850 if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
michael@0 851 reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
michael@0 852 }
michael@0 853 else {
michael@0 854 mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
michael@0 855 getter_AddRefs(mimeInfo));
michael@0 856 }
michael@0 857 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
michael@0 858
michael@0 859 // No mimeinfo -> we can't continue. probably OOM.
michael@0 860 if (!mimeInfo)
michael@0 861 return NS_ERROR_OUT_OF_MEMORY;
michael@0 862
michael@0 863 *aStreamListener = nullptr;
michael@0 864 // We want the mimeInfo's primary extension to pass it to
michael@0 865 // nsExternalAppHandler
michael@0 866 nsAutoCString buf;
michael@0 867 mimeInfo->GetPrimaryExtension(buf);
michael@0 868
michael@0 869 nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
michael@0 870 buf,
michael@0 871 aWindowContext,
michael@0 872 this,
michael@0 873 fileName,
michael@0 874 reason,
michael@0 875 aForceSave);
michael@0 876 if (!handler)
michael@0 877 return NS_ERROR_OUT_OF_MEMORY;
michael@0 878 NS_ADDREF(*aStreamListener = handler);
michael@0 879
michael@0 880 return NS_OK;
michael@0 881 }
michael@0 882
michael@0 883 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
michael@0 884 const nsACString& aEncodingType,
michael@0 885 bool *aApplyDecoding)
michael@0 886 {
michael@0 887 *aApplyDecoding = true;
michael@0 888 uint32_t i;
michael@0 889 for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
michael@0 890 if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
michael@0 891 aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
michael@0 892 *aApplyDecoding = false;
michael@0 893 break;
michael@0 894 }
michael@0 895 }
michael@0 896 return NS_OK;
michael@0 897 }
michael@0 898
michael@0 899 nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
michael@0 900 nsIFile ** aFile)
michael@0 901 {
michael@0 902 nsDependentString platformAppPath(aPlatformAppPath);
michael@0 903 // First, check if we have an absolute path
michael@0 904 nsIFile* localFile = nullptr;
michael@0 905 nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
michael@0 906 if (NS_SUCCEEDED(rv)) {
michael@0 907 *aFile = localFile;
michael@0 908 bool exists;
michael@0 909 if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
michael@0 910 NS_RELEASE(*aFile);
michael@0 911 return NS_ERROR_FILE_NOT_FOUND;
michael@0 912 }
michael@0 913 return NS_OK;
michael@0 914 }
michael@0 915
michael@0 916
michael@0 917 // Second, check if file exists in mozilla program directory
michael@0 918 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
michael@0 919 if (NS_SUCCEEDED(rv)) {
michael@0 920 rv = (*aFile)->Append(platformAppPath);
michael@0 921 if (NS_SUCCEEDED(rv)) {
michael@0 922 bool exists = false;
michael@0 923 rv = (*aFile)->Exists(&exists);
michael@0 924 if (NS_SUCCEEDED(rv) && exists)
michael@0 925 return NS_OK;
michael@0 926 }
michael@0 927 NS_RELEASE(*aFile);
michael@0 928 }
michael@0 929
michael@0 930
michael@0 931 return NS_ERROR_NOT_AVAILABLE;
michael@0 932 }
michael@0 933
michael@0 934 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 935 // begin external protocol service default implementation...
michael@0 936 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 937 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
michael@0 938 bool * aHandlerExists)
michael@0 939 {
michael@0 940 nsCOMPtr<nsIHandlerInfo> handlerInfo;
michael@0 941 nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
michael@0 942 getter_AddRefs(handlerInfo));
michael@0 943 NS_ENSURE_SUCCESS(rv, rv);
michael@0 944
michael@0 945 // See if we have any known possible handler apps for this
michael@0 946 nsCOMPtr<nsIMutableArray> possibleHandlers;
michael@0 947 handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
michael@0 948
michael@0 949 uint32_t length;
michael@0 950 possibleHandlers->GetLength(&length);
michael@0 951 if (length) {
michael@0 952 *aHandlerExists = true;
michael@0 953 return NS_OK;
michael@0 954 }
michael@0 955
michael@0 956 // if not, fall back on an os-based handler
michael@0 957 return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
michael@0 958 }
michael@0 959
michael@0 960 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
michael@0 961 {
michael@0 962 // check the per protocol setting first. it always takes precedence.
michael@0 963 // if not set, then use the global setting.
michael@0 964
michael@0 965 nsAutoCString prefName("network.protocol-handler.expose.");
michael@0 966 prefName += aProtocolScheme;
michael@0 967 bool val;
michael@0 968 if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
michael@0 969 *aResult = val;
michael@0 970 return NS_OK;
michael@0 971 }
michael@0 972
michael@0 973 // by default, no protocol is exposed. i.e., by default all link clicks must
michael@0 974 // go through the external protocol service. most applications override this
michael@0 975 // default behavior.
michael@0 976 *aResult =
michael@0 977 Preferences::GetBool("network.protocol-handler.expose-all", false);
michael@0 978
michael@0 979 return NS_OK;
michael@0 980 }
michael@0 981
michael@0 982 NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
michael@0 983 {
michael@0 984 return LoadURI(aURL, nullptr);
michael@0 985 }
michael@0 986
michael@0 987 static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external.";
michael@0 988 static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
michael@0 989
michael@0 990 NS_IMETHODIMP
michael@0 991 nsExternalHelperAppService::LoadURI(nsIURI *aURI,
michael@0 992 nsIInterfaceRequestor *aWindowContext)
michael@0 993 {
michael@0 994 NS_ENSURE_ARG_POINTER(aURI);
michael@0 995
michael@0 996 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 997 URIParams uri;
michael@0 998 SerializeURI(aURI, uri);
michael@0 999
michael@0 1000 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri);
michael@0 1001 return NS_OK;
michael@0 1002 }
michael@0 1003
michael@0 1004 nsAutoCString spec;
michael@0 1005 aURI->GetSpec(spec);
michael@0 1006
michael@0 1007 if (spec.Find("%00") != -1)
michael@0 1008 return NS_ERROR_MALFORMED_URI;
michael@0 1009
michael@0 1010 spec.ReplaceSubstring("\"", "%22");
michael@0 1011 spec.ReplaceSubstring("`", "%60");
michael@0 1012
michael@0 1013 nsCOMPtr<nsIIOService> ios(do_GetIOService());
michael@0 1014 nsCOMPtr<nsIURI> uri;
michael@0 1015 nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
michael@0 1016 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1017
michael@0 1018 nsAutoCString scheme;
michael@0 1019 uri->GetScheme(scheme);
michael@0 1020 if (scheme.IsEmpty())
michael@0 1021 return NS_OK; // must have a scheme
michael@0 1022
michael@0 1023 // Deny load if the prefs say to do so
michael@0 1024 nsAutoCString externalPref(kExternalProtocolPrefPrefix);
michael@0 1025 externalPref += scheme;
michael@0 1026 bool allowLoad = false;
michael@0 1027 if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
michael@0 1028 // no scheme-specific value, check the default
michael@0 1029 if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
michael@0 1030 &allowLoad))) {
michael@0 1031 return NS_OK; // missing default pref
michael@0 1032 }
michael@0 1033 }
michael@0 1034
michael@0 1035 if (!allowLoad) {
michael@0 1036 return NS_OK; // explicitly denied
michael@0 1037 }
michael@0 1038
michael@0 1039 #ifdef NECKO_PROTOCOL_rtsp
michael@0 1040 // Handle rtsp protocol.
michael@0 1041 {
michael@0 1042 bool isRTSP = false;
michael@0 1043 rv = aURI->SchemeIs("rtsp", &isRTSP);
michael@0 1044 if (NS_SUCCEEDED(rv) && isRTSP) {
michael@0 1045 LaunchVideoAppForRtsp(aURI);
michael@0 1046 return NS_OK;
michael@0 1047 }
michael@0 1048 }
michael@0 1049 #endif
michael@0 1050
michael@0 1051 nsCOMPtr<nsIHandlerInfo> handler;
michael@0 1052 rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
michael@0 1053 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1054
michael@0 1055 nsHandlerInfoAction preferredAction;
michael@0 1056 handler->GetPreferredAction(&preferredAction);
michael@0 1057 bool alwaysAsk = true;
michael@0 1058 handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
michael@0 1059
michael@0 1060 // if we are not supposed to ask, and the preferred action is to use
michael@0 1061 // a helper app or the system default, we just launch the URI.
michael@0 1062 if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
michael@0 1063 preferredAction == nsIHandlerInfo::useSystemDefault))
michael@0 1064 return handler->LaunchWithURI(uri, aWindowContext);
michael@0 1065
michael@0 1066 nsCOMPtr<nsIContentDispatchChooser> chooser =
michael@0 1067 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
michael@0 1068 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1069
michael@0 1070 return chooser->Ask(handler, aWindowContext, uri,
michael@0 1071 nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
michael@0 1072 }
michael@0 1073
michael@0 1074 NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
michael@0 1075 {
michael@0 1076 // this method should only be implemented by each OS specific implementation of this service.
michael@0 1077 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1078 }
michael@0 1079
michael@0 1080
michael@0 1081 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 1082 // Methods related to deleting temporary files on exit
michael@0 1083 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 1084
michael@0 1085 /* static */
michael@0 1086 nsresult
michael@0 1087 nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
michael@0 1088 nsCOMArray<nsIFile> &aFileList)
michael@0 1089 {
michael@0 1090 bool isFile = false;
michael@0 1091
michael@0 1092 // as a safety measure, make sure the nsIFile is really a file and not a directory object.
michael@0 1093 aTemporaryFile->IsFile(&isFile);
michael@0 1094 if (!isFile) return NS_OK;
michael@0 1095
michael@0 1096 aFileList.AppendObject(aTemporaryFile);
michael@0 1097
michael@0 1098 return NS_OK;
michael@0 1099 }
michael@0 1100
michael@0 1101 NS_IMETHODIMP
michael@0 1102 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
michael@0 1103 {
michael@0 1104 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
michael@0 1105 }
michael@0 1106
michael@0 1107 NS_IMETHODIMP
michael@0 1108 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
michael@0 1109 {
michael@0 1110 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
michael@0 1111 }
michael@0 1112
michael@0 1113 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
michael@0 1114 {
michael@0 1115 int32_t numEntries = fileList.Count();
michael@0 1116 nsIFile* localFile;
michael@0 1117 for (int32_t index = 0; index < numEntries; index++)
michael@0 1118 {
michael@0 1119 localFile = fileList[index];
michael@0 1120 if (localFile) {
michael@0 1121 // First make the file writable, since the temp file is probably readonly.
michael@0 1122 localFile->SetPermissions(0600);
michael@0 1123 localFile->Remove(false);
michael@0 1124 }
michael@0 1125 }
michael@0 1126
michael@0 1127 fileList.Clear();
michael@0 1128 }
michael@0 1129
michael@0 1130 void nsExternalHelperAppService::ExpungeTemporaryFiles()
michael@0 1131 {
michael@0 1132 ExpungeTemporaryFilesHelper(mTemporaryFilesList);
michael@0 1133 }
michael@0 1134
michael@0 1135 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
michael@0 1136 {
michael@0 1137 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
michael@0 1138 }
michael@0 1139
michael@0 1140 static const char kExternalWarningPrefPrefix[] =
michael@0 1141 "network.protocol-handler.warn-external.";
michael@0 1142 static const char kExternalWarningDefaultPref[] =
michael@0 1143 "network.protocol-handler.warn-external-default";
michael@0 1144
michael@0 1145 NS_IMETHODIMP
michael@0 1146 nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
michael@0 1147 nsIHandlerInfo **aHandlerInfo)
michael@0 1148 {
michael@0 1149 // XXX enterprise customers should be able to turn this support off with a
michael@0 1150 // single master pref (maybe use one of the "exposed" prefs here?)
michael@0 1151
michael@0 1152 bool exists;
michael@0 1153 nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
michael@0 1154 if (NS_FAILED(rv)) {
michael@0 1155 // Either it knows nothing, or we ran out of memory
michael@0 1156 return NS_ERROR_FAILURE;
michael@0 1157 }
michael@0 1158
michael@0 1159 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
michael@0 1160 if (handlerSvc) {
michael@0 1161 bool hasHandler = false;
michael@0 1162 (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
michael@0 1163 if (hasHandler) {
michael@0 1164 rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
michael@0 1165 if (NS_SUCCEEDED(rv))
michael@0 1166 return NS_OK;
michael@0 1167 }
michael@0 1168 }
michael@0 1169
michael@0 1170 return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
michael@0 1171 }
michael@0 1172
michael@0 1173 NS_IMETHODIMP
michael@0 1174 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
michael@0 1175 bool *found,
michael@0 1176 nsIHandlerInfo **aHandlerInfo)
michael@0 1177 {
michael@0 1178 // intended to be implemented by the subclass
michael@0 1179 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1180 }
michael@0 1181
michael@0 1182 NS_IMETHODIMP
michael@0 1183 nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
michael@0 1184 bool aOSHandlerExists)
michael@0 1185 {
michael@0 1186 // this type isn't in our database, so we've only got an OS default handler,
michael@0 1187 // if one exists
michael@0 1188
michael@0 1189 if (aOSHandlerExists) {
michael@0 1190 // we've got a default, so use it
michael@0 1191 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
michael@0 1192
michael@0 1193 // whether or not to ask the user depends on the warning preference
michael@0 1194 nsAutoCString scheme;
michael@0 1195 aHandlerInfo->GetType(scheme);
michael@0 1196
michael@0 1197 nsAutoCString warningPref(kExternalWarningPrefPrefix);
michael@0 1198 warningPref += scheme;
michael@0 1199 bool warn;
michael@0 1200 if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
michael@0 1201 // no scheme-specific value, check the default
michael@0 1202 warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
michael@0 1203 }
michael@0 1204 aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
michael@0 1205 } else {
michael@0 1206 // If no OS default existed, we set the preferred action to alwaysAsk.
michael@0 1207 // This really means not initialized (i.e. there's no available handler)
michael@0 1208 // to all the code...
michael@0 1209 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
michael@0 1210 }
michael@0 1211
michael@0 1212 return NS_OK;
michael@0 1213 }
michael@0 1214
michael@0 1215 // XPCOM profile change observer
michael@0 1216 NS_IMETHODIMP
michael@0 1217 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
michael@0 1218 {
michael@0 1219 if (!strcmp(aTopic, "profile-before-change")) {
michael@0 1220 ExpungeTemporaryFiles();
michael@0 1221 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
michael@0 1222 ExpungeTemporaryPrivateFiles();
michael@0 1223 }
michael@0 1224 return NS_OK;
michael@0 1225 }
michael@0 1226
michael@0 1227 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 1228 // begin external app handler implementation
michael@0 1229 //////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 1230
michael@0 1231 NS_IMPL_ADDREF(nsExternalAppHandler)
michael@0 1232 NS_IMPL_RELEASE(nsExternalAppHandler)
michael@0 1233
michael@0 1234 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
michael@0 1235 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
michael@0 1236 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
michael@0 1237 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
michael@0 1238 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
michael@0 1239 NS_INTERFACE_MAP_ENTRY(nsICancelable)
michael@0 1240 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
michael@0 1241 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
michael@0 1242 NS_INTERFACE_MAP_END_THREADSAFE
michael@0 1243
michael@0 1244 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
michael@0 1245 const nsCSubstring& aTempFileExtension,
michael@0 1246 nsIInterfaceRequestor* aWindowContext,
michael@0 1247 nsExternalHelperAppService *aExtProtSvc,
michael@0 1248 const nsAString& aSuggestedFilename,
michael@0 1249 uint32_t aReason, bool aForceSave)
michael@0 1250 : mMimeInfo(aMIMEInfo)
michael@0 1251 , mWindowContext(aWindowContext)
michael@0 1252 , mWindowToClose(nullptr)
michael@0 1253 , mSuggestedFileName(aSuggestedFilename)
michael@0 1254 , mForceSave(aForceSave)
michael@0 1255 , mCanceled(false)
michael@0 1256 , mShouldCloseWindow(false)
michael@0 1257 , mStopRequestIssued(false)
michael@0 1258 , mReason(aReason)
michael@0 1259 , mContentLength(-1)
michael@0 1260 , mProgress(0)
michael@0 1261 , mSaver(nullptr)
michael@0 1262 , mDialogProgressListener(nullptr)
michael@0 1263 , mTransfer(nullptr)
michael@0 1264 , mRequest(nullptr)
michael@0 1265 , mExtProtSvc(aExtProtSvc)
michael@0 1266 {
michael@0 1267
michael@0 1268 // make sure the extention includes the '.'
michael@0 1269 if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
michael@0 1270 mTempFileExtension = char16_t('.');
michael@0 1271 AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
michael@0 1272
michael@0 1273 // replace platform specific path separator and illegal characters to avoid any confusion
michael@0 1274 mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
michael@0 1275 mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
michael@0 1276
michael@0 1277 // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
michael@0 1278 const char16_t unsafeBidiCharacters[] = {
michael@0 1279 char16_t(0x061c), // Arabic Letter Mark
michael@0 1280 char16_t(0x200e), // Left-to-Right Mark
michael@0 1281 char16_t(0x200f), // Right-to-Left Mark
michael@0 1282 char16_t(0x202a), // Left-to-Right Embedding
michael@0 1283 char16_t(0x202b), // Right-to-Left Embedding
michael@0 1284 char16_t(0x202c), // Pop Directional Formatting
michael@0 1285 char16_t(0x202d), // Left-to-Right Override
michael@0 1286 char16_t(0x202e), // Right-to-Left Override
michael@0 1287 char16_t(0x2066), // Left-to-Right Isolate
michael@0 1288 char16_t(0x2067), // Right-to-Left Isolate
michael@0 1289 char16_t(0x2068), // First Strong Isolate
michael@0 1290 char16_t(0x2069), // Pop Directional Isolate
michael@0 1291 char16_t(0)
michael@0 1292 };
michael@0 1293 mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
michael@0 1294 mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
michael@0 1295
michael@0 1296 // Make sure extension is correct.
michael@0 1297 EnsureSuggestedFileName();
michael@0 1298
michael@0 1299 mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
michael@0 1300 }
michael@0 1301
michael@0 1302 nsExternalAppHandler::~nsExternalAppHandler()
michael@0 1303 {
michael@0 1304 MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
michael@0 1305 }
michael@0 1306
michael@0 1307 void
michael@0 1308 nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
michael@0 1309 {
michael@0 1310 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process");
michael@0 1311 // Remove our request from the child loadGroup
michael@0 1312 RetargetLoadNotifications(request);
michael@0 1313 MaybeCloseWindow();
michael@0 1314 }
michael@0 1315
michael@0 1316 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
michael@0 1317 {
michael@0 1318 // This is always called by nsHelperDlg.js. Go ahead and register the
michael@0 1319 // progress listener. At this point, we don't have mTransfer.
michael@0 1320 mDialogProgressListener = aWebProgressListener;
michael@0 1321 return NS_OK;
michael@0 1322 }
michael@0 1323
michael@0 1324 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
michael@0 1325 {
michael@0 1326 if (mFinalFileDestination)
michael@0 1327 *aTarget = mFinalFileDestination;
michael@0 1328 else
michael@0 1329 *aTarget = mTempFile;
michael@0 1330
michael@0 1331 NS_IF_ADDREF(*aTarget);
michael@0 1332 return NS_OK;
michael@0 1333 }
michael@0 1334
michael@0 1335 NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
michael@0 1336 {
michael@0 1337 // Use the real target if it's been set
michael@0 1338 if (mFinalFileDestination)
michael@0 1339 return mFinalFileDestination->IsExecutable(aExec);
michael@0 1340
michael@0 1341 // Otherwise, use the stored executable-ness of the temporary
michael@0 1342 *aExec = mTempFileIsExecutable;
michael@0 1343 return NS_OK;
michael@0 1344 }
michael@0 1345
michael@0 1346 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
michael@0 1347 {
michael@0 1348 *aTime = mTimeDownloadStarted;
michael@0 1349 return NS_OK;
michael@0 1350 }
michael@0 1351
michael@0 1352 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
michael@0 1353 {
michael@0 1354 *aContentLength = mContentLength;
michael@0 1355 return NS_OK;
michael@0 1356 }
michael@0 1357
michael@0 1358 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
michael@0 1359 {
michael@0 1360 // we are going to run the downloading of the helper app in our own little docloader / load group context.
michael@0 1361 // so go ahead and force the creation of a load group and doc loader for us to use...
michael@0 1362 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
michael@0 1363 if (!aChannel)
michael@0 1364 return;
michael@0 1365
michael@0 1366 // we need to store off the original (pre redirect!) channel that initiated the load. We do
michael@0 1367 // this so later on, we can pass any refresh urls associated with the original channel back to the
michael@0 1368 // window context which started the whole process. More comments about that are listed below....
michael@0 1369 // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
michael@0 1370 // ideally we should be able to just use mChannel (the channel we are extracting content from) or
michael@0 1371 // the default load channel associated with the original load group. Unfortunately because
michael@0 1372 // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
michael@0 1373 // which is what we really want....
michael@0 1374
michael@0 1375 // Note that we need to do this before removing aChannel from the loadgroup,
michael@0 1376 // since that would mess with the original channel on the loader.
michael@0 1377 nsCOMPtr<nsIDocumentLoader> origContextLoader =
michael@0 1378 do_GetInterface(mWindowContext);
michael@0 1379 if (origContextLoader)
michael@0 1380 origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
michael@0 1381
michael@0 1382 bool isPrivate = NS_UsePrivateBrowsing(aChannel);
michael@0 1383
michael@0 1384 nsCOMPtr<nsILoadGroup> oldLoadGroup;
michael@0 1385 aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
michael@0 1386
michael@0 1387 if(oldLoadGroup)
michael@0 1388 oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
michael@0 1389
michael@0 1390 aChannel->SetLoadGroup(nullptr);
michael@0 1391 aChannel->SetNotificationCallbacks(nullptr);
michael@0 1392
michael@0 1393 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
michael@0 1394 if (pbChannel) {
michael@0 1395 pbChannel->SetPrivate(isPrivate);
michael@0 1396 }
michael@0 1397 }
michael@0 1398
michael@0 1399 /**
michael@0 1400 * Make mTempFileExtension contain an extension exactly when its previous value
michael@0 1401 * is different from mSuggestedFileName's extension, so that it can be appended
michael@0 1402 * to mSuggestedFileName and form a valid, useful leaf name.
michael@0 1403 * This is required so that the (renamed) temporary file has the correct extension
michael@0 1404 * after downloading to make sure the OS will launch the application corresponding
michael@0 1405 * to the MIME type (which was used to calculate mTempFileExtension). This prevents
michael@0 1406 * a cgi-script named foobar.exe that returns application/zip from being named
michael@0 1407 * foobar.exe and executed as an executable file. It also blocks content that
michael@0 1408 * a web site might provide with a content-disposition header indicating
michael@0 1409 * filename="foobar.exe" from being downloaded to a file with extension .exe
michael@0 1410 * and executed.
michael@0 1411 */
michael@0 1412 void nsExternalAppHandler::EnsureSuggestedFileName()
michael@0 1413 {
michael@0 1414 // Make sure there is a mTempFileExtension (not "" or ".").
michael@0 1415 // Remember that mTempFileExtension will always have the leading "."
michael@0 1416 // (the check for empty is just to be safe).
michael@0 1417 if (mTempFileExtension.Length() > 1)
michael@0 1418 {
michael@0 1419 // Get mSuggestedFileName's current extension.
michael@0 1420 nsAutoString fileExt;
michael@0 1421 int32_t pos = mSuggestedFileName.RFindChar('.');
michael@0 1422 if (pos != kNotFound)
michael@0 1423 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
michael@0 1424
michael@0 1425 // Now, compare fileExt to mTempFileExtension.
michael@0 1426 if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
michael@0 1427 {
michael@0 1428 // Matches -> mTempFileExtension can be empty
michael@0 1429 mTempFileExtension.Truncate();
michael@0 1430 }
michael@0 1431 }
michael@0 1432 }
michael@0 1433
michael@0 1434 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
michael@0 1435 {
michael@0 1436 // First we need to try to get the destination directory for the temporary
michael@0 1437 // file.
michael@0 1438 nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
michael@0 1439 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1440
michael@0 1441 // At this point, we do not have a filename for the temp file. For security
michael@0 1442 // purposes, this cannot be predictable, so we must use a cryptographic
michael@0 1443 // quality PRNG to generate one.
michael@0 1444 // We will request raw random bytes, and transform that to a base64 string,
michael@0 1445 // as all characters from the base64 set are acceptable for filenames. For
michael@0 1446 // each three bytes of random data, we will get four bytes of ASCII. Request
michael@0 1447 // a bit more, to be safe, and truncate to the length we want in the end.
michael@0 1448
michael@0 1449 const uint32_t wantedFileNameLength = 8;
michael@0 1450 const uint32_t requiredBytesLength =
michael@0 1451 static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
michael@0 1452
michael@0 1453 nsCOMPtr<nsIRandomGenerator> rg =
michael@0 1454 do_GetService("@mozilla.org/security/random-generator;1", &rv);
michael@0 1455 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1456
michael@0 1457 uint8_t *buffer;
michael@0 1458 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
michael@0 1459 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1460
michael@0 1461 nsAutoCString tempLeafName;
michael@0 1462 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
michael@0 1463 rv = Base64Encode(randomData, tempLeafName);
michael@0 1464 NS_Free(buffer);
michael@0 1465 buffer = nullptr;
michael@0 1466 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1467
michael@0 1468 tempLeafName.Truncate(wantedFileNameLength);
michael@0 1469
michael@0 1470 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
michael@0 1471 // to replace illegal characters -- notably '/'
michael@0 1472 tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
michael@0 1473
michael@0 1474 // now append our extension.
michael@0 1475 nsAutoCString ext;
michael@0 1476 mMimeInfo->GetPrimaryExtension(ext);
michael@0 1477 if (!ext.IsEmpty()) {
michael@0 1478 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
michael@0 1479 if (ext.First() != '.')
michael@0 1480 tempLeafName.Append('.');
michael@0 1481 tempLeafName.Append(ext);
michael@0 1482 }
michael@0 1483
michael@0 1484 // We need to temporarily create a dummy file with the correct
michael@0 1485 // file extension to determine the executable-ness, so do this before adding
michael@0 1486 // the extra .part extension.
michael@0 1487 nsCOMPtr<nsIFile> dummyFile;
michael@0 1488 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
michael@0 1489 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1490
michael@0 1491 // Set the file name without .part
michael@0 1492 rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
michael@0 1493 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1494 rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
michael@0 1495 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1496
michael@0 1497 // Store executable-ness then delete
michael@0 1498 dummyFile->IsExecutable(&mTempFileIsExecutable);
michael@0 1499 dummyFile->Remove(false);
michael@0 1500
michael@0 1501 // Add an additional .part to prevent the OS from running this file in the
michael@0 1502 // default application.
michael@0 1503 tempLeafName.Append(NS_LITERAL_CSTRING(".part"));
michael@0 1504
michael@0 1505 rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
michael@0 1506 // make this file unique!!!
michael@0 1507 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1508 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
michael@0 1509 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1510
michael@0 1511 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
michael@0 1512 // This is a bit broken in the case when createUnique actually had to append
michael@0 1513 // some numbers, because then we now have a filename like foo.bar-1.part and
michael@0 1514 // we'll end up with foo.bar-1.bar as our final filename if we end up using
michael@0 1515 // this. But the other options are all bad too.... Ideally we'd have a way
michael@0 1516 // to tell createUnique to put its unique marker before the extension that
michael@0 1517 // comes before ".part" or something.
michael@0 1518 rv = mTempFile->GetLeafName(mTempLeafName);
michael@0 1519 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1520
michael@0 1521 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
michael@0 1522 NS_ERROR_UNEXPECTED);
michael@0 1523
michael@0 1524 // Strip off the ".part" from mTempLeafName
michael@0 1525 mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
michael@0 1526
michael@0 1527 MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
michael@0 1528 mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
michael@0 1529 &rv);
michael@0 1530 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1531
michael@0 1532 rv = mSaver->SetObserver(this);
michael@0 1533 if (NS_FAILED(rv)) {
michael@0 1534 mSaver = nullptr;
michael@0 1535 return rv;
michael@0 1536 }
michael@0 1537
michael@0 1538 rv = mSaver->EnableSha256();
michael@0 1539 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1540
michael@0 1541 rv = mSaver->EnableSignatureInfo();
michael@0 1542 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1543 LOG(("Enabled hashing and signature verification"));
michael@0 1544
michael@0 1545 rv = mSaver->SetTarget(mTempFile, false);
michael@0 1546 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1547
michael@0 1548 return rv;
michael@0 1549 }
michael@0 1550
michael@0 1551 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
michael@0 1552 {
michael@0 1553 NS_PRECONDITION(request, "OnStartRequest without request?");
michael@0 1554
michael@0 1555 // Set mTimeDownloadStarted here as the download has already started and
michael@0 1556 // we want to record the start time before showing the filepicker.
michael@0 1557 mTimeDownloadStarted = PR_Now();
michael@0 1558
michael@0 1559 mRequest = request;
michael@0 1560
michael@0 1561 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
michael@0 1562
michael@0 1563 nsresult rv;
michael@0 1564
michael@0 1565 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
michael@0 1566 mIsFileChannel = fileChan != nullptr;
michael@0 1567
michael@0 1568 // Get content length
michael@0 1569 if (aChannel) {
michael@0 1570 aChannel->GetContentLength(&mContentLength);
michael@0 1571 }
michael@0 1572
michael@0 1573 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
michael@0 1574 // Determine whether a new window was opened specifically for this request
michael@0 1575 if (props) {
michael@0 1576 bool tmp = false;
michael@0 1577 props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
michael@0 1578 &tmp);
michael@0 1579 mShouldCloseWindow = tmp;
michael@0 1580 }
michael@0 1581
michael@0 1582 // Now get the URI
michael@0 1583 if (aChannel)
michael@0 1584 {
michael@0 1585 aChannel->GetURI(getter_AddRefs(mSourceUrl));
michael@0 1586 }
michael@0 1587
michael@0 1588 // retarget all load notifications to our docloader instead of the original window's docloader...
michael@0 1589 RetargetLoadNotifications(request);
michael@0 1590
michael@0 1591 // Check to see if there is a refresh header on the original channel.
michael@0 1592 if (mOriginalChannel) {
michael@0 1593 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
michael@0 1594 if (httpChannel) {
michael@0 1595 nsAutoCString refreshHeader;
michael@0 1596 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
michael@0 1597 refreshHeader);
michael@0 1598 if (!refreshHeader.IsEmpty()) {
michael@0 1599 mShouldCloseWindow = false;
michael@0 1600 }
michael@0 1601 }
michael@0 1602 }
michael@0 1603
michael@0 1604 // Close the underlying DOMWindow if there is no refresh header
michael@0 1605 // and it was opened specifically for the download
michael@0 1606 MaybeCloseWindow();
michael@0 1607
michael@0 1608 // In an IPC setting, we're allowing the child process, here, to make
michael@0 1609 // decisions about decoding the channel (e.g. decompression). It will
michael@0 1610 // still forward the decoded (uncompressed) data back to the parent.
michael@0 1611 // Con: Uncompressed data means more IPC overhead.
michael@0 1612 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
michael@0 1613 // Parent process doesn't need to expect CPU time on decompression.
michael@0 1614 nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
michael@0 1615 if (encChannel)
michael@0 1616 {
michael@0 1617 // Turn off content encoding conversions if needed
michael@0 1618 bool applyConversion = true;
michael@0 1619
michael@0 1620 nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
michael@0 1621 if (sourceURL)
michael@0 1622 {
michael@0 1623 nsAutoCString extension;
michael@0 1624 sourceURL->GetFileExtension(extension);
michael@0 1625 if (!extension.IsEmpty())
michael@0 1626 {
michael@0 1627 nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
michael@0 1628 encChannel->GetContentEncodings(getter_AddRefs(encEnum));
michael@0 1629 if (encEnum)
michael@0 1630 {
michael@0 1631 bool hasMore;
michael@0 1632 rv = encEnum->HasMore(&hasMore);
michael@0 1633 if (NS_SUCCEEDED(rv) && hasMore)
michael@0 1634 {
michael@0 1635 nsAutoCString encType;
michael@0 1636 rv = encEnum->GetNext(encType);
michael@0 1637 if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
michael@0 1638 {
michael@0 1639 mExtProtSvc->ApplyDecodingForExtension(extension, encType,
michael@0 1640 &applyConversion);
michael@0 1641 }
michael@0 1642 }
michael@0 1643 }
michael@0 1644 }
michael@0 1645 }
michael@0 1646
michael@0 1647 encChannel->SetApplyConversion( applyConversion );
michael@0 1648 }
michael@0 1649
michael@0 1650 // At this point, the child process has done everything it can usefully do
michael@0 1651 // for OnStartRequest.
michael@0 1652 if (XRE_GetProcessType() == GeckoProcessType_Content)
michael@0 1653 return NS_OK;
michael@0 1654
michael@0 1655 rv = SetUpTempFile(aChannel);
michael@0 1656 if (NS_FAILED(rv)) {
michael@0 1657 nsresult transferError = rv;
michael@0 1658
michael@0 1659 rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
michael@0 1660 #ifdef PR_LOGGING
michael@0 1661 if (NS_FAILED(rv)) {
michael@0 1662 LOG(("Failed to create transfer to report failure."
michael@0 1663 "Will fallback to prompter!"));
michael@0 1664 }
michael@0 1665 #endif
michael@0 1666
michael@0 1667 mCanceled = true;
michael@0 1668 request->Cancel(transferError);
michael@0 1669
michael@0 1670 nsAutoString path;
michael@0 1671 if (mTempFile)
michael@0 1672 mTempFile->GetPath(path);
michael@0 1673
michael@0 1674 SendStatusChange(kWriteError, transferError, request, path);
michael@0 1675
michael@0 1676 return NS_OK;
michael@0 1677 }
michael@0 1678
michael@0 1679 // Inform channel it is open on behalf of a download to prevent caching.
michael@0 1680 nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
michael@0 1681 if (httpInternal) {
michael@0 1682 httpInternal->SetChannelIsForDownload(true);
michael@0 1683 }
michael@0 1684
michael@0 1685 // now that the temp file is set up, find out if we need to invoke a dialog
michael@0 1686 // asking the user what they want us to do with this content...
michael@0 1687
michael@0 1688 // We can get here for three reasons: "can't handle", "sniffed type", or
michael@0 1689 // "server sent content-disposition:attachment". In the first case we want
michael@0 1690 // to honor the user's "always ask" pref; in the other two cases we want to
michael@0 1691 // honor it only if the default action is "save". Opening attachments in
michael@0 1692 // helper apps by default breaks some websites (especially if the attachment
michael@0 1693 // is one part of a multipart document). Opening sniffed content in helper
michael@0 1694 // apps by default introduces security holes that we'd rather not have.
michael@0 1695
michael@0 1696 // So let's find out whether the user wants to be prompted. If he does not,
michael@0 1697 // check mReason and the preferred action to see what we should do.
michael@0 1698
michael@0 1699 bool alwaysAsk = true;
michael@0 1700 mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
michael@0 1701 if (alwaysAsk)
michael@0 1702 {
michael@0 1703 // But we *don't* ask if this mimeInfo didn't come from
michael@0 1704 // our user configuration datastore and the user has said
michael@0 1705 // at some point in the distant past that they don't
michael@0 1706 // want to be asked. The latter fact would have been
michael@0 1707 // stored in pref strings back in the old days.
michael@0 1708
michael@0 1709 bool mimeTypeIsInDatastore = false;
michael@0 1710 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
michael@0 1711 if (handlerSvc)
michael@0 1712 handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
michael@0 1713 if (!handlerSvc || !mimeTypeIsInDatastore)
michael@0 1714 {
michael@0 1715 nsAutoCString MIMEType;
michael@0 1716 mMimeInfo->GetMIMEType(MIMEType);
michael@0 1717
michael@0 1718 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
michael@0 1719 {
michael@0 1720 // Don't need to ask after all.
michael@0 1721 alwaysAsk = false;
michael@0 1722 // Make sure action matches pref (save to disk).
michael@0 1723 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
michael@0 1724 }
michael@0 1725 else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get()))
michael@0 1726 {
michael@0 1727 // Don't need to ask after all.
michael@0 1728 alwaysAsk = false;
michael@0 1729 }
michael@0 1730 }
michael@0 1731 }
michael@0 1732
michael@0 1733 int32_t action = nsIMIMEInfo::saveToDisk;
michael@0 1734 mMimeInfo->GetPreferredAction( &action );
michael@0 1735
michael@0 1736 // OK, now check why we're here
michael@0 1737 if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
michael@0 1738 // Force asking if we're not saving. See comment back when we fetched the
michael@0 1739 // alwaysAsk boolean for details.
michael@0 1740 alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
michael@0 1741 }
michael@0 1742
michael@0 1743 // if we were told that we _must_ save to disk without asking, all the stuff
michael@0 1744 // before this is irrelevant; override it
michael@0 1745 if (mForceSave) {
michael@0 1746 alwaysAsk = false;
michael@0 1747 action = nsIMIMEInfo::saveToDisk;
michael@0 1748 }
michael@0 1749
michael@0 1750 if (alwaysAsk)
michael@0 1751 {
michael@0 1752 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
michael@0 1753 mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
michael@0 1754 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1755
michael@0 1756 // this will create a reference cycle (the dialog holds a reference to us as
michael@0 1757 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
michael@0 1758 rv = mDialog->Show( this, mWindowContext, mReason );
michael@0 1759
michael@0 1760 // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
michael@0 1761 }
michael@0 1762 else
michael@0 1763 {
michael@0 1764
michael@0 1765 // We need to do the save/open immediately, then.
michael@0 1766 #ifdef XP_WIN
michael@0 1767 /* We need to see whether the file we've got here could be
michael@0 1768 * executable. If it could, we had better not try to open it!
michael@0 1769 * We can skip this check, though, if we have a setting to open in a
michael@0 1770 * helper app.
michael@0 1771 * This code mirrors the code in
michael@0 1772 * nsExternalAppHandler::LaunchWithApplication so that what we
michael@0 1773 * test here is as close as possible to what will really be
michael@0 1774 * happening if we decide to execute
michael@0 1775 */
michael@0 1776 nsCOMPtr<nsIHandlerApp> prefApp;
michael@0 1777 mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
michael@0 1778 if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
michael@0 1779 nsCOMPtr<nsIFile> fileToTest;
michael@0 1780 GetTargetFile(getter_AddRefs(fileToTest));
michael@0 1781 if (fileToTest) {
michael@0 1782 bool isExecutable;
michael@0 1783 rv = fileToTest->IsExecutable(&isExecutable);
michael@0 1784 if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good
michael@0 1785 action = nsIMIMEInfo::saveToDisk;
michael@0 1786 }
michael@0 1787 } else { // Paranoia is good here too, though this really should not happen
michael@0 1788 NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
michael@0 1789 action = nsIMIMEInfo::saveToDisk;
michael@0 1790 }
michael@0 1791 }
michael@0 1792
michael@0 1793 #endif
michael@0 1794 if (action == nsIMIMEInfo::useHelperApp ||
michael@0 1795 action == nsIMIMEInfo::useSystemDefault)
michael@0 1796 {
michael@0 1797 rv = LaunchWithApplication(nullptr, false);
michael@0 1798 }
michael@0 1799 else // Various unknown actions go here too
michael@0 1800 {
michael@0 1801 rv = SaveToDisk(nullptr, false);
michael@0 1802 }
michael@0 1803 }
michael@0 1804
michael@0 1805 return NS_OK;
michael@0 1806 }
michael@0 1807
michael@0 1808 // Convert error info into proper message text and send OnStatusChange
michael@0 1809 // notification to the dialog progress listener or nsITransfer implementation.
michael@0 1810 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
michael@0 1811 {
michael@0 1812 nsAutoString msgId;
michael@0 1813 switch(rv)
michael@0 1814 {
michael@0 1815 case NS_ERROR_OUT_OF_MEMORY:
michael@0 1816 // No memory
michael@0 1817 msgId.AssignLiteral("noMemory");
michael@0 1818 break;
michael@0 1819
michael@0 1820 case NS_ERROR_FILE_DISK_FULL:
michael@0 1821 case NS_ERROR_FILE_NO_DEVICE_SPACE:
michael@0 1822 // Out of space on target volume.
michael@0 1823 msgId.AssignLiteral("diskFull");
michael@0 1824 break;
michael@0 1825
michael@0 1826 case NS_ERROR_FILE_READ_ONLY:
michael@0 1827 // Attempt to write to read/only file.
michael@0 1828 msgId.AssignLiteral("readOnly");
michael@0 1829 break;
michael@0 1830
michael@0 1831 case NS_ERROR_FILE_ACCESS_DENIED:
michael@0 1832 if (type == kWriteError) {
michael@0 1833 // Attempt to write without sufficient permissions.
michael@0 1834 #if defined(ANDROID)
michael@0 1835 // On Android (and Gonk), this means the SD card is present but
michael@0 1836 // unavailable (read-only).
michael@0 1837 msgId.AssignLiteral("SDAccessErrorCardReadOnly");
michael@0 1838 #else
michael@0 1839 msgId.AssignLiteral("accessError");
michael@0 1840 #endif
michael@0 1841 }
michael@0 1842 else
michael@0 1843 {
michael@0 1844 msgId.AssignLiteral("launchError");
michael@0 1845 }
michael@0 1846 break;
michael@0 1847
michael@0 1848 case NS_ERROR_FILE_NOT_FOUND:
michael@0 1849 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
michael@0 1850 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
michael@0 1851 // Helper app not found, let's verify this happened on launch
michael@0 1852 if (type == kLaunchError) {
michael@0 1853 msgId.AssignLiteral("helperAppNotFound");
michael@0 1854 break;
michael@0 1855 }
michael@0 1856 #if defined(ANDROID)
michael@0 1857 else if (type == kWriteError) {
michael@0 1858 // On Android (and Gonk), this means the SD card is missing (not in
michael@0 1859 // SD slot).
michael@0 1860 msgId.AssignLiteral("SDAccessErrorCardMissing");
michael@0 1861 break;
michael@0 1862 }
michael@0 1863 #endif
michael@0 1864 // fall through
michael@0 1865
michael@0 1866 default:
michael@0 1867 // Generic read/write/launch error message.
michael@0 1868 switch(type)
michael@0 1869 {
michael@0 1870 case kReadError:
michael@0 1871 msgId.AssignLiteral("readError");
michael@0 1872 break;
michael@0 1873 case kWriteError:
michael@0 1874 msgId.AssignLiteral("writeError");
michael@0 1875 break;
michael@0 1876 case kLaunchError:
michael@0 1877 msgId.AssignLiteral("launchError");
michael@0 1878 break;
michael@0 1879 }
michael@0 1880 break;
michael@0 1881 }
michael@0 1882 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
michael@0 1883 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
michael@0 1884 NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
michael@0 1885 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
michael@0 1886 (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
michael@0 1887
michael@0 1888 // Get properties file bundle and extract status string.
michael@0 1889 nsCOMPtr<nsIStringBundleService> stringService =
michael@0 1890 mozilla::services::GetStringBundleService();
michael@0 1891 if (stringService)
michael@0 1892 {
michael@0 1893 nsCOMPtr<nsIStringBundle> bundle;
michael@0 1894 if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
michael@0 1895 {
michael@0 1896 nsXPIDLString msgText;
michael@0 1897 const char16_t *strings[] = { path.get() };
michael@0 1898 if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
michael@0 1899 {
michael@0 1900 if (mDialogProgressListener)
michael@0 1901 {
michael@0 1902 // We have a listener, let it handle the error.
michael@0 1903 mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
michael@0 1904 } else if (mTransfer) {
michael@0 1905 mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
michael@0 1906 }
michael@0 1907 else
michael@0 1908 if (XRE_GetProcessType() == GeckoProcessType_Default) {
michael@0 1909 // We don't have a listener. Simply show the alert ourselves.
michael@0 1910 nsresult qiRv;
michael@0 1911 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext, &qiRv));
michael@0 1912 nsXPIDLString title;
michael@0 1913 bundle->FormatStringFromName(MOZ_UTF16("title"),
michael@0 1914 strings,
michael@0 1915 1,
michael@0 1916 getter_Copies(title));
michael@0 1917
michael@0 1918 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
michael@0 1919 ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
michael@0 1920 mWindowContext.get(),
michael@0 1921 prompter.get(),
michael@0 1922 qiRv,
michael@0 1923 NS_ConvertUTF16toUTF8(title).get(),
michael@0 1924 NS_ConvertUTF16toUTF8(msgText).get()));
michael@0 1925
michael@0 1926 // If we didn't have a prompter we will try and get a window
michael@0 1927 // instead, get it's docshell and use it to alert the user.
michael@0 1928 if (!prompter)
michael@0 1929 {
michael@0 1930 nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(mWindowContext));
michael@0 1931 if (!window || !window->GetDocShell())
michael@0 1932 {
michael@0 1933 return;
michael@0 1934 }
michael@0 1935
michael@0 1936 prompter = do_GetInterface(window->GetDocShell(), &qiRv);
michael@0 1937
michael@0 1938 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
michael@0 1939 ("No prompter from mWindowContext, using DocShell, " \
michael@0 1940 "window=0x%p, docShell=0x%p, " \
michael@0 1941 "prompter=0x%p, qi rv=0x%08X",
michael@0 1942 window.get(),
michael@0 1943 window->GetDocShell(),
michael@0 1944 prompter.get(),
michael@0 1945 qiRv));
michael@0 1946
michael@0 1947 // If we still don't have a prompter, there's nothing else we
michael@0 1948 // can do so just return.
michael@0 1949 if (!prompter)
michael@0 1950 {
michael@0 1951 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
michael@0 1952 ("No prompter from DocShell, no way to alert user"));
michael@0 1953 return;
michael@0 1954 }
michael@0 1955 }
michael@0 1956
michael@0 1957 // We should always have a prompter at this point.
michael@0 1958 prompter->Alert(title, msgText);
michael@0 1959 }
michael@0 1960 }
michael@0 1961 }
michael@0 1962 }
michael@0 1963 }
michael@0 1964
michael@0 1965 NS_IMETHODIMP
michael@0 1966 nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
michael@0 1967 nsIInputStream * inStr,
michael@0 1968 uint64_t sourceOffset, uint32_t count)
michael@0 1969 {
michael@0 1970 nsresult rv = NS_OK;
michael@0 1971 // first, check to see if we've been canceled....
michael@0 1972 if (mCanceled || !mSaver) // then go cancel our underlying channel too
michael@0 1973 return request->Cancel(NS_BINDING_ABORTED);
michael@0 1974
michael@0 1975 // read the data out of the stream and write it to the temp file.
michael@0 1976 if (count > 0)
michael@0 1977 {
michael@0 1978 mProgress += count;
michael@0 1979
michael@0 1980 nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
michael@0 1981 rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
michael@0 1982 if (NS_SUCCEEDED(rv))
michael@0 1983 {
michael@0 1984 // Send progress notification.
michael@0 1985 if (mTransfer) {
michael@0 1986 mTransfer->OnProgressChange64(nullptr, request, mProgress,
michael@0 1987 mContentLength, mProgress,
michael@0 1988 mContentLength);
michael@0 1989 }
michael@0 1990 }
michael@0 1991 else
michael@0 1992 {
michael@0 1993 // An error occurred, notify listener.
michael@0 1994 nsAutoString tempFilePath;
michael@0 1995 if (mTempFile)
michael@0 1996 mTempFile->GetPath(tempFilePath);
michael@0 1997 SendStatusChange(kReadError, rv, request, tempFilePath);
michael@0 1998
michael@0 1999 // Cancel the download.
michael@0 2000 Cancel(rv);
michael@0 2001 }
michael@0 2002 }
michael@0 2003 return rv;
michael@0 2004 }
michael@0 2005
michael@0 2006 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
michael@0 2007 nsresult aStatus)
michael@0 2008 {
michael@0 2009 LOG(("nsExternalAppHandler::OnStopRequest\n"
michael@0 2010 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
michael@0 2011 mCanceled, mTransfer.get(), aStatus));
michael@0 2012
michael@0 2013 mStopRequestIssued = true;
michael@0 2014
michael@0 2015 // Cancel if the request did not complete successfully.
michael@0 2016 if (!mCanceled && NS_FAILED(aStatus))
michael@0 2017 {
michael@0 2018 // Send error notification.
michael@0 2019 nsAutoString tempFilePath;
michael@0 2020 if (mTempFile)
michael@0 2021 mTempFile->GetPath(tempFilePath);
michael@0 2022 SendStatusChange( kReadError, aStatus, request, tempFilePath );
michael@0 2023
michael@0 2024 Cancel(aStatus);
michael@0 2025 }
michael@0 2026
michael@0 2027 // first, check to see if we've been canceled....
michael@0 2028 if (mCanceled || !mSaver)
michael@0 2029 return NS_OK;
michael@0 2030
michael@0 2031 return mSaver->Finish(NS_OK);
michael@0 2032 }
michael@0 2033
michael@0 2034 NS_IMETHODIMP
michael@0 2035 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
michael@0 2036 nsIFile *aTarget)
michael@0 2037 {
michael@0 2038 return NS_OK;
michael@0 2039 }
michael@0 2040
michael@0 2041 NS_IMETHODIMP
michael@0 2042 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
michael@0 2043 nsresult aStatus)
michael@0 2044 {
michael@0 2045 LOG(("nsExternalAppHandler::OnSaveComplete\n"
michael@0 2046 " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
michael@0 2047 aSaver, aStatus, mCanceled, mTransfer.get()));
michael@0 2048
michael@0 2049 if (!mCanceled) {
michael@0 2050 // Save the hash
michael@0 2051 (void)mSaver->GetSha256Hash(mHash);
michael@0 2052 (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
michael@0 2053 // Free the reference that the saver keeps on us, even if we couldn't get
michael@0 2054 // the hash.
michael@0 2055 mSaver = nullptr;
michael@0 2056
michael@0 2057 if (NS_FAILED(aStatus)) {
michael@0 2058 nsAutoString path;
michael@0 2059 mTempFile->GetPath(path);
michael@0 2060
michael@0 2061 // It may happen when e10s is enabled that there will be no transfer
michael@0 2062 // object available to communicate status as expected by the system.
michael@0 2063 // Let's try and create a temporary transfer object to take care of this
michael@0 2064 // for us, we'll fall back to using the prompt service if we absolutely
michael@0 2065 // have to.
michael@0 2066 if (!mTransfer) {
michael@0 2067 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
michael@0 2068 // We don't care if this fails.
michael@0 2069 CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
michael@0 2070 }
michael@0 2071
michael@0 2072 SendStatusChange(kWriteError, aStatus, nullptr, path);
michael@0 2073 if (!mCanceled)
michael@0 2074 Cancel(aStatus);
michael@0 2075 return NS_OK;
michael@0 2076 }
michael@0 2077 }
michael@0 2078
michael@0 2079 // Notify the transfer object that we are done if the user has chosen an
michael@0 2080 // action. If the user hasn't chosen an action, the progress listener
michael@0 2081 // (nsITransfer) will be notified in CreateTransfer.
michael@0 2082 if (mTransfer) {
michael@0 2083 NotifyTransfer(aStatus);
michael@0 2084 }
michael@0 2085
michael@0 2086 return NS_OK;
michael@0 2087 }
michael@0 2088
michael@0 2089 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
michael@0 2090 {
michael@0 2091 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
michael@0 2092 MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
michael@0 2093
michael@0 2094 LOG(("Notifying progress listener"));
michael@0 2095
michael@0 2096 if (NS_SUCCEEDED(aStatus)) {
michael@0 2097 (void)mTransfer->SetSha256Hash(mHash);
michael@0 2098 (void)mTransfer->SetSignatureInfo(mSignatureInfo);
michael@0 2099 (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
michael@0 2100 mContentLength, mProgress, mContentLength);
michael@0 2101 }
michael@0 2102
michael@0 2103 (void)mTransfer->OnStateChange(nullptr, nullptr,
michael@0 2104 nsIWebProgressListener::STATE_STOP |
michael@0 2105 nsIWebProgressListener::STATE_IS_REQUEST |
michael@0 2106 nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
michael@0 2107
michael@0 2108 // This nsITransfer object holds a reference to us (we are its observer), so
michael@0 2109 // we need to release the reference to break a reference cycle (and therefore
michael@0 2110 // to prevent leaking). We do this even if the previous calls failed.
michael@0 2111 mTransfer = nullptr;
michael@0 2112 }
michael@0 2113
michael@0 2114 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
michael@0 2115 {
michael@0 2116 *aMIMEInfo = mMimeInfo;
michael@0 2117 NS_ADDREF(*aMIMEInfo);
michael@0 2118 return NS_OK;
michael@0 2119 }
michael@0 2120
michael@0 2121 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
michael@0 2122 {
michael@0 2123 NS_ENSURE_ARG(aSourceURI);
michael@0 2124 *aSourceURI = mSourceUrl;
michael@0 2125 NS_IF_ADDREF(*aSourceURI);
michael@0 2126 return NS_OK;
michael@0 2127 }
michael@0 2128
michael@0 2129 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
michael@0 2130 {
michael@0 2131 aSuggestedFileName = mSuggestedFileName;
michael@0 2132 return NS_OK;
michael@0 2133 }
michael@0 2134
michael@0 2135 nsresult nsExternalAppHandler::CreateTransfer()
michael@0 2136 {
michael@0 2137 LOG(("nsExternalAppHandler::CreateTransfer"));
michael@0 2138
michael@0 2139 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
michael@0 2140 // We are back from the helper app dialog (where the user chooses to save or
michael@0 2141 // open), but we aren't done processing the load. in this case, throw up a
michael@0 2142 // progress dialog so the user can see what's going on.
michael@0 2143 // Also, release our reference to mDialog. We don't need it anymore, and we
michael@0 2144 // need to break the reference cycle.
michael@0 2145 mDialog = nullptr;
michael@0 2146 if (!mDialogProgressListener) {
michael@0 2147 NS_WARNING("The dialog should nullify the dialog progress listener");
michael@0 2148 }
michael@0 2149 nsresult rv;
michael@0 2150
michael@0 2151 // We must be able to create an nsITransfer object. If not, it doesn't matter
michael@0 2152 // much that we can't launch the helper application or save to disk. Work on
michael@0 2153 // a local copy rather than mTransfer until we know we succeeded, to make it
michael@0 2154 // clearer that this function is re-entrant.
michael@0 2155 nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
michael@0 2156 NS_TRANSFER_CONTRACTID, &rv);
michael@0 2157 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2158
michael@0 2159 // Initialize the download
michael@0 2160 nsCOMPtr<nsIURI> target;
michael@0 2161 rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
michael@0 2162 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2163
michael@0 2164 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
michael@0 2165
michael@0 2166 rv = transfer->Init(mSourceUrl, target, EmptyString(),
michael@0 2167 mMimeInfo, mTimeDownloadStarted, mTempFile, this,
michael@0 2168 channel && NS_UsePrivateBrowsing(channel));
michael@0 2169 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2170
michael@0 2171 // Now let's add the download to history
michael@0 2172 nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
michael@0 2173 if (dh) {
michael@0 2174 nsCOMPtr<nsIURI> referrer;
michael@0 2175 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
michael@0 2176 if (channel) {
michael@0 2177 NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
michael@0 2178 }
michael@0 2179
michael@0 2180 if (channel && !NS_UsePrivateBrowsing(channel)) {
michael@0 2181 dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
michael@0 2182 }
michael@0 2183 }
michael@0 2184
michael@0 2185 // If we were cancelled since creating the transfer, just return. It is
michael@0 2186 // always ok to return NS_OK if we are cancelled. Callers of this function
michael@0 2187 // must call Cancel if CreateTransfer fails, but there's no need to cancel
michael@0 2188 // twice.
michael@0 2189 if (mCanceled) {
michael@0 2190 return NS_OK;
michael@0 2191 }
michael@0 2192 rv = transfer->OnStateChange(nullptr, mRequest,
michael@0 2193 nsIWebProgressListener::STATE_START |
michael@0 2194 nsIWebProgressListener::STATE_IS_REQUEST |
michael@0 2195 nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
michael@0 2196 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2197
michael@0 2198 if (mCanceled) {
michael@0 2199 return NS_OK;
michael@0 2200 }
michael@0 2201
michael@0 2202 mRequest = nullptr;
michael@0 2203 // Finally, save the transfer to mTransfer.
michael@0 2204 mTransfer = transfer;
michael@0 2205 transfer = nullptr;
michael@0 2206
michael@0 2207 // While we were bringing up the progress dialog, we actually finished
michael@0 2208 // processing the url. If that's the case then mStopRequestIssued will be
michael@0 2209 // true and OnSaveComplete has been called.
michael@0 2210 if (mStopRequestIssued && !mSaver && mTransfer) {
michael@0 2211 NotifyTransfer(NS_OK);
michael@0 2212 }
michael@0 2213
michael@0 2214 return rv;
michael@0 2215 }
michael@0 2216
michael@0 2217 nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
michael@0 2218 {
michael@0 2219 nsresult rv;
michael@0 2220 nsCOMPtr<nsITransfer> transfer =
michael@0 2221 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
michael@0 2222 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2223
michael@0 2224 // If we don't have a download directory we're kinda screwed but it's OK
michael@0 2225 // we'll still report the error via the prompter.
michael@0 2226 nsCOMPtr<nsIFile> pseudoFile;
michael@0 2227 rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
michael@0 2228 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2229
michael@0 2230 // Append the default suggested filename. If the user restarts the transfer
michael@0 2231 // we will re-trigger a filename check anyway to ensure that it is unique.
michael@0 2232 rv = pseudoFile->Append(mSuggestedFileName);
michael@0 2233 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2234
michael@0 2235 nsCOMPtr<nsIURI> pseudoTarget;
michael@0 2236 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
michael@0 2237 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2238
michael@0 2239 rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
michael@0 2240 mMimeInfo, mTimeDownloadStarted, nullptr, this,
michael@0 2241 aIsPrivateBrowsing);
michael@0 2242 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2243
michael@0 2244 // Our failed transfer is ready.
michael@0 2245 mTransfer = transfer.forget();
michael@0 2246
michael@0 2247 return NS_OK;
michael@0 2248 }
michael@0 2249
michael@0 2250 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
michael@0 2251 {
michael@0 2252 if (aFile)
michael@0 2253 ContinueSave(aFile);
michael@0 2254 else
michael@0 2255 Cancel(NS_BINDING_ABORTED);
michael@0 2256
michael@0 2257 return NS_OK;
michael@0 2258 }
michael@0 2259
michael@0 2260 void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
michael@0 2261 {
michael@0 2262 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
michael@0 2263 // Convert to use file picker? No, then embeddors could not do any sort of
michael@0 2264 // "AutoDownload" w/o showing a prompt
michael@0 2265 nsresult rv = NS_OK;
michael@0 2266 if (!mDialog)
michael@0 2267 {
michael@0 2268 // Get helper app launcher dialog.
michael@0 2269 mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
michael@0 2270 if (rv != NS_OK) {
michael@0 2271 Cancel(NS_BINDING_ABORTED);
michael@0 2272 return;
michael@0 2273 }
michael@0 2274 }
michael@0 2275
michael@0 2276 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
michael@0 2277 // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
michael@0 2278
michael@0 2279 // Now, be sure to keep |this| alive, and the dialog
michael@0 2280 // If we don't do this, users that close the helper app dialog while the file
michael@0 2281 // picker is up would cause Cancel() to be called, and the dialog would be
michael@0 2282 // released, which would release this object too, which would crash.
michael@0 2283 // See Bug 249143
michael@0 2284 nsIFile* fileToUse;
michael@0 2285 nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
michael@0 2286 nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
michael@0 2287 rv = mDialog->PromptForSaveToFile(this,
michael@0 2288 mWindowContext,
michael@0 2289 aDefaultFile.get(),
michael@0 2290 aFileExtension.get(),
michael@0 2291 mForceSave, &fileToUse);
michael@0 2292
michael@0 2293 if (rv == NS_ERROR_NOT_AVAILABLE) {
michael@0 2294 // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
michael@0 2295 rv = mDialog->PromptForSaveToFileAsync(this,
michael@0 2296 mWindowContext,
michael@0 2297 aDefaultFile.get(),
michael@0 2298 aFileExtension.get(),
michael@0 2299 mForceSave);
michael@0 2300 } else {
michael@0 2301 SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
michael@0 2302 }
michael@0 2303 }
michael@0 2304
michael@0 2305 // SaveToDisk should only be called by the helper app dialog which allows
michael@0 2306 // the user to say launch with application or save to disk. It doesn't actually
michael@0 2307 // perform the save, it just prompts for the destination file name.
michael@0 2308 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
michael@0 2309 {
michael@0 2310 if (mCanceled)
michael@0 2311 return NS_OK;
michael@0 2312
michael@0 2313 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
michael@0 2314
michael@0 2315 if (!aNewFileLocation) {
michael@0 2316 if (mSuggestedFileName.IsEmpty())
michael@0 2317 RequestSaveDestination(mTempLeafName, mTempFileExtension);
michael@0 2318 else
michael@0 2319 {
michael@0 2320 nsAutoString fileExt;
michael@0 2321 int32_t pos = mSuggestedFileName.RFindChar('.');
michael@0 2322 if (pos >= 0)
michael@0 2323 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
michael@0 2324 if (fileExt.IsEmpty())
michael@0 2325 fileExt = mTempFileExtension;
michael@0 2326
michael@0 2327 RequestSaveDestination(mSuggestedFileName, fileExt);
michael@0 2328 }
michael@0 2329 } else {
michael@0 2330 ContinueSave(aNewFileLocation);
michael@0 2331 }
michael@0 2332
michael@0 2333 return NS_OK;
michael@0 2334 }
michael@0 2335 nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
michael@0 2336 {
michael@0 2337 if (mCanceled)
michael@0 2338 return NS_OK;
michael@0 2339
michael@0 2340 NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
michael@0 2341
michael@0 2342 nsresult rv = NS_OK;
michael@0 2343 nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
michael@0 2344 mFinalFileDestination = do_QueryInterface(fileToUse);
michael@0 2345
michael@0 2346 // Move what we have in the final directory, but append .part
michael@0 2347 // to it, to indicate that it's unfinished. Do not call SetTarget on the
michael@0 2348 // saver if we are done (Finish has been called) but OnSaverComplete has not
michael@0 2349 // been called.
michael@0 2350 if (mFinalFileDestination && mSaver && !mStopRequestIssued)
michael@0 2351 {
michael@0 2352 nsCOMPtr<nsIFile> movedFile;
michael@0 2353 mFinalFileDestination->Clone(getter_AddRefs(movedFile));
michael@0 2354 if (movedFile) {
michael@0 2355 // Get the old leaf name and append .part to it
michael@0 2356 nsAutoString name;
michael@0 2357 mFinalFileDestination->GetLeafName(name);
michael@0 2358 name.AppendLiteral(".part");
michael@0 2359 movedFile->SetLeafName(name);
michael@0 2360
michael@0 2361 rv = mSaver->SetTarget(movedFile, true);
michael@0 2362 if (NS_FAILED(rv)) {
michael@0 2363 nsAutoString path;
michael@0 2364 mTempFile->GetPath(path);
michael@0 2365 SendStatusChange(kWriteError, rv, nullptr, path);
michael@0 2366 Cancel(rv);
michael@0 2367 return NS_OK;
michael@0 2368 }
michael@0 2369
michael@0 2370 mTempFile = movedFile;
michael@0 2371 }
michael@0 2372 }
michael@0 2373
michael@0 2374 // The helper app dialog has told us what to do and we have a final file
michael@0 2375 // destination.
michael@0 2376 rv = CreateTransfer();
michael@0 2377 // If we fail to create the transfer, Cancel.
michael@0 2378 if (NS_FAILED(rv)) {
michael@0 2379 Cancel(rv);
michael@0 2380 return rv;
michael@0 2381 }
michael@0 2382
michael@0 2383 // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
michael@0 2384 // if there is one. We don't want to do this before the save as dialog goes away because this dialog
michael@0 2385 // is modal and we do bad things if you try to load a web page in the underlying window while a modal
michael@0 2386 // dialog is still up.
michael@0 2387 ProcessAnyRefreshTags();
michael@0 2388
michael@0 2389 return NS_OK;
michael@0 2390 }
michael@0 2391
michael@0 2392
michael@0 2393 // LaunchWithApplication should only be called by the helper app dialog which
michael@0 2394 // allows the user to say launch with application or save to disk. It doesn't
michael@0 2395 // actually perform launch with application.
michael@0 2396 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
michael@0 2397 {
michael@0 2398 if (mCanceled)
michael@0 2399 return NS_OK;
michael@0 2400
michael@0 2401 // user has chosen to launch using an application, fire any refresh tags now...
michael@0 2402 ProcessAnyRefreshTags();
michael@0 2403
michael@0 2404 if (mMimeInfo && aApplication) {
michael@0 2405 PlatformLocalHandlerApp_t *handlerApp =
michael@0 2406 new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
michael@0 2407 mMimeInfo->SetPreferredApplicationHandler(handlerApp);
michael@0 2408 }
michael@0 2409
michael@0 2410 // Now check if the file is local, in which case we won't bother with saving
michael@0 2411 // it to a temporary directory and just launch it from where it is
michael@0 2412 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
michael@0 2413 if (fileUrl && mIsFileChannel)
michael@0 2414 {
michael@0 2415 Cancel(NS_BINDING_ABORTED);
michael@0 2416 nsCOMPtr<nsIFile> file;
michael@0 2417 nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
michael@0 2418
michael@0 2419 if (NS_SUCCEEDED(rv))
michael@0 2420 {
michael@0 2421 rv = mMimeInfo->LaunchWithFile(file);
michael@0 2422 if (NS_SUCCEEDED(rv))
michael@0 2423 return NS_OK;
michael@0 2424 }
michael@0 2425 nsAutoString path;
michael@0 2426 if (file)
michael@0 2427 file->GetPath(path);
michael@0 2428 // If we get here, an error happened
michael@0 2429 SendStatusChange(kLaunchError, rv, nullptr, path);
michael@0 2430 return rv;
michael@0 2431 }
michael@0 2432
michael@0 2433 // Now that the user has elected to launch the downloaded file with a helper
michael@0 2434 // app, we're justified in removing the 'salted' name. We'll rename to what
michael@0 2435 // was specified in mSuggestedFileName after the download is done prior to
michael@0 2436 // launching the helper app. So that any existing file of that name won't be
michael@0 2437 // overwritten we call CreateUnique(). Also note that we use the same
michael@0 2438 // directory as originally downloaded so nsDownload can rename in place
michael@0 2439 // later.
michael@0 2440 nsCOMPtr<nsIFile> fileToUse;
michael@0 2441 (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
michael@0 2442
michael@0 2443 if (mSuggestedFileName.IsEmpty())
michael@0 2444 {
michael@0 2445 // Keep using the leafname of the temp file, since we're just starting a helper
michael@0 2446 mSuggestedFileName = mTempLeafName;
michael@0 2447 }
michael@0 2448
michael@0 2449 #ifdef XP_WIN
michael@0 2450 fileToUse->Append(mSuggestedFileName + mTempFileExtension);
michael@0 2451 #else
michael@0 2452 fileToUse->Append(mSuggestedFileName);
michael@0 2453 #endif
michael@0 2454
michael@0 2455 nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
michael@0 2456 if(NS_SUCCEEDED(rv))
michael@0 2457 {
michael@0 2458 mFinalFileDestination = do_QueryInterface(fileToUse);
michael@0 2459 // launch the progress window now that the user has picked the desired action.
michael@0 2460 rv = CreateTransfer();
michael@0 2461 if (NS_FAILED(rv)) {
michael@0 2462 Cancel(rv);
michael@0 2463 }
michael@0 2464 }
michael@0 2465 else
michael@0 2466 {
michael@0 2467 // Cancel the download and report an error. We do not want to end up in
michael@0 2468 // a state where it appears that we have a normal download that is
michael@0 2469 // pointing to a file that we did not actually create.
michael@0 2470 nsAutoString path;
michael@0 2471 mTempFile->GetPath(path);
michael@0 2472 SendStatusChange(kWriteError, rv, nullptr, path);
michael@0 2473 Cancel(rv);
michael@0 2474 }
michael@0 2475 return rv;
michael@0 2476 }
michael@0 2477
michael@0 2478 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
michael@0 2479 {
michael@0 2480 NS_ENSURE_ARG(NS_FAILED(aReason));
michael@0 2481
michael@0 2482 if (mCanceled) {
michael@0 2483 return NS_OK;
michael@0 2484 }
michael@0 2485 mCanceled = true;
michael@0 2486
michael@0 2487 if (mSaver) {
michael@0 2488 // We are still writing to the target file. Give the saver a chance to
michael@0 2489 // close the target file, then notify the transfer object if necessary in
michael@0 2490 // the OnSaveComplete callback.
michael@0 2491 mSaver->Finish(aReason);
michael@0 2492 mSaver = nullptr;
michael@0 2493 } else {
michael@0 2494 if (mStopRequestIssued && mTempFile) {
michael@0 2495 // This branch can only happen when the user cancels the helper app dialog
michael@0 2496 // when the request has completed. The temp file has to be removed here,
michael@0 2497 // because mSaver has been released at that time with the temp file left.
michael@0 2498 (void)mTempFile->Remove(false);
michael@0 2499 }
michael@0 2500
michael@0 2501 // Notify the transfer object that the download has been canceled, if the
michael@0 2502 // user has already chosen an action and we didn't notify already.
michael@0 2503 if (mTransfer) {
michael@0 2504 NotifyTransfer(aReason);
michael@0 2505 }
michael@0 2506 }
michael@0 2507
michael@0 2508 // Break our reference cycle with the helper app dialog (set up in
michael@0 2509 // OnStartRequest)
michael@0 2510 mDialog = nullptr;
michael@0 2511
michael@0 2512 mRequest = nullptr;
michael@0 2513
michael@0 2514 // Release the listener, to break the reference cycle with it (we are the
michael@0 2515 // observer of the listener).
michael@0 2516 mDialogProgressListener = nullptr;
michael@0 2517
michael@0 2518 return NS_OK;
michael@0 2519 }
michael@0 2520
michael@0 2521 void nsExternalAppHandler::ProcessAnyRefreshTags()
michael@0 2522 {
michael@0 2523 // one last thing, try to see if the original window context supports a refresh interface...
michael@0 2524 // Sometimes, when you download content that requires an external handler, there is
michael@0 2525 // a refresh header associated with the download. This refresh header points to a page
michael@0 2526 // the content provider wants the user to see after they download the content. How do we
michael@0 2527 // pass this refresh information back to the caller? For now, try to get the refresh URI
michael@0 2528 // interface. If the window context where the request originated came from supports this
michael@0 2529 // then we can force it to process the refresh information (if there is any) from this channel.
michael@0 2530 if (mWindowContext && mOriginalChannel)
michael@0 2531 {
michael@0 2532 nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext));
michael@0 2533 if (refreshHandler) {
michael@0 2534 refreshHandler->SetupRefreshURI(mOriginalChannel);
michael@0 2535 }
michael@0 2536 mOriginalChannel = nullptr;
michael@0 2537 }
michael@0 2538 }
michael@0 2539
michael@0 2540 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
michael@0 2541 {
michael@0 2542 // Search the obsolete pref strings.
michael@0 2543 nsAdoptingCString prefCString = Preferences::GetCString(prefName);
michael@0 2544 if (prefCString.IsEmpty()) {
michael@0 2545 // Default is true, if not found in the pref string.
michael@0 2546 return true;
michael@0 2547 }
michael@0 2548
michael@0 2549 NS_UnescapeURL(prefCString);
michael@0 2550 nsACString::const_iterator start, end;
michael@0 2551 prefCString.BeginReading(start);
michael@0 2552 prefCString.EndReading(end);
michael@0 2553 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
michael@0 2554 start, end);
michael@0 2555 }
michael@0 2556
michael@0 2557 nsresult nsExternalAppHandler::MaybeCloseWindow()
michael@0 2558 {
michael@0 2559 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWindowContext);
michael@0 2560 NS_ENSURE_STATE(window);
michael@0 2561
michael@0 2562 if (mShouldCloseWindow) {
michael@0 2563 // Reset the window context to the opener window so that the dependent
michael@0 2564 // dialogs have a parent
michael@0 2565 nsCOMPtr<nsIDOMWindow> opener;
michael@0 2566 window->GetOpener(getter_AddRefs(opener));
michael@0 2567
michael@0 2568 bool isClosed;
michael@0 2569 if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) {
michael@0 2570 mWindowContext = do_GetInterface(opener);
michael@0 2571
michael@0 2572 // Now close the old window. Do it on a timer so that we don't run
michael@0 2573 // into issues trying to close the window before it has fully opened.
michael@0 2574 NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
michael@0 2575 mTimer = do_CreateInstance("@mozilla.org/timer;1");
michael@0 2576 if (!mTimer) {
michael@0 2577 return NS_ERROR_FAILURE;
michael@0 2578 }
michael@0 2579
michael@0 2580 mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
michael@0 2581 mWindowToClose = window;
michael@0 2582 }
michael@0 2583 }
michael@0 2584
michael@0 2585 return NS_OK;
michael@0 2586 }
michael@0 2587
michael@0 2588 NS_IMETHODIMP
michael@0 2589 nsExternalAppHandler::Notify(nsITimer* timer)
michael@0 2590 {
michael@0 2591 NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
michael@0 2592
michael@0 2593 mWindowToClose->Close();
michael@0 2594 mWindowToClose = nullptr;
michael@0 2595 mTimer = nullptr;
michael@0 2596
michael@0 2597 return NS_OK;
michael@0 2598 }
michael@0 2599 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 2600 // The following section contains our nsIMIMEService implementation and related methods.
michael@0 2601 //
michael@0 2602 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
michael@0 2603
michael@0 2604 // nsIMIMEService methods
michael@0 2605 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval)
michael@0 2606 {
michael@0 2607 NS_PRECONDITION(!aMIMEType.IsEmpty() ||
michael@0 2608 !aFileExt.IsEmpty(),
michael@0 2609 "Give me something to work with");
michael@0 2610 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
michael@0 2611 PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
michael@0 2612
michael@0 2613 *_retval = nullptr;
michael@0 2614
michael@0 2615 // OK... we need a type. Get one.
michael@0 2616 nsAutoCString typeToUse(aMIMEType);
michael@0 2617 if (typeToUse.IsEmpty()) {
michael@0 2618 nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
michael@0 2619 if (NS_FAILED(rv))
michael@0 2620 return NS_ERROR_NOT_AVAILABLE;
michael@0 2621 }
michael@0 2622
michael@0 2623 // We promise to only send lower case mime types to the OS
michael@0 2624 ToLowerCase(typeToUse);
michael@0 2625
michael@0 2626 // (1) Ask the OS for a mime info
michael@0 2627 bool found;
michael@0 2628 *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
michael@0 2629 LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
michael@0 2630 // If we got no mimeinfo, something went wrong. Probably lack of memory.
michael@0 2631 if (!*_retval)
michael@0 2632 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2633
michael@0 2634 // (2) Now, let's see if we can find something in our datastore
michael@0 2635 // This will not overwrite the OS information that interests us
michael@0 2636 // (i.e. default application, default app. description)
michael@0 2637 nsresult rv;
michael@0 2638 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
michael@0 2639 if (handlerSvc) {
michael@0 2640 bool hasHandler = false;
michael@0 2641 (void) handlerSvc->Exists(*_retval, &hasHandler);
michael@0 2642 if (hasHandler) {
michael@0 2643 rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
michael@0 2644 LOG(("Data source: Via type: retval 0x%08x\n", rv));
michael@0 2645 } else {
michael@0 2646 rv = NS_ERROR_NOT_AVAILABLE;
michael@0 2647 }
michael@0 2648
michael@0 2649 found = found || NS_SUCCEEDED(rv);
michael@0 2650
michael@0 2651 if (!found || NS_FAILED(rv)) {
michael@0 2652 // No type match, try extension match
michael@0 2653 if (!aFileExt.IsEmpty()) {
michael@0 2654 nsAutoCString overrideType;
michael@0 2655 rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
michael@0 2656 if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
michael@0 2657 // We can't check handlerSvc->Exists() here, because we have a
michael@0 2658 // overideType. That's ok, it just results in some console noise.
michael@0 2659 // (If there's no handler for the override type, it throws)
michael@0 2660 rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
michael@0 2661 LOG(("Data source: Via ext: retval 0x%08x\n", rv));
michael@0 2662 found = found || NS_SUCCEEDED(rv);
michael@0 2663 }
michael@0 2664 }
michael@0 2665 }
michael@0 2666 }
michael@0 2667
michael@0 2668 // (3) No match yet. Ask extras.
michael@0 2669 if (!found) {
michael@0 2670 rv = NS_ERROR_FAILURE;
michael@0 2671 #ifdef XP_WIN
michael@0 2672 /* XXX Gross hack to wallpaper over the most common Win32
michael@0 2673 * extension issues caused by the fix for bug 116938. See bug
michael@0 2674 * 120327, comment 271 for why this is needed. Not even sure we
michael@0 2675 * want to remove this once we have fixed all this stuff to work
michael@0 2676 * right; any info we get from extras on this type is pretty much
michael@0 2677 * useless....
michael@0 2678 */
michael@0 2679 if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
michael@0 2680 #endif
michael@0 2681 rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
michael@0 2682 LOG(("Searched extras (by type), rv 0x%08X\n", rv));
michael@0 2683 // If that didn't work out, try file extension from extras
michael@0 2684 if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
michael@0 2685 rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
michael@0 2686 LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
michael@0 2687 }
michael@0 2688 // If that still didn't work, set the file description to "ext File"
michael@0 2689 if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
michael@0 2690 // XXXzpao This should probably be localized
michael@0 2691 nsAutoCString desc(aFileExt);
michael@0 2692 desc.Append(" File");
michael@0 2693 (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
michael@0 2694 LOG(("Falling back to 'File' file description\n"));
michael@0 2695 }
michael@0 2696 }
michael@0 2697
michael@0 2698 // Finally, check if we got a file extension and if yes, if it is an
michael@0 2699 // extension on the mimeinfo, in which case we want it to be the primary one
michael@0 2700 if (!aFileExt.IsEmpty()) {
michael@0 2701 bool matches = false;
michael@0 2702 (*_retval)->ExtensionExists(aFileExt, &matches);
michael@0 2703 LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
michael@0 2704 if (matches)
michael@0 2705 (*_retval)->SetPrimaryExtension(aFileExt);
michael@0 2706 }
michael@0 2707
michael@0 2708 #ifdef PR_LOGGING
michael@0 2709 if (LOG_ENABLED()) {
michael@0 2710 nsAutoCString type;
michael@0 2711 (*_retval)->GetMIMEType(type);
michael@0 2712
michael@0 2713 nsAutoCString ext;
michael@0 2714 (*_retval)->GetPrimaryExtension(ext);
michael@0 2715 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
michael@0 2716 }
michael@0 2717 #endif
michael@0 2718
michael@0 2719 return NS_OK;
michael@0 2720 }
michael@0 2721
michael@0 2722 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType)
michael@0 2723 {
michael@0 2724 // OK. We want to try the following sources of mimetype information, in this order:
michael@0 2725 // 1. defaultMimeEntries array
michael@0 2726 // 2. User-set preferences (managed by the handler service)
michael@0 2727 // 3. OS-provided information
michael@0 2728 // 4. our "extras" array
michael@0 2729 // 5. Information from plugins
michael@0 2730 // 6. The "ext-to-type-mapping" category
michael@0 2731
michael@0 2732 // Early return if called with an empty extension parameter
michael@0 2733 if (aFileExt.IsEmpty())
michael@0 2734 return NS_ERROR_NOT_AVAILABLE;
michael@0 2735
michael@0 2736 nsresult rv = NS_OK;
michael@0 2737 // First of all, check our default entries
michael@0 2738 for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++)
michael@0 2739 {
michael@0 2740 if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) {
michael@0 2741 aContentType = defaultMimeEntries[i].mMimeType;
michael@0 2742 return rv;
michael@0 2743 }
michael@0 2744 }
michael@0 2745
michael@0 2746 // Check user-set prefs
michael@0 2747 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
michael@0 2748 if (handlerSvc)
michael@0 2749 rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType);
michael@0 2750 if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty())
michael@0 2751 return NS_OK;
michael@0 2752
michael@0 2753 // Ask OS.
michael@0 2754 bool found = false;
michael@0 2755 nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
michael@0 2756 if (mi && found)
michael@0 2757 return mi->GetMIMEType(aContentType);
michael@0 2758
michael@0 2759 // Check extras array.
michael@0 2760 found = GetTypeFromExtras(aFileExt, aContentType);
michael@0 2761 if (found)
michael@0 2762 return NS_OK;
michael@0 2763
michael@0 2764 const nsCString& flatExt = PromiseFlatCString(aFileExt);
michael@0 2765 // Try the plugins
michael@0 2766 const char* mimeType;
michael@0 2767 nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv));
michael@0 2768 nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get());
michael@0 2769 if (NS_SUCCEEDED(rv)) {
michael@0 2770 if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) {
michael@0 2771 aContentType = mimeType;
michael@0 2772 return NS_OK;
michael@0 2773 }
michael@0 2774 }
michael@0 2775
michael@0 2776 rv = NS_OK;
michael@0 2777 // Let's see if an extension added something
michael@0 2778 nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
michael@0 2779 if (catMan) {
michael@0 2780 // The extension in the category entry is always stored as lowercase
michael@0 2781 nsAutoCString lowercaseFileExt(aFileExt);
michael@0 2782 ToLowerCase(lowercaseFileExt);
michael@0 2783 // Read the MIME type from the category entry, if available
michael@0 2784 nsXPIDLCString type;
michael@0 2785 rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(),
michael@0 2786 getter_Copies(type));
michael@0 2787 aContentType = type;
michael@0 2788 }
michael@0 2789 else {
michael@0 2790 rv = NS_ERROR_NOT_AVAILABLE;
michael@0 2791 }
michael@0 2792
michael@0 2793 return rv;
michael@0 2794 }
michael@0 2795
michael@0 2796 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
michael@0 2797 {
michael@0 2798 NS_ENSURE_ARG(!aMIMEType.IsEmpty());
michael@0 2799
michael@0 2800 nsCOMPtr<nsIMIMEInfo> mi;
michael@0 2801 nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
michael@0 2802 if (NS_FAILED(rv))
michael@0 2803 return rv;
michael@0 2804
michael@0 2805 return mi->GetPrimaryExtension(_retval);
michael@0 2806 }
michael@0 2807
michael@0 2808 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType)
michael@0 2809 {
michael@0 2810 NS_ENSURE_ARG_POINTER(aURI);
michael@0 2811 nsresult rv = NS_ERROR_NOT_AVAILABLE;
michael@0 2812 aContentType.Truncate();
michael@0 2813
michael@0 2814 // First look for a file to use. If we have one, we just use that.
michael@0 2815 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
michael@0 2816 if (fileUrl) {
michael@0 2817 nsCOMPtr<nsIFile> file;
michael@0 2818 rv = fileUrl->GetFile(getter_AddRefs(file));
michael@0 2819 if (NS_SUCCEEDED(rv)) {
michael@0 2820 rv = GetTypeFromFile(file, aContentType);
michael@0 2821 if (NS_SUCCEEDED(rv)) {
michael@0 2822 // we got something!
michael@0 2823 return rv;
michael@0 2824 }
michael@0 2825 }
michael@0 2826 }
michael@0 2827
michael@0 2828 // Now try to get an nsIURL so we don't have to do our own parsing
michael@0 2829 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
michael@0 2830 if (url) {
michael@0 2831 nsAutoCString ext;
michael@0 2832 rv = url->GetFileExtension(ext);
michael@0 2833 if (NS_FAILED(rv))
michael@0 2834 return rv;
michael@0 2835 if (ext.IsEmpty())
michael@0 2836 return NS_ERROR_NOT_AVAILABLE;
michael@0 2837
michael@0 2838 UnescapeFragment(ext, url, ext);
michael@0 2839
michael@0 2840 return GetTypeFromExtension(ext, aContentType);
michael@0 2841 }
michael@0 2842
michael@0 2843 // no url, let's give the raw spec a shot
michael@0 2844 nsAutoCString specStr;
michael@0 2845 rv = aURI->GetSpec(specStr);
michael@0 2846 if (NS_FAILED(rv))
michael@0 2847 return rv;
michael@0 2848 UnescapeFragment(specStr, aURI, specStr);
michael@0 2849
michael@0 2850 // find the file extension (if any)
michael@0 2851 int32_t extLoc = specStr.RFindChar('.');
michael@0 2852 int32_t specLength = specStr.Length();
michael@0 2853 if (-1 != extLoc &&
michael@0 2854 extLoc != specLength - 1 &&
michael@0 2855 // nothing over 20 chars long can be sanely considered an
michael@0 2856 // extension.... Dat dere would be just data.
michael@0 2857 specLength - extLoc < 20)
michael@0 2858 {
michael@0 2859 return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
michael@0 2860 }
michael@0 2861
michael@0 2862 // We found no information; say so.
michael@0 2863 return NS_ERROR_NOT_AVAILABLE;
michael@0 2864 }
michael@0 2865
michael@0 2866 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
michael@0 2867 {
michael@0 2868 NS_ENSURE_ARG_POINTER(aFile);
michael@0 2869 nsresult rv;
michael@0 2870 nsCOMPtr<nsIMIMEInfo> info;
michael@0 2871
michael@0 2872 // Get the Extension
michael@0 2873 nsAutoString fileName;
michael@0 2874 rv = aFile->GetLeafName(fileName);
michael@0 2875 if (NS_FAILED(rv)) return rv;
michael@0 2876
michael@0 2877 nsAutoCString fileExt;
michael@0 2878 if (!fileName.IsEmpty())
michael@0 2879 {
michael@0 2880 int32_t len = fileName.Length();
michael@0 2881 for (int32_t i = len; i >= 0; i--)
michael@0 2882 {
michael@0 2883 if (fileName[i] == char16_t('.'))
michael@0 2884 {
michael@0 2885 CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
michael@0 2886 break;
michael@0 2887 }
michael@0 2888 }
michael@0 2889 }
michael@0 2890
michael@0 2891 if (fileExt.IsEmpty())
michael@0 2892 return NS_ERROR_FAILURE;
michael@0 2893
michael@0 2894 return GetTypeFromExtension(fileExt, aContentType);
michael@0 2895 }
michael@0 2896
michael@0 2897 nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
michael@0 2898 const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
michael@0 2899 {
michael@0 2900 NS_ENSURE_ARG( aMIMEInfo );
michael@0 2901
michael@0 2902 NS_ENSURE_ARG( !aContentType.IsEmpty() );
michael@0 2903
michael@0 2904 // Look for default entry with matching mime type.
michael@0 2905 nsAutoCString MIMEType(aContentType);
michael@0 2906 ToLowerCase(MIMEType);
michael@0 2907 int32_t numEntries = ArrayLength(extraMimeEntries);
michael@0 2908 for (int32_t index = 0; index < numEntries; index++)
michael@0 2909 {
michael@0 2910 if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
michael@0 2911 {
michael@0 2912 // This is the one. Set attributes appropriately.
michael@0 2913 aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
michael@0 2914 aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
michael@0 2915 return NS_OK;
michael@0 2916 }
michael@0 2917 }
michael@0 2918
michael@0 2919 return NS_ERROR_NOT_AVAILABLE;
michael@0 2920 }
michael@0 2921
michael@0 2922 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
michael@0 2923 const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
michael@0 2924 {
michael@0 2925 nsAutoCString type;
michael@0 2926 bool found = GetTypeFromExtras(aExtension, type);
michael@0 2927 if (!found)
michael@0 2928 return NS_ERROR_NOT_AVAILABLE;
michael@0 2929 return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
michael@0 2930 }
michael@0 2931
michael@0 2932 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
michael@0 2933 {
michael@0 2934 NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
michael@0 2935
michael@0 2936 // Look for default entry with matching extension.
michael@0 2937 nsDependentCString::const_iterator start, end, iter;
michael@0 2938 int32_t numEntries = ArrayLength(extraMimeEntries);
michael@0 2939 for (int32_t index = 0; index < numEntries; index++)
michael@0 2940 {
michael@0 2941 nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
michael@0 2942 extList.BeginReading(start);
michael@0 2943 extList.EndReading(end);
michael@0 2944 iter = start;
michael@0 2945 while (start != end)
michael@0 2946 {
michael@0 2947 FindCharInReadable(',', iter, end);
michael@0 2948 if (Substring(start, iter).Equals(aExtension,
michael@0 2949 nsCaseInsensitiveCStringComparator()))
michael@0 2950 {
michael@0 2951 aMIMEType = extraMimeEntries[index].mMimeType;
michael@0 2952 return true;
michael@0 2953 }
michael@0 2954 if (iter != end) {
michael@0 2955 ++iter;
michael@0 2956 }
michael@0 2957 start = iter;
michael@0 2958 }
michael@0 2959 }
michael@0 2960
michael@0 2961 return false;
michael@0 2962 }

mercurial