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