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 #include "base/basictypes.h"
9 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Base64.h"
12 #include "mozilla/ResultExtensions.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "mozilla/dom/BrowserChild.h"
16 #include "mozilla/dom/CanonicalBrowsingContext.h"
17 #include "mozilla/dom/WindowGlobalParent.h"
18 #include "mozilla/StaticPrefs_security.h"
19 #include "nsXULAppAPI.h"
21 #include "nsExternalHelperAppService.h"
22 #include "nsCExternalHandlerService.h"
26 #include "nsIFileURL.h"
27 #include "nsIChannel.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "nsICategoryManager.h"
30 #include "nsDependentSubstring.h"
32 #include "nsUnicharUtils.h"
33 #include "nsIStringEnumerator.h"
35 #include "nsIStreamListener.h"
36 #include "nsIMIMEService.h"
37 #include "nsILoadGroup.h"
38 #include "nsIWebProgressListener.h"
39 #include "nsITransfer.h"
40 #include "nsReadableUtils.h"
41 #include "nsIRequest.h"
42 #include "nsDirectoryServiceDefs.h"
43 #include "nsIInterfaceRequestor.h"
44 #include "nsThreadUtils.h"
45 #include "nsIMutableArray.h"
46 #include "nsIRedirectHistoryEntry.h"
47 #include "nsOSHelperAppService.h"
48 #include "nsOSHelperAppServiceChild.h"
49 #include "nsContentSecurityUtils.h"
51 // used to access our datastore of user-configured helper applications
52 #include "nsIHandlerService.h"
53 #include "nsIMIMEInfo.h"
54 #include "nsIHelperAppLauncherDialog.h"
55 #include "nsIContentDispatchChooser.h"
56 #include "nsNetUtil.h"
57 #include "nsIPrivateBrowsingChannel.h"
58 #include "nsIIOService.h"
61 #include "nsIApplicationReputation.h"
63 #include "nsDSURIContentListener.h"
64 #include "nsMimeTypes.h"
65 // used for header disposition information.
66 #include "nsIHttpChannel.h"
67 #include "nsIHttpChannelInternal.h"
68 #include "nsIEncodedChannel.h"
69 #include "nsIMultiPartChannel.h"
70 #include "nsIFileChannel.h"
71 #include "nsIObserverService.h" // so we can be a profile change observer
72 #include "nsIPropertyBag2.h" // for the 64-bit content length
75 # include "nsILocalFileMac.h"
78 #include "nsPluginHost.h"
81 #include "nsIStringBundle.h" // XXX needed to localize error msgs
82 #include "nsIPrompt.h"
84 #include "nsITextToSubURI.h" // to unescape the filename
86 #include "nsDocShellCID.h"
89 #include "nsLocalHandlerApp.h"
91 #include "nsIRandomGenerator.h"
93 #include "ContentChild.h"
94 #include "nsXULAppAPI.h"
95 #include "nsPIDOMWindow.h"
96 #include "ExternalHelperAppChild.h"
99 # include "nsWindowsHelpers.h"
102 #include "mozilla/Components.h"
103 #include "mozilla/ClearOnShutdown.h"
104 #include "mozilla/Preferences.h"
105 #include "mozilla/ipc/URIUtils.h"
107 using namespace mozilla
;
108 using namespace mozilla::ipc
;
109 using namespace mozilla::dom
;
111 // Download Folder location constants
112 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
113 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
115 NS_FOLDER_VALUE_DESKTOP
= 0,
116 NS_FOLDER_VALUE_DOWNLOADS
= 1,
117 NS_FOLDER_VALUE_CUSTOM
= 2
120 LazyLogModule
nsExternalHelperAppService::mLog("HelperAppService");
122 // Using level 3 here because the OSHelperAppServices use a log level
123 // of LogLevel::Debug (4), and we want less detailed output here
124 // Using 3 instead of LogLevel::Warning because we don't output warnings
127 MOZ_LOG(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info, args)
128 #define LOG_ENABLED() \
129 MOZ_LOG_TEST(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info)
131 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF
[] =
132 "browser.helperApps.neverAsk.saveToDisk";
133 static const char NEVER_ASK_FOR_OPEN_FILE_PREF
[] =
134 "browser.helperApps.neverAsk.openFile";
136 // Helper functions for Content-Disposition headers
139 * Given a URI fragment, unescape it
140 * @param aFragment The string to unescape
141 * @param aURI The URI from which this fragment is taken. Only its character set
143 * @param aResult [out] Unescaped string.
145 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
146 nsAString
& aResult
) {
147 // We need the unescaper
149 nsCOMPtr
<nsITextToSubURI
> textToSubURI
=
150 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
151 NS_ENSURE_SUCCESS(rv
, rv
);
153 return textToSubURI
->UnEscapeURIForUI(aFragment
, aResult
);
157 * UTF-8 version of UnescapeFragment.
158 * @param aFragment The string to unescape
159 * @param aURI The URI from which this fragment is taken. Only its character set
161 * @param aResult [out] Unescaped string, UTF-8 encoded.
162 * @note It is safe to pass the same string for aFragment and aResult.
163 * @note When this function fails, aResult will not be modified.
165 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
166 nsACString
& aResult
) {
168 nsresult rv
= UnescapeFragment(aFragment
, aURI
, result
);
169 if (NS_SUCCEEDED(rv
)) CopyUTF16toUTF8(result
, aResult
);
174 * Given a channel, returns the filename and extension the channel has.
175 * This uses the URL and other sources (nsIMultiPartChannel).
176 * Also gives back whether the channel requested external handling (i.e.
177 * whether Content-Disposition: attachment was sent)
178 * @param aChannel The channel to extract the filename/extension from
179 * @param aFileName [out] Reference to the string where the filename should be
180 * stored. Empty if it could not be retrieved.
181 * WARNING - this filename may contain characters which the OS does not
182 * allow as part of filenames!
183 * @param aExtension [out] Reference to the string where the extension should
184 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
185 * @param aAllowURLExtension (optional) Get the extension from the URL if no
186 * Content-Disposition header is present. Default is true.
187 * @retval true The server sent Content-Disposition:attachment or equivalent
188 * @retval false Content-Disposition: inline or no content-disposition header
191 static bool GetFilenameAndExtensionFromChannel(nsIChannel
* aChannel
,
193 nsCString
& aExtension
,
194 bool aAllowURLExtension
= true) {
195 aExtension
.Truncate();
197 * If the channel is an http or part of a multipart channel and we
198 * have a content disposition header set, then use the file name
199 * suggested there as the preferred file name to SUGGEST to the
200 * user. we shouldn't actually use that without their
201 * permission... otherwise just use our temp file
203 bool handleExternally
= false;
205 nsresult rv
= aChannel
->GetContentDisposition(&disp
);
206 bool gotFileNameFromURI
= false;
207 if (NS_SUCCEEDED(rv
)) {
208 aChannel
->GetContentDispositionFilename(aFileName
);
209 if (disp
== nsIChannel::DISPOSITION_ATTACHMENT
) handleExternally
= true;
212 // If the disposition header didn't work, try the filename from nsIURL
213 nsCOMPtr
<nsIURI
> uri
;
214 aChannel
->GetURI(getter_AddRefs(uri
));
215 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
216 if (url
&& aFileName
.IsEmpty()) {
217 if (aAllowURLExtension
) {
218 url
->GetFileExtension(aExtension
);
219 UnescapeFragment(aExtension
, url
, aExtension
);
221 // Windows ignores terminating dots. So we have to as well, so
222 // that our security checks do "the right thing"
223 // In case the aExtension consisted only of the dot, the code below will
224 // extract an aExtension from the filename
225 aExtension
.Trim(".", false);
228 // try to extract the file name from the url and use that as a first pass as
229 // the leaf name of our temp file...
230 nsAutoCString leafName
;
231 url
->GetFileName(leafName
);
232 if (!leafName
.IsEmpty()) {
233 gotFileNameFromURI
= true;
234 rv
= UnescapeFragment(leafName
, url
, aFileName
);
236 CopyUTF8toUTF16(leafName
, aFileName
); // use escaped name
241 // If we have a filename and no extension, remove trailing dots from the
242 // filename and extract the extension if that is possible.
243 if (aExtension
.IsEmpty() && !aFileName
.IsEmpty()) {
244 // Windows ignores terminating dots. So we have to as well, so
245 // that our security checks do "the right thing"
246 aFileName
.Trim(".", false);
247 // We can get an extension if the filename is from a header, or if getting
248 // it from the URL was allowed.
249 bool canGetExtensionFromFilename
=
250 !gotFileNameFromURI
|| aAllowURLExtension
;
251 // ... , or if the mimetype is meaningless and we have nothing to go on:
252 if (!canGetExtensionFromFilename
) {
253 nsAutoCString contentType
;
254 if (NS_SUCCEEDED(aChannel
->GetContentType(contentType
))) {
255 canGetExtensionFromFilename
=
256 contentType
.EqualsIgnoreCase(APPLICATION_OCTET_STREAM
) ||
257 contentType
.EqualsIgnoreCase("binary/octet-stream") ||
258 contentType
.EqualsIgnoreCase("application/x-msdownload");
262 if (canGetExtensionFromFilename
) {
263 // XXX RFindCharInReadable!!
264 nsAutoString
fileNameStr(aFileName
);
265 int32_t idx
= fileNameStr
.RFindChar(char16_t('.'));
266 if (idx
!= kNotFound
)
267 CopyUTF16toUTF8(StringTail(fileNameStr
, fileNameStr
.Length() - idx
- 1),
272 return handleExternally
;
276 * Obtains the directory to use. This tends to vary per platform, and
277 * needs to be consistent throughout our codepaths. For platforms where
278 * helper apps use the downloads directory, this should be kept in
279 * sync with DownloadIntegration.jsm.
281 * Optionally skip availability of the directory and storage.
283 static nsresult
GetDownloadDirectory(nsIFile
** _directory
,
284 bool aSkipChecks
= false) {
285 nsCOMPtr
<nsIFile
> dir
;
287 // On OS X, we first try to get the users download location, if it's set.
288 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST
, -1)) {
289 case NS_FOLDER_VALUE_DESKTOP
:
290 (void)NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR
, getter_AddRefs(dir
));
292 case NS_FOLDER_VALUE_CUSTOM
: {
293 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR
, NS_GET_IID(nsIFile
),
294 getter_AddRefs(dir
));
297 // If we're not checking for availability we're done.
299 dir
.forget(_directory
);
303 // We have the directory, and now we need to make sure it exists
304 bool dirExists
= false;
305 (void)dir
->Exists(&dirExists
);
306 if (dirExists
) break;
308 nsresult rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
314 case NS_FOLDER_VALUE_DOWNLOADS
:
315 // This is just the OS default location, so fall out
320 // If not, we default to the OS X default download location.
321 nsresult rv
= NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR
,
322 getter_AddRefs(dir
));
323 NS_ENSURE_SUCCESS(rv
, rv
);
325 #elif defined(ANDROID)
326 return NS_ERROR_FAILURE
;
328 // On all other platforms, we default to the systems temporary directory.
329 nsresult rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dir
));
330 NS_ENSURE_SUCCESS(rv
, rv
);
332 # if defined(XP_UNIX)
333 // Ensuring that only the current user can read the file names we end up
334 // creating. Note that Creating directories with specified permission only
335 // supported on Unix platform right now. That's why above if exists.
337 uint32_t permissions
;
338 rv
= dir
->GetPermissions(&permissions
);
339 NS_ENSURE_SUCCESS(rv
, rv
);
341 if (permissions
!= PR_IRWXU
) {
342 const char* userName
= PR_GetEnv("USERNAME");
343 if (!userName
|| !*userName
) {
344 userName
= PR_GetEnv("USER");
346 if (!userName
|| !*userName
) {
347 userName
= PR_GetEnv("LOGNAME");
349 if (!userName
|| !*userName
) {
350 userName
= "mozillaUser";
353 nsAutoString userDir
;
354 userDir
.AssignLiteral("mozilla_");
355 userDir
.AppendASCII(userName
);
356 userDir
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
360 nsCOMPtr
<nsIFile
> finalPath
;
363 nsAutoString
countedUserDir(userDir
);
364 countedUserDir
.AppendInt(counter
, 10);
365 dir
->Clone(getter_AddRefs(finalPath
));
366 finalPath
->Append(countedUserDir
);
368 rv
= finalPath
->Exists(&pathExists
);
369 NS_ENSURE_SUCCESS(rv
, rv
);
372 // If this path has the right permissions, use it.
373 rv
= finalPath
->GetPermissions(&permissions
);
374 NS_ENSURE_SUCCESS(rv
, rv
);
376 // Ensuring the path is writable by the current user.
378 rv
= finalPath
->IsWritable(&isWritable
);
379 NS_ENSURE_SUCCESS(rv
, rv
);
381 if (permissions
== PR_IRWXU
&& isWritable
) {
387 rv
= finalPath
->Create(nsIFile::DIRECTORY_TYPE
, PR_IRWXU
);
388 if (NS_SUCCEEDED(rv
)) {
391 } else if (rv
!= NS_ERROR_FILE_ALREADY_EXISTS
) {
403 NS_ASSERTION(dir
, "Somehow we didn't get a download directory!");
404 dir
.forget(_directory
);
409 * Structure for storing extension->type mappings.
410 * @see defaultMimeEntries
412 struct nsDefaultMimeTypeEntry
{
413 const char* mMimeType
;
414 const char* mFileExtension
;
418 * Default extension->mimetype mappings. These are not overridable.
419 * If you add types here, make sure they are lowercase, or you'll regret it.
421 static const nsDefaultMimeTypeEntry defaultMimeEntries
[] = {
422 // The following are those extensions that we're asked about during startup,
423 // sorted by order used
426 {APPLICATION_RDF
, "rdf"},
428 // -- end extensions used during startup
430 {IMAGE_JPEG
, "jpeg"},
432 {IMAGE_SVG_XML
, "svg"},
435 {APPLICATION_XPINSTALL
, "xpi"},
436 {"application/xhtml+xml", "xhtml"},
437 {"application/xhtml+xml", "xht"},
439 {APPLICATION_JSON
, "json"},
440 {APPLICATION_XJAVASCRIPT
, "js"},
441 {APPLICATION_XJAVASCRIPT
, "jsm"},
444 {APPLICATION_OGG
, "ogg"},
447 {APPLICATION_PDF
, "pdf"},
448 {VIDEO_WEBM
, "webm"},
449 {AUDIO_WEBM
, "webm"},
451 {TEXT_PLAIN
, "properties"},
452 {TEXT_PLAIN
, "locale"},
465 * This is a small private struct used to help us initialize some
466 * default mime types.
468 struct nsExtraMimeTypeEntry
{
469 const char* mMimeType
;
470 const char* mFileExtensions
;
471 const char* mDescription
;
475 * This table lists all of the 'extra' content types that we can deduce from
476 * particular file extensions. These entries also ensure that we provide a good
477 * descriptive name when we encounter files with these content types and/or
478 * extensions. These can be overridden by user helper app prefs. If you add
479 * types here, make sure they are lowercase, or you'll regret it.
481 static const nsExtraMimeTypeEntry extraMimeEntries
[] = {
482 #if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to
484 {APPLICATION_OCTET_STREAM
, "exe,com", "Binary File"},
486 {APPLICATION_OCTET_STREAM
, "exe,com,bin", "Binary File"},
488 {APPLICATION_GZIP2
, "gz", "gzip"},
489 {"application/x-arj", "arj", "ARJ file"},
490 {"application/rtf", "rtf", "Rich Text Format File"},
491 {APPLICATION_ZIP
, "zip", "ZIP Archive"},
492 {APPLICATION_XPINSTALL
, "xpi", "XPInstall Install"},
493 {APPLICATION_PDF
, "pdf", "Portable Document Format"},
494 {APPLICATION_POSTSCRIPT
, "ps,eps,ai", "Postscript File"},
495 {APPLICATION_XJAVASCRIPT
, "js", "Javascript Source File"},
496 {APPLICATION_XJAVASCRIPT
, "jsm,mjs", "Javascript Module Source File"},
497 #ifdef MOZ_WIDGET_ANDROID
498 {"application/vnd.android.package-archive", "apk", "Android Package"},
501 // OpenDocument formats
502 {"application/vnd.oasis.opendocument.text", "odt", "OpenDocument Text"},
503 {"application/vnd.oasis.opendocument.presentation", "odp",
504 "OpenDocument Presentation"},
505 {"application/vnd.oasis.opendocument.spreadsheet", "ods",
506 "OpenDocument Spreadsheet"},
507 {"application/vnd.oasis.opendocument.graphics", "odg",
508 "OpenDocument Graphics"},
510 // Legacy Microsoft Office
511 {"application/msword", "doc", "Microsoft Word"},
512 {"application/vnd.ms-powerpoint", "ppt", "Microsoft PowerPoint"},
513 {"application/vnd.ms-excel", "xls", "Microsoft Excel"},
516 {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
517 "docx", "Microsoft Word (Open XML)"},
519 "vnd.openxmlformats-officedocument.presentationml.presentation",
520 "pptx", "Microsoft PowerPoint (Open XML)"},
521 {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
522 "xlsx", "Microsoft Excel (Open XML)"},
524 // Note: if you add new image types, please also update the list in
525 // contentAreaUtils.js to match.
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
, "jpg,jpeg,jfif,pjpeg,pjp", "JPEG Image"},
531 {IMAGE_PNG
, "png", "PNG Image"},
532 {IMAGE_APNG
, "apng", "APNG Image"},
533 {IMAGE_TIFF
, "tiff,tif", "TIFF Image"},
534 {IMAGE_XBM
, "xbm", "XBM Image"},
535 {IMAGE_SVG_XML
, "svg", "Scalable Vector Graphics"},
536 {IMAGE_WEBP
, "webp", "WebP Image"},
537 {IMAGE_AVIF
, "avif", "AV1 Image File"},
538 {IMAGE_JXL
, "jxl", "JPEG XL Image File"},
540 {MESSAGE_RFC822
, "eml", "RFC-822 data"},
541 {TEXT_PLAIN
, "txt,text", "Text File"},
542 {APPLICATION_JSON
, "json", "JavaScript Object Notation"},
543 {TEXT_VTT
, "vtt", "Web Video Text Tracks"},
544 {TEXT_CACHE_MANIFEST
, "appcache", "Application Cache Manifest"},
545 {TEXT_HTML
, "html,htm,shtml,ehtml", "HyperText Markup Language"},
546 {"application/xhtml+xml", "xhtml,xht",
547 "Extensible HyperText Markup Language"},
548 {APPLICATION_MATHML_XML
, "mml", "Mathematical Markup Language"},
549 {APPLICATION_RDF
, "rdf", "Resource Description Framework"},
550 {"text/csv", "csv", "CSV File"},
551 {TEXT_XML
, "xml,xsl,xbl", "Extensible Markup Language"},
552 {TEXT_CSS
, "css", "Style Sheet"},
553 {TEXT_VCARD
, "vcf,vcard", "Contact Information"},
554 {TEXT_CALENDAR
, "ics,ical,ifb,icalendar", "iCalendar"},
555 {VIDEO_OGG
, "ogv", "Ogg Video"},
556 {VIDEO_OGG
, "ogg", "Ogg Video"},
557 {APPLICATION_OGG
, "ogg", "Ogg Video"},
558 {AUDIO_OGG
, "oga", "Ogg Audio"},
559 {AUDIO_OGG
, "opus", "Opus Audio"},
560 {VIDEO_WEBM
, "webm", "Web Media Video"},
561 {AUDIO_WEBM
, "webm", "Web Media Audio"},
562 {AUDIO_MP3
, "mp3", "MPEG Audio"},
563 {VIDEO_MP4
, "mp4", "MPEG-4 Video"},
564 {AUDIO_MP4
, "m4a", "MPEG-4 Audio"},
565 {VIDEO_RAW
, "yuv", "Raw YUV Video"},
566 {AUDIO_WAV
, "wav", "Waveform Audio"},
567 {VIDEO_3GPP
, "3gpp,3gp", "3GPP Video"},
568 {VIDEO_3GPP2
, "3g2", "3GPP2 Video"},
569 {AUDIO_AAC
, "aac", "AAC Audio"},
570 {AUDIO_FLAC
, "flac", "FLAC Audio"},
571 {AUDIO_MIDI
, "mid", "Standard MIDI Audio"},
572 {APPLICATION_WASM
, "wasm", "WebAssembly Module"}};
574 static const nsDefaultMimeTypeEntry sForbiddenPrimaryExtensions
[] = {
575 {IMAGE_JPEG
, "jfif"}};
578 * File extensions for which decoding should be disabled.
579 * NOTE: These MUST be lower-case and ASCII.
581 static const nsDefaultMimeTypeEntry nonDecodableExtensions
[] = {
582 {APPLICATION_GZIP
, "gz"},
583 {APPLICATION_GZIP
, "tgz"},
584 {APPLICATION_ZIP
, "zip"},
585 {APPLICATION_COMPRESS
, "z"},
586 {APPLICATION_GZIP
, "svgz"}};
589 * Mimetypes for which we enforce using a known extension.
591 * In addition to this list, we do this for all audio/, video/ and
594 static const char* forcedExtensionMimetypes
[] = {
595 // Note: zip and json mimetypes are commonly used with a variety of
596 // extensions; don't add them here. It's a similar story for text/xml,
597 // but slightly worse because we can use it when sniffing for a mimetype
598 // if one hasn't been provided, so don't re-add that here either.
599 APPLICATION_PDF
, APPLICATION_OGG
, APPLICATION_WASM
,
600 TEXT_CALENDAR
, TEXT_CSS
, TEXT_VCARD
};
603 * Primary extensions of types whose descriptions should be overwritten.
604 * This extension is concatenated with "ExtHandlerDescription" to look up the
605 * description in unknownContentType.properties.
606 * NOTE: These MUST be lower-case and ASCII.
608 static const char* descriptionOverwriteExtensions
[] = {
609 "avif", "jxl", "pdf", "svg", "webp", "xml",
612 static StaticRefPtr
<nsExternalHelperAppService
> sExtHelperAppSvcSingleton
;
615 * On Mac child processes, return an nsOSHelperAppServiceChild for remoting
616 * OS calls to the parent process. On all other platforms use
617 * nsOSHelperAppService.
620 already_AddRefed
<nsExternalHelperAppService
>
621 nsExternalHelperAppService::GetSingleton() {
622 if (!sExtHelperAppSvcSingleton
) {
624 if (XRE_IsParentProcess()) {
625 sExtHelperAppSvcSingleton
= new nsOSHelperAppService();
627 sExtHelperAppSvcSingleton
= new nsOSHelperAppServiceChild();
630 sExtHelperAppSvcSingleton
= new nsOSHelperAppService();
631 #endif /* XP_MACOSX */
632 ClearOnShutdown(&sExtHelperAppSvcSingleton
);
635 return do_AddRef(sExtHelperAppSvcSingleton
);
638 NS_IMPL_ISUPPORTS(nsExternalHelperAppService
, nsIExternalHelperAppService
,
639 nsPIExternalAppLauncher
, nsIExternalProtocolService
,
640 nsIMIMEService
, nsIObserver
, nsISupportsWeakReference
)
642 nsExternalHelperAppService::nsExternalHelperAppService() {}
643 nsresult
nsExternalHelperAppService::Init() {
644 // Add an observer for profile change
645 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
646 if (!obs
) return NS_ERROR_FAILURE
;
648 nsresult rv
= obs
->AddObserver(this, "profile-before-change", true);
649 NS_ENSURE_SUCCESS(rv
, rv
);
650 return obs
->AddObserver(this, "last-pb-context-exited", true);
653 nsExternalHelperAppService::~nsExternalHelperAppService() {}
655 nsresult
nsExternalHelperAppService::DoContentContentProcessHelper(
656 const nsACString
& aMimeContentType
, nsIRequest
* aRequest
,
657 BrowsingContext
* aContentContext
, bool aForceSave
,
658 nsIInterfaceRequestor
* aWindowContext
,
659 nsIStreamListener
** aStreamListener
) {
660 // We need to get a hold of a ContentChild so that we can begin forwarding
661 // this data to the parent. In the HTTP case, this is unfortunate, since
662 // we're actually passing data from parent->child->parent wastefully, but
663 // the Right Fix will eventually be to short-circuit those channels on the
664 // parent side based on some sort of subscription concept.
665 using mozilla::dom::ContentChild
;
666 using mozilla::dom::ExternalHelperAppChild
;
667 ContentChild
* child
= ContentChild::GetSingleton();
669 return NS_ERROR_FAILURE
;
673 nsCOMPtr
<nsIURI
> uri
;
674 int64_t contentLength
= -1;
675 bool wasFileChannel
= false;
676 uint32_t contentDisposition
= -1;
677 nsAutoString fileName
;
678 nsCOMPtr
<nsILoadInfo
> loadInfo
;
680 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
682 channel
->GetURI(getter_AddRefs(uri
));
683 channel
->GetContentLength(&contentLength
);
684 channel
->GetContentDisposition(&contentDisposition
);
685 channel
->GetContentDispositionFilename(fileName
);
686 channel
->GetContentDispositionHeader(disp
);
687 loadInfo
= channel
->LoadInfo();
689 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(aRequest
));
690 wasFileChannel
= fileChan
!= nullptr;
693 nsCOMPtr
<nsIURI
> referrer
;
694 NS_GetReferrerFromChannel(channel
, getter_AddRefs(referrer
));
696 Maybe
<mozilla::net::LoadInfoArgs
> loadInfoArgs
;
697 MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo
, &loadInfoArgs
));
699 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(aRequest
));
700 // Determine whether a new window was opened specifically for this request
701 bool shouldCloseWindow
= false;
703 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
,
707 // Now we build a protocol for forwarding our data to the parent. The
708 // protocol will act as a listener on the child-side and create a "real"
709 // helperAppService listener on the parent-side, via another call to
711 RefPtr
<ExternalHelperAppChild
> childListener
= new ExternalHelperAppChild();
712 MOZ_ALWAYS_TRUE(child
->SendPExternalHelperAppConstructor(
713 childListener
, uri
, loadInfoArgs
, nsCString(aMimeContentType
), disp
,
714 contentDisposition
, fileName
, aForceSave
, contentLength
, wasFileChannel
,
715 referrer
, aContentContext
, shouldCloseWindow
));
717 NS_ADDREF(*aStreamListener
= childListener
);
719 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
721 RefPtr
<nsExternalAppHandler
> handler
=
722 new nsExternalAppHandler(nullptr, ""_ns
, aContentContext
, aWindowContext
,
723 this, fileName
, reason
, aForceSave
);
725 return NS_ERROR_OUT_OF_MEMORY
;
728 childListener
->SetHandler(handler
);
732 NS_IMETHODIMP
nsExternalHelperAppService::CreateListener(
733 const nsACString
& aMimeContentType
, nsIRequest
* aRequest
,
734 BrowsingContext
* aContentContext
, bool aForceSave
,
735 nsIInterfaceRequestor
* aWindowContext
,
736 nsIStreamListener
** aStreamListener
) {
737 MOZ_ASSERT(!XRE_IsContentProcess());
739 nsAutoString fileName
;
740 nsAutoCString fileExtension
;
741 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
742 uint32_t contentDisposition
= -1;
744 // Get the file extension and name that we will need later
745 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
746 nsCOMPtr
<nsIURI
> uri
;
747 int64_t contentLength
= -1;
749 channel
->GetURI(getter_AddRefs(uri
));
750 channel
->GetContentLength(&contentLength
);
751 channel
->GetContentDisposition(&contentDisposition
);
752 channel
->GetContentDispositionFilename(fileName
);
754 // Check if we have a POST request, in which case we don't want to use
755 // the url's extension
756 bool allowURLExt
= !net::ChannelIsPost(channel
);
758 // Check if we had a query string - we don't want to check the URL
759 // extension if a query is present in the URI
760 // If we already know we don't want to check the URL extension, don't
761 // bother checking the query
762 if (uri
&& allowURLExt
) {
763 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(uri
);
768 // We only care about the query for HTTP and HTTPS URLs
769 if (uri
->SchemeIs("http") || uri
->SchemeIs("https")) {
770 url
->GetQuery(query
);
773 // Only get the extension if the query is empty; if it isn't, then the
774 // extension likely belongs to a cgi script and isn't helpful
775 allowURLExt
= query
.IsEmpty();
778 // Extract name & extension
779 bool isAttachment
= GetFilenameAndExtensionFromChannel(
780 channel
, fileName
, fileExtension
, allowURLExt
);
781 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
782 fileExtension
.get(), NS_ConvertUTF16toUTF8(fileName
).get(),
785 reason
= nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
;
789 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
790 PromiseFlatCString(aMimeContentType
).get(), fileExtension
.get()));
792 // We get the mime service here even though we're the default implementation
793 // of it, so it's possible to override only the mime service and not need to
794 // reimplement the whole external helper app service itself.
795 nsCOMPtr
<nsIMIMEService
> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID
));
796 NS_ENSURE_TRUE(mimeSvc
, NS_ERROR_FAILURE
);
798 // Try to find a mime object by looking at the mime type/extension
799 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
800 if (aMimeContentType
.Equals(APPLICATION_GUESS_FROM_EXT
,
801 nsCaseInsensitiveCStringComparator
)) {
802 nsAutoCString mimeType
;
803 if (!fileExtension
.IsEmpty()) {
804 mimeSvc
->GetFromTypeAndExtension(""_ns
, fileExtension
,
805 getter_AddRefs(mimeInfo
));
807 mimeInfo
->GetMIMEType(mimeType
);
809 LOG(("OS-Provided mime type '%s' for extension '%s'\n", mimeType
.get(),
810 fileExtension
.get()));
814 if (fileExtension
.IsEmpty() || mimeType
.IsEmpty()) {
815 // Extension lookup gave us no useful match
816 mimeSvc
->GetFromTypeAndExtension(
817 nsLiteralCString(APPLICATION_OCTET_STREAM
), fileExtension
,
818 getter_AddRefs(mimeInfo
));
819 mimeType
.AssignLiteral(APPLICATION_OCTET_STREAM
);
823 channel
->SetContentType(mimeType
);
826 // Don't overwrite SERVERREQUEST
827 if (reason
== nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
828 reason
= nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
831 mimeSvc
->GetFromTypeAndExtension(aMimeContentType
, fileExtension
,
832 getter_AddRefs(mimeInfo
));
834 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo
.get()));
836 // No mimeinfo -> we can't continue. probably OOM.
838 return NS_ERROR_OUT_OF_MEMORY
;
841 *aStreamListener
= nullptr;
842 // We want the mimeInfo's primary extension to pass it to
843 // nsExternalAppHandler
845 mimeInfo
->GetPrimaryExtension(buf
);
847 // NB: ExternalHelperAppParent depends on this listener always being an
848 // nsExternalAppHandler. If this changes, make sure to update that code.
849 nsExternalAppHandler
* handler
=
850 new nsExternalAppHandler(mimeInfo
, buf
, aContentContext
, aWindowContext
,
851 this, fileName
, reason
, aForceSave
);
853 return NS_ERROR_OUT_OF_MEMORY
;
856 NS_ADDREF(*aStreamListener
= handler
);
860 NS_IMETHODIMP
nsExternalHelperAppService::DoContent(
861 const nsACString
& aMimeContentType
, nsIRequest
* aRequest
,
862 nsIInterfaceRequestor
* aContentContext
, bool aForceSave
,
863 nsIInterfaceRequestor
* aWindowContext
,
864 nsIStreamListener
** aStreamListener
) {
865 // Scripted interface requestors cannot return an instance of the
866 // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
867 // get to the window via `nsIDOMWindow`. Unfortunately, at that point we
868 // don't know whether the thing we got is an inner or outer window, so have to
869 // work with either one.
870 RefPtr
<BrowsingContext
> bc
;
871 nsCOMPtr
<nsIDOMWindow
> domWindow
= do_GetInterface(aContentContext
);
872 if (nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= do_QueryInterface(domWindow
)) {
873 bc
= outerWindow
->GetBrowsingContext();
874 } else if (nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
875 do_QueryInterface(domWindow
)) {
876 bc
= innerWindow
->GetBrowsingContext();
879 if (XRE_IsContentProcess()) {
880 return DoContentContentProcessHelper(aMimeContentType
, aRequest
, bc
,
881 aForceSave
, aWindowContext
,
885 nsresult rv
= CreateListener(aMimeContentType
, aRequest
, bc
, aForceSave
,
886 aWindowContext
, aStreamListener
);
890 NS_IMETHODIMP
nsExternalHelperAppService::ApplyDecodingForExtension(
891 const nsACString
& aExtension
, const nsACString
& aEncodingType
,
892 bool* aApplyDecoding
) {
893 *aApplyDecoding
= true;
895 for (i
= 0; i
< ArrayLength(nonDecodableExtensions
); ++i
) {
896 if (aExtension
.LowerCaseEqualsASCII(
897 nonDecodableExtensions
[i
].mFileExtension
) &&
898 aEncodingType
.LowerCaseEqualsASCII(
899 nonDecodableExtensions
[i
].mMimeType
)) {
900 *aApplyDecoding
= false;
907 nsresult
nsExternalHelperAppService::GetFileTokenForPath(
908 const char16_t
* aPlatformAppPath
, nsIFile
** aFile
) {
909 nsDependentString
platformAppPath(aPlatformAppPath
);
910 // First, check if we have an absolute path
911 nsIFile
* localFile
= nullptr;
912 nsresult rv
= NS_NewLocalFile(platformAppPath
, true, &localFile
);
913 if (NS_SUCCEEDED(rv
)) {
916 if (NS_FAILED((*aFile
)->Exists(&exists
)) || !exists
) {
918 return NS_ERROR_FILE_NOT_FOUND
;
923 // Second, check if file exists in mozilla program directory
924 rv
= NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR
, aFile
);
925 if (NS_SUCCEEDED(rv
)) {
926 rv
= (*aFile
)->Append(platformAppPath
);
927 if (NS_SUCCEEDED(rv
)) {
929 rv
= (*aFile
)->Exists(&exists
);
930 if (NS_SUCCEEDED(rv
) && exists
) return NS_OK
;
935 return NS_ERROR_NOT_AVAILABLE
;
938 //////////////////////////////////////////////////////////////////////////////////////////////////////
939 // begin external protocol service default implementation...
940 //////////////////////////////////////////////////////////////////////////////////////////////////////
941 NS_IMETHODIMP
nsExternalHelperAppService::ExternalProtocolHandlerExists(
942 const char* aProtocolScheme
, bool* aHandlerExists
) {
943 nsCOMPtr
<nsIHandlerInfo
> handlerInfo
;
944 nsresult rv
= GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme
),
945 getter_AddRefs(handlerInfo
));
946 if (NS_SUCCEEDED(rv
)) {
947 // See if we have any known possible handler apps for this
948 nsCOMPtr
<nsIMutableArray
> possibleHandlers
;
949 handlerInfo
->GetPossibleApplicationHandlers(
950 getter_AddRefs(possibleHandlers
));
953 possibleHandlers
->GetLength(&length
);
955 *aHandlerExists
= true;
960 // if not, fall back on an os-based handler
961 return OSProtocolHandlerExists(aProtocolScheme
, aHandlerExists
);
964 NS_IMETHODIMP
nsExternalHelperAppService::IsExposedProtocol(
965 const char* aProtocolScheme
, bool* aResult
) {
966 // check the per protocol setting first. it always takes precedence.
967 // if not set, then use the global setting.
969 nsAutoCString
prefName("network.protocol-handler.expose.");
970 prefName
+= aProtocolScheme
;
972 if (NS_SUCCEEDED(Preferences::GetBool(prefName
.get(), &val
))) {
977 // by default, no protocol is exposed. i.e., by default all link clicks must
978 // go through the external protocol service. most applications override this
980 *aResult
= Preferences::GetBool("network.protocol-handler.expose-all", false);
985 static const char kExternalProtocolPrefPrefix
[] =
986 "network.protocol-handler.external.";
987 static const char kExternalProtocolDefaultPref
[] =
988 "network.protocol-handler.external-default";
991 nsExternalHelperAppService::LoadURI(nsIURI
* aURI
,
992 nsIPrincipal
* aTriggeringPrincipal
,
993 BrowsingContext
* aBrowsingContext
,
994 bool aTriggeredExternally
) {
995 NS_ENSURE_ARG_POINTER(aURI
);
997 if (XRE_IsContentProcess()) {
998 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
999 aURI
, aTriggeringPrincipal
, aBrowsingContext
, aTriggeredExternally
);
1004 aURI
->GetSpec(spec
);
1006 if (spec
.Find("%00") != -1) return NS_ERROR_MALFORMED_URI
;
1008 spec
.ReplaceSubstring("\"", "%22");
1009 spec
.ReplaceSubstring("`", "%60");
1011 nsCOMPtr
<nsIIOService
> ios(do_GetIOService());
1012 nsCOMPtr
<nsIURI
> uri
;
1013 nsresult rv
= ios
->NewURI(spec
, nullptr, nullptr, getter_AddRefs(uri
));
1014 NS_ENSURE_SUCCESS(rv
, rv
);
1016 nsAutoCString scheme
;
1017 uri
->GetScheme(scheme
);
1018 if (scheme
.IsEmpty()) return NS_OK
; // must have a scheme
1020 // Deny load if the prefs say to do so
1021 nsAutoCString
externalPref(kExternalProtocolPrefPrefix
);
1022 externalPref
+= scheme
;
1023 bool allowLoad
= false;
1024 if (NS_FAILED(Preferences::GetBool(externalPref
.get(), &allowLoad
))) {
1025 // no scheme-specific value, check the default
1027 Preferences::GetBool(kExternalProtocolDefaultPref
, &allowLoad
))) {
1028 return NS_OK
; // missing default pref
1033 return NS_OK
; // explicitly denied
1036 // Now check if the principal is allowed to access the navigated context.
1037 // We allow navigating subframes, even if not same-origin - non-external
1038 // links can always navigate everywhere, so this is a minor additional
1039 // restriction, only aiming to prevent some types of spoofing attacks
1040 // from otherwise disjoint browsingcontext trees.
1041 if (aBrowsingContext
&& aTriggeringPrincipal
&&
1042 !StaticPrefs::security_allow_disjointed_external_uri_loads() &&
1043 // Add-on principals are always allowed:
1044 !BasePrincipal::Cast(aTriggeringPrincipal
)->AddonPolicy() &&
1045 // As is chrome code:
1046 !aTriggeringPrincipal
->IsSystemPrincipal()) {
1047 RefPtr
<BrowsingContext
> bc
= aBrowsingContext
;
1048 WindowGlobalParent
* wgp
= bc
->Canonical()->GetCurrentWindowGlobal();
1049 bool foundAccessibleFrame
= false;
1051 // Also allow this load if the target is a toplevel BC and contains a
1052 // non-web-controlled about:blank document
1053 if (bc
->IsTop() && !bc
->HadOriginalOpener() && wgp
) {
1054 RefPtr
<nsIURI
> uri
= wgp
->GetDocumentURI();
1055 foundAccessibleFrame
=
1056 uri
&& uri
->GetSpecOrDefault().EqualsLiteral("about:blank");
1059 while (!foundAccessibleFrame
) {
1061 foundAccessibleFrame
=
1062 aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal());
1064 // We have to get the parent via the bc, because there may not
1065 // be a window global for the innermost bc; see bug 1650162.
1066 BrowsingContext
* parent
= bc
->GetParent();
1071 wgp
= parent
->Canonical()->GetCurrentWindowGlobal();
1074 if (!foundAccessibleFrame
) {
1075 // See if this navigation could have come from a subframe.
1076 nsTArray
<RefPtr
<BrowsingContext
>> contexts
;
1077 aBrowsingContext
->GetAllBrowsingContextsInSubtree(contexts
);
1078 for (const auto& kid
: contexts
) {
1079 wgp
= kid
->Canonical()->GetCurrentWindowGlobal();
1080 if (wgp
&& aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal())) {
1081 foundAccessibleFrame
= true;
1087 if (!foundAccessibleFrame
) {
1088 return NS_OK
; // deny the load.
1092 nsCOMPtr
<nsIHandlerInfo
> handler
;
1093 rv
= GetProtocolHandlerInfo(scheme
, getter_AddRefs(handler
));
1094 NS_ENSURE_SUCCESS(rv
, rv
);
1096 nsCOMPtr
<nsIContentDispatchChooser
> chooser
=
1097 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv
);
1098 NS_ENSURE_SUCCESS(rv
, rv
);
1100 return chooser
->HandleURI(handler
, uri
, aTriggeringPrincipal
,
1101 aBrowsingContext
, aTriggeredExternally
);
1104 //////////////////////////////////////////////////////////////////////////////////////////////////////
1105 // Methods related to deleting temporary files on exit
1106 //////////////////////////////////////////////////////////////////////////////////////////////////////
1109 nsresult
nsExternalHelperAppService::DeleteTemporaryFileHelper(
1110 nsIFile
* aTemporaryFile
, nsCOMArray
<nsIFile
>& aFileList
) {
1111 bool isFile
= false;
1113 // as a safety measure, make sure the nsIFile is really a file and not a
1114 // directory object.
1115 aTemporaryFile
->IsFile(&isFile
);
1116 if (!isFile
) return NS_OK
;
1118 aFileList
.AppendObject(aTemporaryFile
);
1124 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile
* aTemporaryFile
) {
1125 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryFilesList
);
1129 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1130 nsIFile
* aTemporaryFile
) {
1131 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryPrivateFilesList
);
1134 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1135 nsCOMArray
<nsIFile
>& fileList
) {
1136 int32_t numEntries
= fileList
.Count();
1138 for (int32_t index
= 0; index
< numEntries
; index
++) {
1139 localFile
= fileList
[index
];
1141 // First make the file writable, since the temp file is probably readonly.
1142 localFile
->SetPermissions(0600);
1143 localFile
->Remove(false);
1150 void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1151 ExpungeTemporaryFilesHelper(mTemporaryFilesList
);
1154 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1155 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList
);
1158 static const char kExternalWarningPrefPrefix
[] =
1159 "network.protocol-handler.warn-external.";
1160 static const char kExternalWarningDefaultPref
[] =
1161 "network.protocol-handler.warn-external-default";
1164 nsExternalHelperAppService::GetProtocolHandlerInfo(
1165 const nsACString
& aScheme
, nsIHandlerInfo
** aHandlerInfo
) {
1166 // XXX enterprise customers should be able to turn this support off with a
1167 // single master pref (maybe use one of the "exposed" prefs here?)
1170 nsresult rv
= GetProtocolHandlerInfoFromOS(aScheme
, &exists
, aHandlerInfo
);
1171 if (NS_FAILED(rv
)) {
1172 // Either it knows nothing, or we ran out of memory
1173 return NS_ERROR_FAILURE
;
1176 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1177 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1179 bool hasHandler
= false;
1180 (void)handlerSvc
->Exists(*aHandlerInfo
, &hasHandler
);
1182 rv
= handlerSvc
->FillHandlerInfo(*aHandlerInfo
, ""_ns
);
1183 if (NS_SUCCEEDED(rv
)) return NS_OK
;
1187 return SetProtocolHandlerDefaults(*aHandlerInfo
, exists
);
1191 nsExternalHelperAppService::SetProtocolHandlerDefaults(
1192 nsIHandlerInfo
* aHandlerInfo
, bool aOSHandlerExists
) {
1193 // this type isn't in our database, so we've only got an OS default handler,
1196 if (aOSHandlerExists
) {
1197 // we've got a default, so use it
1198 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::useSystemDefault
);
1200 // whether or not to ask the user depends on the warning preference
1201 nsAutoCString scheme
;
1202 aHandlerInfo
->GetType(scheme
);
1204 nsAutoCString
warningPref(kExternalWarningPrefPrefix
);
1205 warningPref
+= scheme
;
1207 if (NS_FAILED(Preferences::GetBool(warningPref
.get(), &warn
))) {
1208 // no scheme-specific value, check the default
1209 warn
= Preferences::GetBool(kExternalWarningDefaultPref
, true);
1211 aHandlerInfo
->SetAlwaysAskBeforeHandling(warn
);
1213 // If no OS default existed, we set the preferred action to alwaysAsk.
1214 // This really means not initialized (i.e. there's no available handler)
1215 // to all the code...
1216 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::alwaysAsk
);
1222 // XPCOM profile change observer
1224 nsExternalHelperAppService::Observe(nsISupports
* aSubject
, const char* aTopic
,
1225 const char16_t
* someData
) {
1226 if (!strcmp(aTopic
, "profile-before-change")) {
1227 ExpungeTemporaryFiles();
1228 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
1229 ExpungeTemporaryPrivateFiles();
1234 //////////////////////////////////////////////////////////////////////////////////////////////////////
1235 // begin external app handler implementation
1236 //////////////////////////////////////////////////////////////////////////////////////////////////////
1238 NS_IMPL_ADDREF(nsExternalAppHandler
)
1239 NS_IMPL_RELEASE(nsExternalAppHandler
)
1241 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler
)
1242 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStreamListener
)
1243 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
1244 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
1245 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher
)
1246 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
1247 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver
)
1248 NS_INTERFACE_MAP_ENTRY(nsINamed
)
1249 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler
)
1250 NS_INTERFACE_MAP_END
1252 nsExternalAppHandler::nsExternalAppHandler(
1253 nsIMIMEInfo
* aMIMEInfo
, const nsACString
& aTempFileExtension
,
1254 BrowsingContext
* aBrowsingContext
, nsIInterfaceRequestor
* aWindowContext
,
1255 nsExternalHelperAppService
* aExtProtSvc
,
1256 const nsAString
& aSuggestedFilename
, uint32_t aReason
, bool aForceSave
)
1257 : mMimeInfo(aMIMEInfo
),
1258 mBrowsingContext(aBrowsingContext
),
1259 mWindowContext(aWindowContext
),
1260 mSuggestedFileName(aSuggestedFilename
),
1261 mForceSave(aForceSave
),
1263 mStopRequestIssued(false),
1264 mIsFileChannel(false),
1265 mShouldCloseWindow(false),
1266 mHandleInternally(false),
1268 mTempFileIsExecutable(false),
1269 mTimeDownloadStarted(0),
1273 mDialogProgressListener(nullptr),
1276 mExtProtSvc(aExtProtSvc
) {
1277 // make sure the extention includes the '.'
1278 if (!aTempFileExtension
.IsEmpty() && aTempFileExtension
.First() != '.')
1279 mTempFileExtension
= char16_t('.');
1280 AppendUTF8toUTF16(aTempFileExtension
, mTempFileExtension
);
1282 // Get mSuggestedFileName's current file extension.
1283 nsAutoString originalFileExt
;
1284 int32_t pos
= mSuggestedFileName
.RFindChar('.');
1285 if (pos
!= kNotFound
) {
1286 mSuggestedFileName
.Right(originalFileExt
,
1287 mSuggestedFileName
.Length() - pos
);
1290 // replace platform specific path separator and illegal characters to avoid
1292 // Try to keep the use of spaces or underscores in sync with the Downloads
1293 // code sanitization in DownloadPaths.jsm
1294 mSuggestedFileName
.ReplaceChar(KNOWN_PATH_SEPARATORS
, '_');
1295 mSuggestedFileName
.ReplaceChar(FILE_ILLEGAL_CHARACTERS
, ' ');
1296 mSuggestedFileName
.ReplaceChar(char16_t(0), '_');
1297 mTempFileExtension
.ReplaceChar(KNOWN_PATH_SEPARATORS
, '_');
1298 mTempFileExtension
.ReplaceChar(FILE_ILLEGAL_CHARACTERS
, ' ');
1300 // Remove unsafe bidi characters which might have spoofing implications (bug
1302 const char16_t unsafeBidiCharacters
[] = {
1303 char16_t(0x061c), // Arabic Letter Mark
1304 char16_t(0x200e), // Left-to-Right Mark
1305 char16_t(0x200f), // Right-to-Left Mark
1306 char16_t(0x202a), // Left-to-Right Embedding
1307 char16_t(0x202b), // Right-to-Left Embedding
1308 char16_t(0x202c), // Pop Directional Formatting
1309 char16_t(0x202d), // Left-to-Right Override
1310 char16_t(0x202e), // Right-to-Left Override
1311 char16_t(0x2066), // Left-to-Right Isolate
1312 char16_t(0x2067), // Right-to-Left Isolate
1313 char16_t(0x2068), // First Strong Isolate
1314 char16_t(0x2069), // Pop Directional Isolate
1316 mSuggestedFileName
.ReplaceChar(unsafeBidiCharacters
, '_');
1317 mTempFileExtension
.ReplaceChar(unsafeBidiCharacters
, '_');
1319 // Remove trailing or leading spaces that we may have generated while
1321 mSuggestedFileName
.CompressWhitespace();
1322 mTempFileExtension
.CompressWhitespace();
1324 EnsureCorrectExtension(originalFileExt
);
1326 mBufferSize
= Preferences::GetUint("network.buffer.cache.size", 4096);
1329 nsExternalAppHandler::~nsExternalAppHandler() {
1330 MOZ_ASSERT(!mSaver
, "Saver should hold a reference to us until deleted");
1333 bool nsExternalAppHandler::ShouldForceExtension(const nsString
& aFileExt
) {
1334 nsAutoCString MIMEType
;
1335 if (!mMimeInfo
|| NS_FAILED(mMimeInfo
->GetMIMEType(MIMEType
))) {
1339 bool canForce
= StringBeginsWith(MIMEType
, "image/"_ns
) ||
1340 StringBeginsWith(MIMEType
, "audio/"_ns
) ||
1341 StringBeginsWith(MIMEType
, "video/"_ns
);
1344 StaticPrefs::browser_download_sanitize_non_media_extensions()) {
1345 for (const char* mime
: forcedExtensionMimetypes
) {
1346 if (MIMEType
.Equals(mime
)) {
1356 // If we get here, we know for sure the mimetype allows us to overwrite the
1357 // existing extension, if it's wrong. Return whether the extension is wrong:
1359 bool knownExtension
= false;
1360 // Note that aFileExt is either empty or consists of an extension
1361 // *including the dot* which we remove for ExtensionExists().
1363 aFileExt
.IsEmpty() || aFileExt
.EqualsLiteral(".") ||
1364 (NS_SUCCEEDED(mMimeInfo
->ExtensionExists(
1365 Substring(NS_ConvertUTF16toUTF8(aFileExt
), 1), &knownExtension
)) &&
1369 void nsExternalAppHandler::EnsureCorrectExtension(const nsString
& aFileExt
) {
1370 // If we don't have an extension (which will include the .),
1371 // just short-circuit.
1372 if (mTempFileExtension
.Length() <= 1) {
1376 // After removing trailing whitespaces from the name, if we have a
1377 // temp file extension, there are broadly 2 cases where we want to
1378 // replace the extension.
1379 // First, if the file extension contains invalid characters.
1380 // Second, for document type mimetypes, if the extension is either
1381 // missing or not valid for this mimetype.
1382 bool replaceExtension
=
1383 (aFileExt
.FindCharInSet(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
) !=
1385 ShouldForceExtension(aFileExt
);
1387 if (replaceExtension
) {
1388 int32_t pos
= mSuggestedFileName
.RFindChar('.');
1389 if (pos
!= kNotFound
) {
1390 mSuggestedFileName
=
1391 Substring(mSuggestedFileName
, 0, pos
) + mTempFileExtension
;
1393 mSuggestedFileName
.Append(mTempFileExtension
);
1398 * Ensure we don't double-append the file extension if it matches:
1400 if (replaceExtension
||
1401 aFileExt
.Equals(mTempFileExtension
, nsCaseInsensitiveStringComparator
)) {
1402 // Matches -> mTempFileExtension can be empty
1403 mTempFileExtension
.Truncate();
1407 void nsExternalAppHandler::DidDivertRequest(nsIRequest
* request
) {
1408 MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1409 // Remove our request from the child loadGroup
1410 RetargetLoadNotifications(request
);
1413 NS_IMETHODIMP
nsExternalAppHandler::SetWebProgressListener(
1414 nsIWebProgressListener2
* aWebProgressListener
) {
1415 // This is always called by nsHelperDlg.js. Go ahead and register the
1416 // progress listener. At this point, we don't have mTransfer.
1417 mDialogProgressListener
= aWebProgressListener
;
1421 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFile(nsIFile
** aTarget
) {
1422 if (mFinalFileDestination
)
1423 *aTarget
= mFinalFileDestination
;
1425 *aTarget
= mTempFile
;
1427 NS_IF_ADDREF(*aTarget
);
1431 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec
) {
1432 // Use the real target if it's been set
1433 if (mFinalFileDestination
) return mFinalFileDestination
->IsExecutable(aExec
);
1435 // Otherwise, use the stored executable-ness of the temporary
1436 *aExec
= mTempFileIsExecutable
;
1440 NS_IMETHODIMP
nsExternalAppHandler::GetTimeDownloadStarted(PRTime
* aTime
) {
1441 *aTime
= mTimeDownloadStarted
;
1445 NS_IMETHODIMP
nsExternalAppHandler::GetContentLength(int64_t* aContentLength
) {
1446 *aContentLength
= mContentLength
;
1450 NS_IMETHODIMP
nsExternalAppHandler::GetBrowsingContextId(
1451 uint64_t* aBrowsingContextId
) {
1452 *aBrowsingContextId
= mBrowsingContext
? mBrowsingContext
->Id() : 0;
1456 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest
* request
) {
1457 // we are going to run the downloading of the helper app in our own little
1458 // docloader / load group context. so go ahead and force the creation of a
1459 // load group and doc loader for us to use...
1460 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1461 if (!aChannel
) return;
1463 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
1465 nsCOMPtr
<nsILoadGroup
> oldLoadGroup
;
1466 aChannel
->GetLoadGroup(getter_AddRefs(oldLoadGroup
));
1469 oldLoadGroup
->RemoveRequest(request
, nullptr, NS_BINDING_RETARGETED
);
1472 aChannel
->SetLoadGroup(nullptr);
1473 aChannel
->SetNotificationCallbacks(nullptr);
1475 nsCOMPtr
<nsIPrivateBrowsingChannel
> pbChannel
= do_QueryInterface(aChannel
);
1477 pbChannel
->SetPrivate(isPrivate
);
1481 nsresult
nsExternalAppHandler::SetUpTempFile(nsIChannel
* aChannel
) {
1482 // First we need to try to get the destination directory for the temporary
1484 nsresult rv
= GetDownloadDirectory(getter_AddRefs(mTempFile
));
1485 NS_ENSURE_SUCCESS(rv
, rv
);
1487 // At this point, we do not have a filename for the temp file. For security
1488 // purposes, this cannot be predictable, so we must use a cryptographic
1489 // quality PRNG to generate one.
1490 // We will request raw random bytes, and transform that to a base64 string,
1491 // as all characters from the base64 set are acceptable for filenames. For
1492 // each three bytes of random data, we will get four bytes of ASCII. Request
1493 // a bit more, to be safe, and truncate to the length we want in the end.
1495 const uint32_t wantedFileNameLength
= 8;
1496 const uint32_t requiredBytesLength
=
1497 static_cast<uint32_t>((wantedFileNameLength
+ 1) / 4 * 3);
1499 nsCOMPtr
<nsIRandomGenerator
> rg
=
1500 do_GetService("@mozilla.org/security/random-generator;1", &rv
);
1501 NS_ENSURE_SUCCESS(rv
, rv
);
1504 rv
= rg
->GenerateRandomBytes(requiredBytesLength
, &buffer
);
1505 NS_ENSURE_SUCCESS(rv
, rv
);
1507 nsAutoCString tempLeafName
;
1508 nsDependentCSubstring
randomData(reinterpret_cast<const char*>(buffer
),
1509 requiredBytesLength
);
1510 rv
= Base64Encode(randomData
, tempLeafName
);
1513 NS_ENSURE_SUCCESS(rv
, rv
);
1515 tempLeafName
.Truncate(wantedFileNameLength
);
1517 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1518 // to replace illegal characters -- notably '/'
1519 tempLeafName
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1521 // now append our extension.
1523 mMimeInfo
->GetPrimaryExtension(ext
);
1524 if (!ext
.IsEmpty()) {
1525 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1526 if (ext
.First() != '.') tempLeafName
.Append('.');
1527 tempLeafName
.Append(ext
);
1530 // We need to temporarily create a dummy file with the correct
1531 // file extension to determine the executable-ness, so do this before adding
1532 // the extra .part extension.
1533 nsCOMPtr
<nsIFile
> dummyFile
;
1534 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dummyFile
));
1535 NS_ENSURE_SUCCESS(rv
, rv
);
1537 // Set the file name without .part
1538 rv
= dummyFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1539 NS_ENSURE_SUCCESS(rv
, rv
);
1540 rv
= dummyFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1541 NS_ENSURE_SUCCESS(rv
, rv
);
1543 // Store executable-ness then delete
1544 dummyFile
->IsExecutable(&mTempFileIsExecutable
);
1545 dummyFile
->Remove(false);
1547 // Add an additional .part to prevent the OS from running this file in the
1548 // default application.
1549 tempLeafName
.AppendLiteral(".part");
1551 rv
= mTempFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1552 // make this file unique!!!
1553 NS_ENSURE_SUCCESS(rv
, rv
);
1554 rv
= mTempFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1555 NS_ENSURE_SUCCESS(rv
, rv
);
1557 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1558 // This is a bit broken in the case when createUnique actually had to append
1559 // some numbers, because then we now have a filename like foo.bar-1.part and
1560 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1561 // this. But the other options are all bad too.... Ideally we'd have a way
1562 // to tell createUnique to put its unique marker before the extension that
1563 // comes before ".part" or something.
1564 rv
= mTempFile
->GetLeafName(mTempLeafName
);
1565 NS_ENSURE_SUCCESS(rv
, rv
);
1567 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName
, u
".part"_ns
),
1568 NS_ERROR_UNEXPECTED
);
1570 // Strip off the ".part" from mTempLeafName
1571 mTempLeafName
.Truncate(mTempLeafName
.Length() - ArrayLength(".part") + 1);
1573 MOZ_ASSERT(!mSaver
, "Output file initialization called more than once!");
1575 do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID
, &rv
);
1576 NS_ENSURE_SUCCESS(rv
, rv
);
1578 rv
= mSaver
->SetObserver(this);
1579 if (NS_FAILED(rv
)) {
1584 rv
= mSaver
->EnableSha256();
1585 NS_ENSURE_SUCCESS(rv
, rv
);
1587 rv
= mSaver
->EnableSignatureInfo();
1588 NS_ENSURE_SUCCESS(rv
, rv
);
1589 LOG(("Enabled hashing and signature verification"));
1591 rv
= mSaver
->SetTarget(mTempFile
, false);
1592 NS_ENSURE_SUCCESS(rv
, rv
);
1597 void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1598 nsIRequest
* aRequest
) {
1599 MOZ_ASSERT(aRequest
);
1601 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface(aRequest
);
1606 // Turn off content encoding conversions if needed
1607 bool applyConversion
= true;
1609 // First, check to see if conversion is already disabled. If so, we
1610 // have nothing to do here.
1611 encChannel
->GetApplyConversion(&applyConversion
);
1612 if (!applyConversion
) {
1616 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(mSourceUrl
));
1618 nsAutoCString extension
;
1619 sourceURL
->GetFileExtension(extension
);
1620 if (!extension
.IsEmpty()) {
1621 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
1622 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
1625 nsresult rv
= encEnum
->HasMore(&hasMore
);
1626 if (NS_SUCCEEDED(rv
) && hasMore
) {
1627 nsAutoCString encType
;
1628 rv
= encEnum
->GetNext(encType
);
1629 if (NS_SUCCEEDED(rv
) && !encType
.IsEmpty()) {
1630 MOZ_ASSERT(mExtProtSvc
);
1631 mExtProtSvc
->ApplyDecodingForExtension(extension
, encType
,
1639 encChannel
->SetApplyConversion(applyConversion
);
1642 already_AddRefed
<nsIInterfaceRequestor
>
1643 nsExternalAppHandler::GetDialogParent() {
1644 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= mWindowContext
;
1646 if (!dialogParent
&& mBrowsingContext
) {
1647 dialogParent
= do_QueryInterface(mBrowsingContext
->GetDOMWindow());
1649 if (!dialogParent
&& mBrowsingContext
&& XRE_IsParentProcess()) {
1650 RefPtr
<Element
> element
= mBrowsingContext
->Top()->GetEmbedderElement();
1652 dialogParent
= do_QueryInterface(element
->OwnerDoc()->GetWindow());
1655 return dialogParent
.forget();
1658 NS_IMETHODIMP
nsExternalAppHandler::OnStartRequest(nsIRequest
* request
) {
1659 MOZ_ASSERT(request
, "OnStartRequest without request?");
1661 // Set mTimeDownloadStarted here as the download has already started and
1662 // we want to record the start time before showing the filepicker.
1663 mTimeDownloadStarted
= PR_Now();
1667 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1670 nsAutoCString MIMEType
;
1672 mMimeInfo
->GetMIMEType(MIMEType
);
1676 aChannel
->GetURI(getter_AddRefs(mSourceUrl
));
1679 mDownloadClassification
=
1680 nsContentSecurityUtils::ClassifyDownload(aChannel
, MIMEType
);
1682 if (mDownloadClassification
== nsITransfer::DOWNLOAD_FORBIDDEN
) {
1683 // If the download is rated as forbidden,
1684 // cancel the request so no ui knows about this.
1686 request
->Cancel(NS_ERROR_ABORT
);
1690 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(request
));
1691 mIsFileChannel
= fileChan
!= nullptr;
1692 if (!mIsFileChannel
) {
1693 // It's possible that this request came from the child process and the
1694 // file channel actually lives there. If this returns true, then our
1695 // mSourceUrl will be an nsIFileURL anyway.
1696 nsCOMPtr
<dom::nsIExternalHelperAppParent
> parent(
1697 do_QueryInterface(request
));
1698 mIsFileChannel
= parent
&& parent
->WasFileChannel();
1701 // Get content length
1703 aChannel
->GetContentLength(&mContentLength
);
1706 if (mBrowsingContext
) {
1707 mMaybeCloseWindowHelper
= new MaybeCloseWindowHelper(mBrowsingContext
);
1708 mMaybeCloseWindowHelper
->SetShouldCloseWindow(mShouldCloseWindow
);
1709 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(request
, &rv
));
1710 // Determine whether a new window was opened specifically for this request
1714 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
, &tmp
))) {
1715 mMaybeCloseWindowHelper
->SetShouldCloseWindow(tmp
);
1720 // retarget all load notifications to our docloader instead of the original
1721 // window's docloader...
1722 RetargetLoadNotifications(request
);
1724 // Close the underlying DOMWindow if it was opened specifically for the
1725 // download. We don't run this in the content process, since we have
1726 // an instance running in the parent as well, which will handle this
1728 if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper
) {
1729 mBrowsingContext
= mMaybeCloseWindowHelper
->MaybeCloseWindow();
1732 // In an IPC setting, we're allowing the child process, here, to make
1733 // decisions about decoding the channel (e.g. decompression). It will
1734 // still forward the decoded (uncompressed) data back to the parent.
1735 // Con: Uncompressed data means more IPC overhead.
1736 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1737 // Parent process doesn't need to expect CPU time on decompression.
1738 MaybeApplyDecodingForExtension(aChannel
);
1740 // At this point, the child process has done everything it can usefully do
1741 // for OnStartRequest.
1742 if (XRE_IsContentProcess()) {
1746 rv
= SetUpTempFile(aChannel
);
1747 if (NS_FAILED(rv
)) {
1748 nsresult transferError
= rv
;
1750 rv
= CreateFailedTransfer();
1751 if (NS_FAILED(rv
)) {
1753 ("Failed to create transfer to report failure."
1754 "Will fallback to prompter!"));
1758 request
->Cancel(transferError
);
1761 if (mTempFile
) mTempFile
->GetPath(path
);
1763 SendStatusChange(kWriteError
, transferError
, request
, path
);
1768 // Inform channel it is open on behalf of a download to throttle it during
1769 // page loads and prevent its caching.
1770 nsCOMPtr
<nsIHttpChannelInternal
> httpInternal
= do_QueryInterface(aChannel
);
1772 rv
= httpInternal
->SetChannelIsForDownload(true);
1773 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1776 if (mSourceUrl
->SchemeIs("data")) {
1777 // In case we're downloading a data:// uri
1778 // we don't want to apply AllowTopLevelNavigationToDataURI.
1779 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1780 loadInfo
->SetForceAllowDataURI(true);
1783 // now that the temp file is set up, find out if we need to invoke a dialog
1784 // asking the user what they want us to do with this content...
1786 // We can get here for three reasons: "can't handle", "sniffed type", or
1787 // "server sent content-disposition:attachment". In the first case we want
1788 // to honor the user's "always ask" pref; in the other two cases we want to
1789 // honor it only if the default action is "save". Opening attachments in
1790 // helper apps by default breaks some websites (especially if the attachment
1791 // is one part of a multipart document). Opening sniffed content in helper
1792 // apps by default introduces security holes that we'd rather not have.
1794 // So let's find out whether the user wants to be prompted. If he does not,
1795 // check mReason and the preferred action to see what we should do.
1797 bool alwaysAsk
= true;
1798 mMimeInfo
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1800 // But we *don't* ask if this mimeInfo didn't come from
1801 // our user configuration datastore and the user has said
1802 // at some point in the distant past that they don't
1803 // want to be asked. The latter fact would have been
1804 // stored in pref strings back in the old days.
1806 bool mimeTypeIsInDatastore
= false;
1807 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1808 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1810 handlerSvc
->Exists(mMimeInfo
, &mimeTypeIsInDatastore
);
1812 if (!handlerSvc
|| !mimeTypeIsInDatastore
) {
1813 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF
,
1815 // Don't need to ask after all.
1817 // Make sure action matches pref (save to disk).
1818 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
1819 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF
,
1821 // Don't need to ask after all.
1825 } else if (MIMEType
.EqualsLiteral("text/plain")) {
1827 mMimeInfo
->GetPrimaryExtension(ext
);
1828 // If people are sending us ApplicationReputation-eligible files with
1829 // text/plain mimetypes, enforce asking the user what to do.
1830 if (!ext
.IsEmpty()) {
1831 nsAutoCString
dummyFileName("f");
1832 if (ext
.First() != '.') {
1833 dummyFileName
.Append(".");
1835 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1836 nsCOMPtr
<nsIApplicationReputationService
> appRep
=
1837 components::ApplicationReputation::Service();
1838 appRep
->IsBinary(dummyFileName
+ ext
, &alwaysAsk
);
1842 int32_t action
= nsIMIMEInfo::saveToDisk
;
1843 mMimeInfo
->GetPreferredAction(&action
);
1845 // OK, now check why we're here
1846 if (!alwaysAsk
&& mReason
!= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
1847 // Force asking if we're not saving. See comment back when we fetched the
1848 // alwaysAsk boolean for details.
1849 alwaysAsk
= (action
!= nsIMIMEInfo::saveToDisk
);
1852 // If we're not asking, check we actually know what to do:
1854 alwaysAsk
= action
!= nsIMIMEInfo::saveToDisk
&&
1855 action
!= nsIMIMEInfo::useHelperApp
&&
1856 action
!= nsIMIMEInfo::useSystemDefault
;
1859 // if we were told that we _must_ save to disk without asking, all the stuff
1860 // before this is irrelevant; override it
1863 action
= nsIMIMEInfo::saveToDisk
;
1865 // Additionally, if we are asked by the OS to open a local file,
1866 // automatically downloading it to create a second copy of that file doesn't
1867 // really make sense. We should ask the user what they want to do.
1868 if (mSourceUrl
->SchemeIs("file") && !alwaysAsk
&&
1869 action
== nsIMIMEInfo::saveToDisk
) {
1873 // Display the dialog
1874 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1875 NS_ENSURE_SUCCESS(rv
, rv
);
1877 // this will create a reference cycle (the dialog holds a reference to us as
1878 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1879 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
1880 rv
= mDialog
->Show(this, dialogParent
, mReason
);
1882 // what do we do if the dialog failed? I guess we should call Cancel and
1883 // abort the load....
1885 // We need to do the save/open immediately, then.
1887 /* We need to see whether the file we've got here could be
1888 * executable. If it could, we had better not try to open it!
1889 * We can skip this check, though, if we have a setting to open in a
1891 * This code mirrors the code in
1892 * nsExternalAppHandler::LaunchWithApplication so that what we
1893 * test here is as close as possible to what will really be
1894 * happening if we decide to execute
1896 nsCOMPtr
<nsIHandlerApp
> prefApp
;
1897 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(prefApp
));
1898 if (action
!= nsIMIMEInfo::useHelperApp
|| !prefApp
) {
1899 nsCOMPtr
<nsIFile
> fileToTest
;
1900 GetTargetFile(getter_AddRefs(fileToTest
));
1903 rv
= fileToTest
->IsExecutable(&isExecutable
);
1904 if (NS_FAILED(rv
) ||
1905 isExecutable
) { // checking NS_FAILED, because paranoia is good
1906 action
= nsIMIMEInfo::saveToDisk
;
1908 } else { // Paranoia is good here too, though this really should not
1911 "GetDownloadInfo returned a null file after the temp file has been "
1913 action
= nsIMIMEInfo::saveToDisk
;
1918 if (action
== nsIMIMEInfo::useHelperApp
||
1919 action
== nsIMIMEInfo::useSystemDefault
) {
1920 rv
= LaunchWithApplication(mHandleInternally
);
1922 rv
= PromptForSaveDestination();
1928 // Convert error info into proper message text and send OnStatusChange
1929 // notification to the dialog progress listener or nsITransfer implementation.
1930 void nsExternalAppHandler::SendStatusChange(ErrorType type
, nsresult rv
,
1931 nsIRequest
* aRequest
,
1932 const nsString
& path
) {
1933 const char* msgId
= nullptr;
1935 case NS_ERROR_OUT_OF_MEMORY
:
1940 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1941 // Out of space on target volume.
1945 case NS_ERROR_FILE_READ_ONLY
:
1946 // Attempt to write to read/only file.
1950 case NS_ERROR_FILE_ACCESS_DENIED
:
1951 if (type
== kWriteError
) {
1952 // Attempt to write without sufficient permissions.
1953 #if defined(ANDROID)
1954 // On Android this means the SD card is present but
1955 // unavailable (read-only).
1956 msgId
= "SDAccessErrorCardReadOnly";
1958 msgId
= "accessError";
1961 msgId
= "launchError";
1965 case NS_ERROR_FILE_NOT_FOUND
:
1966 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
:
1967 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
1968 // Helper app not found, let's verify this happened on launch
1969 if (type
== kLaunchError
) {
1970 msgId
= "helperAppNotFound";
1973 #if defined(ANDROID)
1974 else if (type
== kWriteError
) {
1975 // On Android this means the SD card is missing (not in
1977 msgId
= "SDAccessErrorCardMissing";
1984 // Generic read/write/launch error message.
1987 msgId
= "readError";
1990 msgId
= "writeError";
1993 msgId
= "launchError";
2000 nsExternalHelperAppService::mLog
, LogLevel::Error
,
2001 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32
"\n",
2002 msgId
, type
, mDialogProgressListener
.get(), mTransfer
.get(),
2003 static_cast<uint32_t>(rv
)));
2005 MOZ_LOG(nsExternalHelperAppService::mLog
, LogLevel::Error
,
2006 (" path='%s'\n", NS_ConvertUTF16toUTF8(path
).get()));
2008 // Get properties file bundle and extract status string.
2009 nsCOMPtr
<nsIStringBundleService
> stringService
=
2010 mozilla::components::StringBundle::Service();
2011 if (stringService
) {
2012 nsCOMPtr
<nsIStringBundle
> bundle
;
2013 if (NS_SUCCEEDED(stringService
->CreateBundle(
2014 "chrome://global/locale/nsWebBrowserPersist.properties",
2015 getter_AddRefs(bundle
)))) {
2016 nsAutoString msgText
;
2017 AutoTArray
<nsString
, 1> strings
= {path
};
2018 if (NS_SUCCEEDED(bundle
->FormatStringFromName(msgId
, strings
, msgText
))) {
2019 if (mDialogProgressListener
) {
2020 // We have a listener, let it handle the error.
2021 mDialogProgressListener
->OnStatusChange(
2022 nullptr, (type
== kReadError
) ? aRequest
: nullptr, rv
,
2024 } else if (mTransfer
) {
2025 mTransfer
->OnStatusChange(nullptr,
2026 (type
== kReadError
) ? aRequest
: nullptr,
2028 } else if (XRE_IsParentProcess()) {
2029 // We don't have a listener. Simply show the alert ourselves.
2030 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2032 nsCOMPtr
<nsIPrompt
> prompter(do_GetInterface(dialogParent
, &qiRv
));
2034 bundle
->FormatStringFromName("title", strings
, title
);
2037 nsExternalHelperAppService::mLog
, LogLevel::Debug
,
2038 ("mBrowsingContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
2039 ", title='%s', msg='%s'",
2040 mBrowsingContext
.get(), prompter
.get(),
2041 static_cast<uint32_t>(qiRv
), NS_ConvertUTF16toUTF8(title
).get(),
2042 NS_ConvertUTF16toUTF8(msgText
).get()));
2044 // If we didn't have a prompter we will try and get a window
2045 // instead, get it's docshell and use it to alert the user.
2047 nsCOMPtr
<nsPIDOMWindowOuter
> window(do_GetInterface(dialogParent
));
2048 if (!window
|| !window
->GetDocShell()) {
2052 prompter
= do_GetInterface(window
->GetDocShell(), &qiRv
);
2054 MOZ_LOG(nsExternalHelperAppService::mLog
, LogLevel::Debug
,
2055 ("No prompter from mBrowsingContext, using DocShell, "
2056 "window=0x%p, docShell=0x%p, "
2057 "prompter=0x%p, qi rv=0x%08" PRIX32
,
2058 window
.get(), window
->GetDocShell(), prompter
.get(),
2059 static_cast<uint32_t>(qiRv
)));
2061 // If we still don't have a prompter, there's nothing else we
2062 // can do so just return.
2064 MOZ_LOG(nsExternalHelperAppService::mLog
, LogLevel::Error
,
2065 ("No prompter from DocShell, no way to alert user"));
2070 // We should always have a prompter at this point.
2071 prompter
->Alert(title
.get(), msgText
.get());
2079 nsExternalAppHandler::OnDataAvailable(nsIRequest
* request
,
2080 nsIInputStream
* inStr
,
2081 uint64_t sourceOffset
, uint32_t count
) {
2082 nsresult rv
= NS_OK
;
2083 // first, check to see if we've been canceled....
2084 if (mCanceled
|| !mSaver
) {
2085 // then go cancel our underlying channel too
2086 return request
->Cancel(NS_BINDING_ABORTED
);
2089 // read the data out of the stream and write it to the temp file.
2093 nsCOMPtr
<nsIStreamListener
> saver
= do_QueryInterface(mSaver
);
2094 rv
= saver
->OnDataAvailable(request
, inStr
, sourceOffset
, count
);
2095 if (NS_SUCCEEDED(rv
)) {
2096 // Send progress notification.
2098 mTransfer
->OnProgressChange64(nullptr, request
, mProgress
,
2099 mContentLength
, mProgress
,
2103 // An error occurred, notify listener.
2104 nsAutoString tempFilePath
;
2106 mTempFile
->GetPath(tempFilePath
);
2108 SendStatusChange(kReadError
, rv
, request
, tempFilePath
);
2110 // Cancel the download.
2117 NS_IMETHODIMP
nsExternalAppHandler::OnStopRequest(nsIRequest
* request
,
2120 ("nsExternalAppHandler::OnStopRequest\n"
2121 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08" PRIX32
"\n",
2122 mCanceled
, mTransfer
.get(), static_cast<uint32_t>(aStatus
)));
2124 mStopRequestIssued
= true;
2126 // Cancel if the request did not complete successfully.
2127 if (!mCanceled
&& NS_FAILED(aStatus
)) {
2128 // Send error notification.
2129 nsAutoString tempFilePath
;
2130 if (mTempFile
) mTempFile
->GetPath(tempFilePath
);
2131 SendStatusChange(kReadError
, aStatus
, request
, tempFilePath
);
2136 // first, check to see if we've been canceled....
2137 if (mCanceled
|| !mSaver
) {
2141 return mSaver
->Finish(NS_OK
);
2145 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver
* aSaver
,
2151 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver
* aSaver
,
2154 ("nsExternalAppHandler::OnSaveComplete\n"
2155 " aSaver=0x%p, aStatus=0x%08" PRIX32
", mCanceled=%d, mTransfer=0x%p\n",
2156 aSaver
, static_cast<uint32_t>(aStatus
), mCanceled
, mTransfer
.get()));
2159 // Save the hash and signature information
2160 (void)mSaver
->GetSha256Hash(mHash
);
2161 (void)mSaver
->GetSignatureInfo(mSignatureInfo
);
2163 // Free the reference that the saver keeps on us, even if we couldn't get
2167 // Save the redirect information.
2168 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2170 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
2171 nsresult rv
= NS_OK
;
2172 nsCOMPtr
<nsIMutableArray
> redirectChain
=
2173 do_CreateInstance(NS_ARRAY_CONTRACTID
, &rv
);
2174 NS_ENSURE_SUCCESS(rv
, rv
);
2175 LOG(("nsExternalAppHandler: Got %zu redirects\n",
2176 loadInfo
->RedirectChain().Length()));
2177 for (nsIRedirectHistoryEntry
* entry
: loadInfo
->RedirectChain()) {
2178 redirectChain
->AppendElement(entry
);
2180 mRedirects
= redirectChain
;
2183 if (NS_FAILED(aStatus
)) {
2185 mTempFile
->GetPath(path
);
2187 // It may happen when e10s is enabled that there will be no transfer
2188 // object available to communicate status as expected by the system.
2189 // Let's try and create a temporary transfer object to take care of this
2190 // for us, we'll fall back to using the prompt service if we absolutely
2193 // We don't care if this fails.
2194 CreateFailedTransfer();
2197 SendStatusChange(kWriteError
, aStatus
, nullptr, path
);
2198 if (!mCanceled
) Cancel(aStatus
);
2203 // Notify the transfer object that we are done if the user has chosen an
2204 // action. If the user hasn't chosen an action, the progress listener
2205 // (nsITransfer) will be notified in CreateTransfer.
2207 NotifyTransfer(aStatus
);
2213 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus
) {
2214 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2215 MOZ_ASSERT(mTransfer
, "We must have an nsITransfer");
2217 LOG(("Notifying progress listener"));
2219 if (NS_SUCCEEDED(aStatus
)) {
2220 (void)mTransfer
->SetSha256Hash(mHash
);
2221 (void)mTransfer
->SetSignatureInfo(mSignatureInfo
);
2222 (void)mTransfer
->SetRedirects(mRedirects
);
2223 (void)mTransfer
->OnProgressChange64(
2224 nullptr, nullptr, mProgress
, mContentLength
, mProgress
, mContentLength
);
2227 (void)mTransfer
->OnStateChange(nullptr, nullptr,
2228 nsIWebProgressListener::STATE_STOP
|
2229 nsIWebProgressListener::STATE_IS_REQUEST
|
2230 nsIWebProgressListener::STATE_IS_NETWORK
,
2233 // This nsITransfer object holds a reference to us (we are its observer), so
2234 // we need to release the reference to break a reference cycle (and therefore
2235 // to prevent leaking). We do this even if the previous calls failed.
2236 mTransfer
= nullptr;
2239 NS_IMETHODIMP
nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo
** aMIMEInfo
) {
2240 *aMIMEInfo
= mMimeInfo
;
2241 NS_ADDREF(*aMIMEInfo
);
2245 NS_IMETHODIMP
nsExternalAppHandler::GetSource(nsIURI
** aSourceURI
) {
2246 NS_ENSURE_ARG(aSourceURI
);
2247 *aSourceURI
= mSourceUrl
;
2248 NS_IF_ADDREF(*aSourceURI
);
2252 NS_IMETHODIMP
nsExternalAppHandler::GetSuggestedFileName(
2253 nsAString
& aSuggestedFileName
) {
2254 aSuggestedFileName
= mSuggestedFileName
;
2258 nsresult
nsExternalAppHandler::CreateTransfer() {
2259 LOG(("nsExternalAppHandler::CreateTransfer"));
2261 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2262 // We are back from the helper app dialog (where the user chooses to save or
2263 // open), but we aren't done processing the load. in this case, throw up a
2264 // progress dialog so the user can see what's going on.
2265 // Also, release our reference to mDialog. We don't need it anymore, and we
2266 // need to break the reference cycle.
2268 if (!mDialogProgressListener
) {
2269 NS_WARNING("The dialog should nullify the dialog progress listener");
2271 // In case of a non acceptable download, we need to cancel the request and
2272 // pass a FailedTransfer for the Download UI.
2273 if (mDownloadClassification
!= nsITransfer::DOWNLOAD_ACCEPTABLE
) {
2275 mRequest
->Cancel(NS_ERROR_ABORT
);
2276 return CreateFailedTransfer();
2280 // We must be able to create an nsITransfer object. If not, it doesn't matter
2281 // much that we can't launch the helper application or save to disk. Work on
2282 // a local copy rather than mTransfer until we know we succeeded, to make it
2283 // clearer that this function is re-entrant.
2284 nsCOMPtr
<nsITransfer
> transfer
=
2285 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2286 NS_ENSURE_SUCCESS(rv
, rv
);
2288 // Initialize the download
2289 nsCOMPtr
<nsIURI
> target
;
2290 rv
= NS_NewFileURI(getter_AddRefs(target
), mFinalFileDestination
);
2291 NS_ENSURE_SUCCESS(rv
, rv
);
2293 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2294 if (mBrowsingContext
) {
2295 rv
= transfer
->InitWithBrowsingContext(
2296 mSourceUrl
, target
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
, mTempFile
,
2297 this, channel
&& NS_UsePrivateBrowsing(channel
),
2298 mDownloadClassification
, mBrowsingContext
, mHandleInternally
);
2300 rv
= transfer
->Init(mSourceUrl
, target
, u
""_ns
, mMimeInfo
,
2301 mTimeDownloadStarted
, mTempFile
, this,
2302 channel
&& NS_UsePrivateBrowsing(channel
),
2303 mDownloadClassification
);
2306 NS_ENSURE_SUCCESS(rv
, rv
);
2308 // If we were cancelled since creating the transfer, just return. It is
2309 // always ok to return NS_OK if we are cancelled. Callers of this function
2310 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2315 rv
= transfer
->OnStateChange(nullptr, mRequest
,
2316 nsIWebProgressListener::STATE_START
|
2317 nsIWebProgressListener::STATE_IS_REQUEST
|
2318 nsIWebProgressListener::STATE_IS_NETWORK
,
2320 NS_ENSURE_SUCCESS(rv
, rv
);
2327 // Finally, save the transfer to mTransfer.
2328 mTransfer
= transfer
;
2331 // While we were bringing up the progress dialog, we actually finished
2332 // processing the url. If that's the case then mStopRequestIssued will be
2333 // true and OnSaveComplete has been called.
2334 if (mStopRequestIssued
&& !mSaver
&& mTransfer
) {
2335 NotifyTransfer(NS_OK
);
2341 nsresult
nsExternalAppHandler::CreateFailedTransfer() {
2343 nsCOMPtr
<nsITransfer
> transfer
=
2344 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2345 NS_ENSURE_SUCCESS(rv
, rv
);
2347 // We won't pass the temp file to the transfer, so if we have one it needs to
2350 mTempFile
->Remove(false);
2353 nsCOMPtr
<nsIURI
> pseudoTarget
;
2354 if (!mFinalFileDestination
) {
2355 // If we don't have a download directory we're kinda screwed but it's OK
2356 // we'll still report the error via the prompter.
2357 nsCOMPtr
<nsIFile
> pseudoFile
;
2358 rv
= GetDownloadDirectory(getter_AddRefs(pseudoFile
), true);
2359 NS_ENSURE_SUCCESS(rv
, rv
);
2361 // Append the default suggested filename. If the user restarts the transfer
2362 // we will re-trigger a filename check anyway to ensure that it is unique.
2363 rv
= pseudoFile
->Append(mSuggestedFileName
);
2364 NS_ENSURE_SUCCESS(rv
, rv
);
2366 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), pseudoFile
);
2367 NS_ENSURE_SUCCESS(rv
, rv
);
2369 // Initialize the target, if present
2370 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), mFinalFileDestination
);
2371 NS_ENSURE_SUCCESS(rv
, rv
);
2374 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2375 if (mBrowsingContext
) {
2376 rv
= transfer
->InitWithBrowsingContext(
2377 mSourceUrl
, pseudoTarget
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
,
2378 nullptr, this, channel
&& NS_UsePrivateBrowsing(channel
),
2379 mDownloadClassification
, mBrowsingContext
, mHandleInternally
);
2381 rv
= transfer
->Init(mSourceUrl
, pseudoTarget
, u
""_ns
, mMimeInfo
,
2382 mTimeDownloadStarted
, nullptr, this,
2383 channel
&& NS_UsePrivateBrowsing(channel
),
2384 mDownloadClassification
);
2386 NS_ENSURE_SUCCESS(rv
, rv
);
2388 // Our failed transfer is ready.
2389 mTransfer
= std::move(transfer
);
2394 nsresult
nsExternalAppHandler::SaveDestinationAvailable(nsIFile
* aFile
) {
2396 ContinueSave(aFile
);
2398 Cancel(NS_BINDING_ABORTED
);
2403 void nsExternalAppHandler::RequestSaveDestination(
2404 const nsString
& aDefaultFile
, const nsString
& aFileExtension
) {
2405 // Display the dialog
2406 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2407 // "AutoDownload" w/o showing a prompt
2408 nsresult rv
= NS_OK
;
2410 // Get helper app launcher dialog.
2411 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
2413 Cancel(NS_BINDING_ABORTED
);
2418 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we
2419 // can't unescape it because the dialog is implemented by a JS component which
2420 // doesn't have a window so no unescape routine is defined...
2422 // Now, be sure to keep |this| alive, and the dialog
2423 // If we don't do this, users that close the helper app dialog while the file
2424 // picker is up would cause Cancel() to be called, and the dialog would be
2425 // released, which would release this object too, which would crash.
2427 RefPtr
<nsExternalAppHandler
> kungFuDeathGrip(this);
2428 nsCOMPtr
<nsIHelperAppLauncherDialog
> dlg(mDialog
);
2429 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2431 rv
= dlg
->PromptForSaveToFileAsync(this, dialogParent
, aDefaultFile
.get(),
2432 aFileExtension
.get(), mForceSave
);
2433 if (NS_FAILED(rv
)) {
2434 Cancel(NS_BINDING_ABORTED
);
2438 // PromptForSaveDestination should only be called by the helper app dialog which
2439 // allows the user to say launch with application or save to disk.
2440 NS_IMETHODIMP
nsExternalAppHandler::PromptForSaveDestination() {
2441 if (mCanceled
) return NS_OK
;
2443 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
2445 if (mSuggestedFileName
.IsEmpty()) {
2446 RequestSaveDestination(mTempLeafName
, mTempFileExtension
);
2448 nsAutoString fileExt
;
2449 int32_t pos
= mSuggestedFileName
.RFindChar('.');
2451 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
2453 if (fileExt
.IsEmpty()) {
2454 fileExt
= mTempFileExtension
;
2457 RequestSaveDestination(mSuggestedFileName
, fileExt
);
2462 nsresult
nsExternalAppHandler::ContinueSave(nsIFile
* aNewFileLocation
) {
2463 if (mCanceled
) return NS_OK
;
2465 MOZ_ASSERT(aNewFileLocation
, "Must be called with a non-null file");
2467 nsresult rv
= NS_OK
;
2468 nsCOMPtr
<nsIFile
> fileToUse
= aNewFileLocation
;
2469 mFinalFileDestination
= fileToUse
;
2471 // Move what we have in the final directory, but append .part
2472 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2473 // saver if we are done (Finish has been called) but OnSaverComplete has not
2475 if (mFinalFileDestination
&& mSaver
&& !mStopRequestIssued
) {
2476 nsCOMPtr
<nsIFile
> movedFile
;
2477 mFinalFileDestination
->Clone(getter_AddRefs(movedFile
));
2479 // Get the old leaf name and append .part to it
2481 mFinalFileDestination
->GetLeafName(name
);
2482 name
.AppendLiteral(".part");
2483 movedFile
->SetLeafName(name
);
2485 rv
= mSaver
->SetTarget(movedFile
, true);
2486 if (NS_FAILED(rv
)) {
2488 mTempFile
->GetPath(path
);
2489 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2494 mTempFile
= movedFile
;
2498 // The helper app dialog has told us what to do and we have a final file
2500 rv
= CreateTransfer();
2501 // If we fail to create the transfer, Cancel.
2502 if (NS_FAILED(rv
)) {
2510 // LaunchWithApplication should only be called by the helper app dialog which
2511 // allows the user to say launch with application or save to disk.
2512 NS_IMETHODIMP
nsExternalAppHandler::LaunchWithApplication(
2513 bool aHandleInternally
) {
2514 if (mCanceled
) return NS_OK
;
2516 mHandleInternally
= aHandleInternally
;
2518 // Now check if the file is local, in which case we won't bother with saving
2519 // it to a temporary directory and just launch it from where it is
2520 nsCOMPtr
<nsIFileURL
> fileUrl(do_QueryInterface(mSourceUrl
));
2521 if (fileUrl
&& mIsFileChannel
) {
2522 Cancel(NS_BINDING_ABORTED
);
2523 nsCOMPtr
<nsIFile
> file
;
2524 nsresult rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2526 if (NS_SUCCEEDED(rv
)) {
2527 rv
= mMimeInfo
->LaunchWithFile(file
);
2528 if (NS_SUCCEEDED(rv
)) return NS_OK
;
2531 if (file
) file
->GetPath(path
);
2532 // If we get here, an error happened
2533 SendStatusChange(kLaunchError
, rv
, nullptr, path
);
2537 // Now that the user has elected to launch the downloaded file with a helper
2538 // app, we're justified in removing the 'salted' name. We'll rename to what
2539 // was specified in mSuggestedFileName after the download is done prior to
2540 // launching the helper app. So that any existing file of that name won't be
2541 // overwritten we call CreateUnique(). Also note that we use the same
2542 // directory as originally downloaded so the download can be renamed in place
2544 nsCOMPtr
<nsIFile
> fileToUse
;
2545 (void)GetDownloadDirectory(getter_AddRefs(fileToUse
));
2547 if (mSuggestedFileName
.IsEmpty()) {
2548 // Keep using the leafname of the temp file, since we're just starting a
2550 mSuggestedFileName
= mTempLeafName
;
2554 fileToUse
->Append(mSuggestedFileName
+ mTempFileExtension
);
2556 fileToUse
->Append(mSuggestedFileName
);
2559 nsresult rv
= fileToUse
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
2560 if (NS_SUCCEEDED(rv
)) {
2561 mFinalFileDestination
= fileToUse
;
2562 // launch the progress window now that the user has picked the desired
2564 rv
= CreateTransfer();
2565 if (NS_FAILED(rv
)) {
2569 // Cancel the download and report an error. We do not want to end up in
2570 // a state where it appears that we have a normal download that is
2571 // pointing to a file that we did not actually create.
2573 mTempFile
->GetPath(path
);
2574 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2580 NS_IMETHODIMP
nsExternalAppHandler::Cancel(nsresult aReason
) {
2581 NS_ENSURE_ARG(NS_FAILED(aReason
));
2589 // We are still writing to the target file. Give the saver a chance to
2590 // close the target file, then notify the transfer object if necessary in
2591 // the OnSaveComplete callback.
2592 mSaver
->Finish(aReason
);
2595 if (mStopRequestIssued
&& mTempFile
) {
2596 // This branch can only happen when the user cancels the helper app dialog
2597 // when the request has completed. The temp file has to be removed here,
2598 // because mSaver has been released at that time with the temp file left.
2599 (void)mTempFile
->Remove(false);
2602 // Notify the transfer object that the download has been canceled, if the
2603 // user has already chosen an action and we didn't notify already.
2605 NotifyTransfer(aReason
);
2609 // Break our reference cycle with the helper app dialog (set up in
2615 // Release the listener, to break the reference cycle with it (we are the
2616 // observer of the listener).
2617 mDialogProgressListener
= nullptr;
2622 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char* prefName
,
2623 const char* aContentType
) {
2624 // Search the obsolete pref strings.
2625 nsAutoCString prefCString
;
2626 Preferences::GetCString(prefName
, prefCString
);
2627 if (prefCString
.IsEmpty()) {
2628 // Default is true, if not found in the pref string.
2632 NS_UnescapeURL(prefCString
);
2633 nsACString::const_iterator start
, end
;
2634 prefCString
.BeginReading(start
);
2635 prefCString
.EndReading(end
);
2636 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType
), start
,
2641 nsExternalAppHandler::GetName(nsACString
& aName
) {
2642 aName
.AssignLiteral("nsExternalAppHandler");
2646 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2647 // The following section contains our nsIMIMEService implementation and related
2650 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2652 // nsIMIMEService methods
2653 NS_IMETHODIMP
nsExternalHelperAppService::GetFromTypeAndExtension(
2654 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
2655 nsIMIMEInfo
** _retval
) {
2656 MOZ_ASSERT(!aMIMEType
.IsEmpty() || !aFileExt
.IsEmpty(),
2657 "Give me something to work with");
2658 MOZ_DIAGNOSTIC_ASSERT(aFileExt
.FindChar('\0') == kNotFound
,
2659 "The extension should never contain null characters");
2660 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2661 PromiseFlatCString(aMIMEType
).get(),
2662 PromiseFlatCString(aFileExt
).get()));
2666 // OK... we need a type. Get one.
2667 nsAutoCString
typeToUse(aMIMEType
);
2668 if (typeToUse
.IsEmpty()) {
2669 nsresult rv
= GetTypeFromExtension(aFileExt
, typeToUse
);
2670 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
2673 // We promise to only send lower case mime types to the OS
2674 ToLowerCase(typeToUse
);
2676 // First, ask the OS for a mime info
2678 nsresult rv
= GetMIMEInfoFromOS(typeToUse
, aFileExt
, &found
, _retval
);
2679 if (NS_WARN_IF(NS_FAILED(rv
))) {
2683 LOG(("OS gave back 0x%p - found: %i\n", *_retval
, found
));
2684 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2685 if (!*_retval
) return NS_ERROR_OUT_OF_MEMORY
;
2687 // The handler service can make up for bad mime types by checking the file
2688 // extension. If the mime type is known (in extras or in the handler
2689 // service), we stop it doing so by flipping this bool to true.
2690 bool trustMIMEType
= false;
2692 // Check extras - not everything we support will be known by the OS store,
2693 // unfortunately, and it may even miss some extensions that we know should
2694 // be accepted. We only do this for non-octet-stream mimetypes, because
2695 // our information for octet-stream would lead to us trying to open all such
2696 // files as Binary file with exe, com or bin extension regardless of the
2698 if (!typeToUse
.Equals(APPLICATION_OCTET_STREAM
,
2699 nsCaseInsensitiveCStringComparator
)) {
2700 rv
= FillMIMEInfoForMimeTypeFromExtras(typeToUse
, !found
, *_retval
);
2701 LOG(("Searched extras (by type), rv 0x%08" PRIX32
"\n",
2702 static_cast<uint32_t>(rv
)));
2703 trustMIMEType
= NS_SUCCEEDED(rv
);
2704 found
= found
|| NS_SUCCEEDED(rv
);
2707 // Now, let's see if we can find something in our datastore.
2708 // This will not overwrite the OS information that interests us
2709 // (i.e. default application, default app. description)
2710 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
2711 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2713 bool hasHandler
= false;
2714 (void)handlerSvc
->Exists(*_retval
, &hasHandler
);
2716 rv
= handlerSvc
->FillHandlerInfo(*_retval
, ""_ns
);
2717 LOG(("Data source: Via type: retval 0x%08" PRIx32
"\n",
2718 static_cast<uint32_t>(rv
)));
2719 trustMIMEType
= trustMIMEType
|| NS_SUCCEEDED(rv
);
2721 rv
= NS_ERROR_NOT_AVAILABLE
;
2724 found
= found
|| NS_SUCCEEDED(rv
);
2727 // If we still haven't found anything, try finding a match for
2728 // an extension in extras first:
2729 if (!found
&& !aFileExt
.IsEmpty()) {
2730 rv
= FillMIMEInfoForExtensionFromExtras(aFileExt
, *_retval
);
2731 LOG(("Searched extras (by ext), rv 0x%08" PRIX32
"\n",
2732 static_cast<uint32_t>(rv
)));
2735 // Then check the handler service - but only do so if we really do not know
2736 // the mimetype. This avoids overwriting good mimetype info with bad file
2738 if ((!found
|| !trustMIMEType
) && handlerSvc
&& !aFileExt
.IsEmpty()) {
2739 nsAutoCString overrideType
;
2740 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, overrideType
);
2741 if (NS_SUCCEEDED(rv
) && !overrideType
.IsEmpty()) {
2742 // We can't check handlerSvc->Exists() here, because we have a
2743 // overideType. That's ok, it just results in some console noise.
2744 // (If there's no handler for the override type, it throws)
2745 rv
= handlerSvc
->FillHandlerInfo(*_retval
, overrideType
);
2746 LOG(("Data source: Via ext: retval 0x%08" PRIx32
"\n",
2747 static_cast<uint32_t>(rv
)));
2748 found
= found
|| NS_SUCCEEDED(rv
);
2752 // If we still don't have a match, at least set the file description
2753 // to `${aFileExt} File` if it's empty:
2754 if (!found
&& !aFileExt
.IsEmpty()) {
2755 // XXXzpao This should probably be localized
2756 nsAutoCString
desc(aFileExt
);
2757 desc
.AppendLiteral(" File");
2758 (*_retval
)->SetDescription(NS_ConvertASCIItoUTF16(desc
));
2759 LOG(("Falling back to 'File' file description\n"));
2762 // Sometimes, OSes give us bad data. We have a set of forbidden extensions
2763 // for some MIME types. If the primary extension is forbidden,
2764 // overwrite it with a known-good one. See bug 1571247 for context.
2765 nsAutoCString primaryExtension
;
2766 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2767 if (!primaryExtension
.EqualsIgnoreCase(PromiseFlatCString(aFileExt
).get())) {
2768 if (MaybeReplacePrimaryExtension(primaryExtension
, *_retval
)) {
2769 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2773 // Finally, check if we got a file extension and if yes, if it is an
2774 // extension on the mimeinfo, in which case we want it to be the primary one
2775 if (!aFileExt
.IsEmpty()) {
2776 bool matches
= false;
2777 (*_retval
)->ExtensionExists(aFileExt
, &matches
);
2778 LOG(("Extension '%s' matches mime info: %i\n",
2779 PromiseFlatCString(aFileExt
).get(), matches
));
2781 nsAutoCString fileExt
;
2782 ToLowerCase(aFileExt
, fileExt
);
2783 (*_retval
)->SetPrimaryExtension(fileExt
);
2784 primaryExtension
= fileExt
;
2788 // Overwrite with a generic description if the primary extension for the
2789 // type is in our list; these are file formats supported by Firefox and
2790 // we don't want other brands positioning themselves as the sole viewer
2792 if (!primaryExtension
.IsEmpty()) {
2793 for (const char* ext
: descriptionOverwriteExtensions
) {
2794 if (primaryExtension
.Equals(ext
)) {
2795 nsCOMPtr
<nsIStringBundleService
> bundleService
=
2796 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
2797 NS_ENSURE_SUCCESS(rv
, rv
);
2798 nsCOMPtr
<nsIStringBundle
> unknownContentTypeBundle
;
2799 rv
= bundleService
->CreateBundle(
2800 "chrome://mozapps/locale/downloads/unknownContentType.properties",
2801 getter_AddRefs(unknownContentTypeBundle
));
2802 if (NS_SUCCEEDED(rv
)) {
2803 nsAutoCString
stringName(ext
);
2804 stringName
.AppendLiteral("ExtHandlerDescription");
2805 nsAutoString handlerDescription
;
2806 rv
= unknownContentTypeBundle
->GetStringFromName(stringName
.get(),
2807 handlerDescription
);
2808 if (NS_SUCCEEDED(rv
)) {
2809 (*_retval
)->SetDescription(handlerDescription
);
2817 if (LOG_ENABLED()) {
2819 (*_retval
)->GetMIMEType(type
);
2821 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type
.get(),
2822 primaryExtension
.get()));
2829 nsExternalHelperAppService::GetTypeFromExtension(const nsACString
& aFileExt
,
2830 nsACString
& aContentType
) {
2831 // OK. We want to try the following sources of mimetype information, in this
2833 // 1. defaultMimeEntries array
2834 // 2. OS-provided information
2835 // 3. our "extras" array
2836 // 4. Information from plugins
2837 // 5. The "ext-to-type-mapping" category
2838 // Note that, we are intentionally not looking at the handler service, because
2839 // that can be affected by websites, which leads to undesired behavior.
2841 // Early return if called with an empty extension parameter
2842 if (aFileExt
.IsEmpty()) {
2843 return NS_ERROR_NOT_AVAILABLE
;
2846 // First of all, check our default entries
2847 for (auto& entry
: defaultMimeEntries
) {
2848 if (aFileExt
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
2849 aContentType
= entry
.mMimeType
;
2855 if (GetMIMETypeFromOSForExtension(aFileExt
, aContentType
)) {
2859 // Check extras array.
2860 bool found
= GetTypeFromExtras(aFileExt
, aContentType
);
2865 // Let's see if an extension added something
2866 nsCOMPtr
<nsICategoryManager
> catMan(
2867 do_GetService("@mozilla.org/categorymanager;1"));
2869 // The extension in the category entry is always stored as lowercase
2870 nsAutoCString
lowercaseFileExt(aFileExt
);
2871 ToLowerCase(lowercaseFileExt
);
2872 // Read the MIME type from the category entry, if available
2875 catMan
->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt
, type
);
2876 if (NS_SUCCEEDED(rv
)) {
2877 aContentType
= type
;
2882 return NS_ERROR_NOT_AVAILABLE
;
2885 NS_IMETHODIMP
nsExternalHelperAppService::GetPrimaryExtension(
2886 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
2887 nsACString
& _retval
) {
2888 NS_ENSURE_ARG(!aMIMEType
.IsEmpty());
2890 nsCOMPtr
<nsIMIMEInfo
> mi
;
2892 GetFromTypeAndExtension(aMIMEType
, aFileExt
, getter_AddRefs(mi
));
2893 if (NS_FAILED(rv
)) return rv
;
2895 return mi
->GetPrimaryExtension(_retval
);
2898 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromURI(
2899 nsIURI
* aURI
, nsACString
& aContentType
) {
2900 NS_ENSURE_ARG_POINTER(aURI
);
2901 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
2902 aContentType
.Truncate();
2904 // First look for a file to use. If we have one, we just use that.
2905 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
);
2907 nsCOMPtr
<nsIFile
> file
;
2908 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2909 if (NS_SUCCEEDED(rv
)) {
2910 rv
= GetTypeFromFile(file
, aContentType
);
2911 if (NS_SUCCEEDED(rv
)) {
2912 // we got something!
2918 // Now try to get an nsIURL so we don't have to do our own parsing
2919 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
2922 rv
= url
->GetFileExtension(ext
);
2923 if (NS_FAILED(rv
)) return rv
;
2924 if (ext
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
2926 UnescapeFragment(ext
, url
, ext
);
2928 return GetTypeFromExtension(ext
, aContentType
);
2931 // no url, let's give the raw spec a shot
2932 nsAutoCString specStr
;
2933 rv
= aURI
->GetSpec(specStr
);
2934 if (NS_FAILED(rv
)) return rv
;
2935 UnescapeFragment(specStr
, aURI
, specStr
);
2937 // find the file extension (if any)
2938 int32_t extLoc
= specStr
.RFindChar('.');
2939 int32_t specLength
= specStr
.Length();
2940 if (-1 != extLoc
&& extLoc
!= specLength
- 1 &&
2941 // nothing over 20 chars long can be sanely considered an
2942 // extension.... Dat dere would be just data.
2943 specLength
- extLoc
< 20) {
2944 return GetTypeFromExtension(Substring(specStr
, extLoc
+ 1), aContentType
);
2947 // We found no information; say so.
2948 return NS_ERROR_NOT_AVAILABLE
;
2951 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromFile(
2952 nsIFile
* aFile
, nsACString
& aContentType
) {
2953 NS_ENSURE_ARG_POINTER(aFile
);
2956 // Get the Extension
2957 nsAutoString fileName
;
2958 rv
= aFile
->GetLeafName(fileName
);
2959 if (NS_FAILED(rv
)) return rv
;
2961 nsAutoCString fileExt
;
2962 if (!fileName
.IsEmpty()) {
2963 int32_t len
= fileName
.Length();
2964 for (int32_t i
= len
; i
>= 0; i
--) {
2965 if (fileName
[i
] == char16_t('.')) {
2966 CopyUTF16toUTF8(Substring(fileName
, i
+ 1), fileExt
);
2972 if (fileExt
.IsEmpty()) return NS_ERROR_FAILURE
;
2974 return GetTypeFromExtension(fileExt
, aContentType
);
2977 nsresult
nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
2978 const nsACString
& aContentType
, bool aOverwriteDescription
,
2979 nsIMIMEInfo
* aMIMEInfo
) {
2980 NS_ENSURE_ARG(aMIMEInfo
);
2982 NS_ENSURE_ARG(!aContentType
.IsEmpty());
2984 // Look for default entry with matching mime type.
2985 nsAutoCString
MIMEType(aContentType
);
2986 ToLowerCase(MIMEType
);
2987 for (auto entry
: extraMimeEntries
) {
2988 if (MIMEType
.Equals(entry
.mMimeType
)) {
2989 // This is the one. Set attributes appropriately.
2990 nsDependentCString
extensions(entry
.mFileExtensions
);
2991 nsACString::const_iterator start
, end
;
2992 extensions
.BeginReading(start
);
2993 extensions
.EndReading(end
);
2994 while (start
!= end
) {
2995 nsACString::const_iterator cursor
= start
;
2996 mozilla::Unused
<< FindCharInReadable(',', cursor
, end
);
2997 aMIMEInfo
->AppendExtension(Substring(start
, cursor
));
2998 // If a comma was found, skip it for the next search.
2999 start
= cursor
!= end
? ++cursor
: cursor
;
3003 aMIMEInfo
->GetDescription(desc
);
3004 if (aOverwriteDescription
|| desc
.IsEmpty()) {
3005 aMIMEInfo
->SetDescription(NS_ConvertASCIItoUTF16(entry
.mDescription
));
3011 return NS_ERROR_NOT_AVAILABLE
;
3014 nsresult
nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
3015 const nsACString
& aExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3017 bool found
= GetTypeFromExtras(aExtension
, type
);
3018 if (!found
) return NS_ERROR_NOT_AVAILABLE
;
3019 return FillMIMEInfoForMimeTypeFromExtras(type
, true, aMIMEInfo
);
3022 bool nsExternalHelperAppService::MaybeReplacePrimaryExtension(
3023 const nsACString
& aPrimaryExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3024 for (const auto& entry
: sForbiddenPrimaryExtensions
) {
3025 if (aPrimaryExtension
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
3026 nsDependentCString
mime(entry
.mMimeType
);
3027 for (const auto& extraEntry
: extraMimeEntries
) {
3028 if (mime
.LowerCaseEqualsASCII(extraEntry
.mMimeType
)) {
3029 nsDependentCString
goodExts(extraEntry
.mFileExtensions
);
3030 int32_t commaPos
= goodExts
.FindChar(',');
3031 commaPos
= commaPos
== kNotFound
? goodExts
.Length() : commaPos
;
3032 auto goodExt
= Substring(goodExts
, 0, commaPos
);
3033 aMIMEInfo
->SetPrimaryExtension(goodExt
);
3042 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString
& aExtension
,
3043 nsACString
& aMIMEType
) {
3044 NS_ASSERTION(!aExtension
.IsEmpty(), "Empty aExtension parameter!");
3046 // Look for default entry with matching extension.
3047 nsDependentCString::const_iterator start
, end
, iter
;
3048 int32_t numEntries
= ArrayLength(extraMimeEntries
);
3049 for (int32_t index
= 0; index
< numEntries
; index
++) {
3050 nsDependentCString
extList(extraMimeEntries
[index
].mFileExtensions
);
3051 extList
.BeginReading(start
);
3052 extList
.EndReading(end
);
3054 while (start
!= end
) {
3055 FindCharInReadable(',', iter
, end
);
3056 if (Substring(start
, iter
)
3057 .Equals(aExtension
, nsCaseInsensitiveCStringComparator
)) {
3058 aMIMEType
= extraMimeEntries
[index
].mMimeType
;
3071 bool nsExternalHelperAppService::GetMIMETypeFromOSForExtension(
3072 const nsACString
& aExtension
, nsACString
& aMIMEType
) {
3074 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
3076 GetMIMEInfoFromOS(""_ns
, aExtension
, &found
, getter_AddRefs(mimeInfo
));
3077 return NS_SUCCEEDED(rv
) && found
&& mimeInfo
&&
3078 NS_SUCCEEDED(mimeInfo
->GetMIMEType(aMIMEType
));
3081 nsresult
nsExternalHelperAppService::GetMIMEInfoFromOS(
3082 const nsACString
& aMIMEType
, const nsACString
& aFileExt
, bool* aFound
,
3083 nsIMIMEInfo
** aMIMEInfo
) {
3084 *aMIMEInfo
= nullptr;
3086 return NS_ERROR_NOT_IMPLEMENTED
;