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/Document.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/WindowGlobalParent.h"
20 #include "mozilla/RandomNum.h"
21 #include "mozilla/StaticPrefs_dom.h"
22 #include "mozilla/StaticPrefs_security.h"
23 #include "mozilla/StaticPtr.h"
24 #include "nsXULAppAPI.h"
26 #include "ExternalHelperAppParent.h"
27 #include "nsExternalHelperAppService.h"
28 #include "nsCExternalHandlerService.h"
32 #include "nsIFileURL.h"
33 #include "nsIChannel.h"
34 #include "nsAppDirectoryServiceDefs.h"
35 #include "nsICategoryManager.h"
36 #include "nsDependentSubstring.h"
37 #include "nsSandboxFlags.h"
39 #include "nsUnicharUtils.h"
40 #include "nsIStringEnumerator.h"
41 #include "nsIStreamListener.h"
42 #include "nsIMIMEService.h"
43 #include "nsILoadGroup.h"
44 #include "nsIWebProgressListener.h"
45 #include "nsITransfer.h"
46 #include "nsReadableUtils.h"
47 #include "nsIRequest.h"
48 #include "nsDirectoryServiceDefs.h"
49 #include "nsIInterfaceRequestor.h"
50 #include "nsThreadUtils.h"
51 #include "nsIMutableArray.h"
52 #include "nsIRedirectHistoryEntry.h"
53 #include "nsOSHelperAppService.h"
54 #include "nsOSHelperAppServiceChild.h"
55 #include "nsContentSecurityUtils.h"
56 #include "nsUTF8Utils.h"
57 #include "nsUnicodeProperties.h"
59 // used to access our datastore of user-configured helper applications
60 #include "nsIHandlerService.h"
61 #include "nsIMIMEInfo.h"
62 #include "nsIHelperAppLauncherDialog.h"
63 #include "nsIContentDispatchChooser.h"
64 #include "nsNetUtil.h"
65 #include "nsIPrivateBrowsingChannel.h"
66 #include "nsIIOService.h"
69 #include "nsIApplicationReputation.h"
71 #include "nsDSURIContentListener.h"
72 #include "nsMimeTypes.h"
73 #include "nsMIMEInfoImpl.h"
74 // used for header disposition information.
75 #include "nsIHttpChannel.h"
76 #include "nsIHttpChannelInternal.h"
77 #include "nsIEncodedChannel.h"
78 #include "nsIMultiPartChannel.h"
79 #include "nsIFileChannel.h"
80 #include "nsIObserverService.h" // so we can be a profile change observer
81 #include "nsIPropertyBag2.h" // for the 64-bit content length
84 # include "nsILocalFileMac.h"
89 #include "nsIStringBundle.h" // XXX needed to localize error msgs
90 #include "nsIPrompt.h"
92 #include "nsITextToSubURI.h" // to unescape the filename
94 #include "nsDocShellCID.h"
97 #include "nsLocalHandlerApp.h"
99 #include "nsIRandomGenerator.h"
101 #include "ContentChild.h"
102 #include "nsXULAppAPI.h"
103 #include "nsPIDOMWindow.h"
104 #include "ExternalHelperAppChild.h"
106 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
109 # include "nsWindowsHelpers.h"
110 # include "nsLocalFile.h"
113 #include "mozilla/Components.h"
114 #include "mozilla/ClearOnShutdown.h"
115 #include "mozilla/Preferences.h"
116 #include "mozilla/ipc/URIUtils.h"
118 using namespace mozilla
;
119 using namespace mozilla::ipc
;
120 using namespace mozilla::dom
;
122 #define kDefaultMaxFileNameLength 254
124 // Download Folder location constants
125 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
126 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
128 NS_FOLDER_VALUE_DESKTOP
= 0,
129 NS_FOLDER_VALUE_DOWNLOADS
= 1,
130 NS_FOLDER_VALUE_CUSTOM
= 2
133 LazyLogModule
nsExternalHelperAppService::sLog("HelperAppService");
135 // Using level 3 here because the OSHelperAppServices use a log level
136 // of LogLevel::Debug (4), and we want less detailed output here
137 // Using 3 instead of LogLevel::Warning because we don't output warnings
140 MOZ_LOG(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info, \
142 #define LOG_ENABLED() \
143 MOZ_LOG_TEST(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info)
145 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF
[] =
146 "browser.helperApps.neverAsk.saveToDisk";
147 static const char NEVER_ASK_FOR_OPEN_FILE_PREF
[] =
148 "browser.helperApps.neverAsk.openFile";
150 StaticRefPtr
<nsIFile
> sFallbackDownloadDir
;
152 // Helper functions for Content-Disposition headers
155 * Given a URI fragment, unescape it
156 * @param aFragment The string to unescape
157 * @param aURI The URI from which this fragment is taken. Only its character set
159 * @param aResult [out] Unescaped string.
161 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
162 nsAString
& aResult
) {
163 // We need the unescaper
165 nsCOMPtr
<nsITextToSubURI
> textToSubURI
=
166 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
167 NS_ENSURE_SUCCESS(rv
, rv
);
169 return textToSubURI
->UnEscapeURIForUI(aFragment
, /* aDontEscape = */ true,
174 * UTF-8 version of UnescapeFragment.
175 * @param aFragment The string to unescape
176 * @param aURI The URI from which this fragment is taken. Only its character set
178 * @param aResult [out] Unescaped string, UTF-8 encoded.
179 * @note It is safe to pass the same string for aFragment and aResult.
180 * @note When this function fails, aResult will not be modified.
182 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
183 nsACString
& aResult
) {
185 nsresult rv
= UnescapeFragment(aFragment
, aURI
, result
);
186 if (NS_SUCCEEDED(rv
)) CopyUTF16toUTF8(result
, aResult
);
191 * Obtains the directory to use. This tends to vary per platform, and
192 * needs to be consistent throughout our codepaths. For platforms where
193 * helper apps use the downloads directory, this should be kept in
194 * sync with DownloadIntegration.sys.mjs.
196 * Optionally skip availability of the directory and storage.
198 static nsresult
GetDownloadDirectory(nsIFile
** _directory
,
199 bool aSkipChecks
= false) {
201 return NS_ERROR_FAILURE
;
204 bool usePrefDir
= !StaticPrefs::browser_download_start_downloads_in_tmp_dir();
206 nsCOMPtr
<nsIFile
> dir
;
209 // Try to get the users download location, if it's set.
210 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST
, -1)) {
211 case NS_FOLDER_VALUE_DESKTOP
:
212 (void)NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR
, getter_AddRefs(dir
));
214 case NS_FOLDER_VALUE_CUSTOM
: {
215 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR
, NS_GET_IID(nsIFile
),
216 getter_AddRefs(dir
));
219 // If we're not checking for availability we're done.
221 dir
.forget(_directory
);
225 // We have the directory, and now we need to make sure it exists
226 nsresult rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
227 // If we can't create this and it's not because the file already
228 // exists, clear out `dir` so we don't return it.
229 if (rv
!= NS_ERROR_FILE_ALREADY_EXISTS
&& NS_FAILED(rv
)) {
233 case NS_FOLDER_VALUE_DOWNLOADS
:
234 // This is just the OS default location, so fall out
238 rv
= NS_GetSpecialDirectory(NS_OS_DEFAULT_DOWNLOAD_DIR
,
239 getter_AddRefs(dir
));
241 // On some OSes, there is no guarantee this directory exists.
242 // Fall back to $HOME + Downloads.
243 if (sFallbackDownloadDir
) {
244 sFallbackDownloadDir
->Clone(getter_AddRefs(dir
));
246 rv
= NS_GetSpecialDirectory(NS_OS_HOME_DIR
, getter_AddRefs(dir
));
247 NS_ENSURE_SUCCESS(rv
, rv
);
249 nsCOMPtr
<nsIStringBundleService
> bundleService
=
250 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
251 NS_ENSURE_SUCCESS(rv
, rv
);
252 nsAutoString downloadLocalized
;
253 nsCOMPtr
<nsIStringBundle
> downloadBundle
;
254 rv
= bundleService
->CreateBundle(
255 "chrome://mozapps/locale/downloads/downloads.properties",
256 getter_AddRefs(downloadBundle
));
257 if (NS_SUCCEEDED(rv
)) {
258 rv
= downloadBundle
->GetStringFromName("downloadsFolder",
262 downloadLocalized
.AssignLiteral("Downloads");
264 rv
= dir
->Append(downloadLocalized
);
265 NS_ENSURE_SUCCESS(rv
, rv
);
266 // Can't getter_AddRefs on StaticRefPtr, so do some copying.
267 nsCOMPtr
<nsIFile
> copy
;
268 dir
->Clone(getter_AddRefs(copy
));
269 sFallbackDownloadDir
= copy
.forget();
270 ClearOnShutdown(&sFallbackDownloadDir
);
273 dir
.forget(_directory
);
277 // We have the directory, and now we need to make sure it exists
278 rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
279 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
|| NS_SUCCEEDED(rv
)) {
280 dir
.forget(_directory
);
285 NS_ENSURE_SUCCESS(rv
, rv
);
288 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dir
));
289 NS_ENSURE_SUCCESS(rv
, rv
);
291 #if !defined(XP_MACOSX) && defined(XP_UNIX)
292 // Ensuring that only the current user can read the file names we end up
293 // creating. Note that creating directories with a specified permission is
294 // only supported on Unix platform right now. That's why the above check
297 uint32_t permissions
;
298 rv
= dir
->GetPermissions(&permissions
);
299 NS_ENSURE_SUCCESS(rv
, rv
);
301 if (permissions
!= PR_IRWXU
) {
302 const char* userName
= PR_GetEnv("USERNAME");
303 if (!userName
|| !*userName
) {
304 userName
= PR_GetEnv("USER");
306 if (!userName
|| !*userName
) {
307 userName
= PR_GetEnv("LOGNAME");
309 if (!userName
|| !*userName
) {
310 userName
= "mozillaUser";
313 nsAutoString userDir
;
314 userDir
.AssignLiteral("mozilla_");
315 userDir
.AppendASCII(userName
);
316 userDir
.ReplaceChar(u
"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
320 nsCOMPtr
<nsIFile
> finalPath
;
323 nsAutoString
countedUserDir(userDir
);
324 countedUserDir
.AppendInt(counter
, 10);
325 dir
->Clone(getter_AddRefs(finalPath
));
326 finalPath
->Append(countedUserDir
);
328 rv
= finalPath
->Exists(&pathExists
);
329 NS_ENSURE_SUCCESS(rv
, rv
);
332 // If this path has the right permissions, use it.
333 rv
= finalPath
->GetPermissions(&permissions
);
334 NS_ENSURE_SUCCESS(rv
, rv
);
336 // Ensuring the path is writable by the current user.
338 rv
= finalPath
->IsWritable(&isWritable
);
339 NS_ENSURE_SUCCESS(rv
, rv
);
341 if (permissions
== PR_IRWXU
&& isWritable
) {
347 rv
= finalPath
->Create(nsIFile::DIRECTORY_TYPE
, PR_IRWXU
);
348 if (NS_SUCCEEDED(rv
)) {
352 if (rv
!= NS_ERROR_FILE_ALREADY_EXISTS
) {
363 NS_ASSERTION(dir
, "Somehow we didn't get a download directory!");
364 dir
.forget(_directory
);
369 * Helper for random bytes for the filename of downloaded part files.
371 nsresult
GenerateRandomName(nsACString
& result
) {
372 // We will request raw random bytes, and transform that to a base64 string,
373 // using url-based base64 encoding so that all characters from the base64
374 // result will be acceptable for filenames.
375 // For each three bytes of random data, we will get four bytes of ASCII.
376 // Request a bit more, to be safe, then truncate in the end.
379 const uint32_t wantedFileNameLength
= 8;
380 const uint32_t requiredBytesLength
=
381 static_cast<uint32_t>((wantedFileNameLength
+ 1) / 4 * 3);
383 uint8_t buffer
[requiredBytesLength
];
384 if (!mozilla::GenerateRandomBytesFromOS(buffer
, requiredBytesLength
)) {
385 return NS_ERROR_FAILURE
;
388 nsAutoCString tempLeafName
;
389 // We're forced to specify a padding policy, though this is guaranteed
390 // not to need padding due to requiredBytesLength being a multiple of 3.
391 rv
= Base64URLEncode(requiredBytesLength
, buffer
,
392 Base64URLEncodePaddingPolicy::Omit
, tempLeafName
);
393 NS_ENSURE_SUCCESS(rv
, rv
);
395 tempLeafName
.Truncate(wantedFileNameLength
);
397 result
.Assign(tempLeafName
);
402 * Structure for storing extension->type mappings.
403 * @see defaultMimeEntries
405 struct nsDefaultMimeTypeEntry
{
406 const char* mMimeType
;
407 const char* mFileExtension
;
411 * Default extension->mimetype mappings. These are not overridable.
412 * If you add types here, make sure they are lowercase, or you'll regret it.
414 static const nsDefaultMimeTypeEntry defaultMimeEntries
[] = {
415 // The following are those extensions that we're asked about during startup,
416 // sorted by order used
419 {APPLICATION_RDF
, "rdf"},
421 // -- end extensions used during startup
423 {IMAGE_JPEG
, "jpeg"},
425 {IMAGE_SVG_XML
, "svg"},
428 {APPLICATION_XPINSTALL
, "xpi"},
429 {"application/xhtml+xml", "xhtml"},
430 {"application/xhtml+xml", "xht"},
432 {APPLICATION_JSON
, "json"},
433 {APPLICATION_XJAVASCRIPT
, "mjs"},
434 {APPLICATION_XJAVASCRIPT
, "js"},
435 {APPLICATION_XJAVASCRIPT
, "jsm"},
438 {APPLICATION_OGG
, "ogg"},
441 {APPLICATION_PDF
, "pdf"},
442 {VIDEO_WEBM
, "webm"},
443 {AUDIO_WEBM
, "webm"},
445 {TEXT_PLAIN
, "properties"},
446 {TEXT_PLAIN
, "locale"},
459 * This is a small private struct used to help us initialize some
460 * default mime types.
462 struct nsExtraMimeTypeEntry
{
463 const char* mMimeType
;
464 const char* mFileExtensions
;
465 const char* mDescription
;
469 * This table lists all of the 'extra' content types that we can deduce from
470 * particular file extensions. These entries also ensure that we provide a good
471 * descriptive name when we encounter files with these content types and/or
472 * extensions. These can be overridden by user helper app prefs. If you add
473 * types here, make sure they are lowercase, or you'll regret it.
475 static const nsExtraMimeTypeEntry extraMimeEntries
[] = {
476 #if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to
478 {APPLICATION_OCTET_STREAM
, "exe,com", "Binary File"},
480 {APPLICATION_OCTET_STREAM
, "exe,com,bin", "Binary File"},
482 {APPLICATION_GZIP2
, "gz", "gzip"},
483 {"application/x-arj", "arj", "ARJ file"},
484 {"application/rtf", "rtf", "Rich Text Format File"},
485 {APPLICATION_ZIP
, "zip", "ZIP Archive"},
486 {APPLICATION_XPINSTALL
, "xpi", "XPInstall Install"},
487 {APPLICATION_PDF
, "pdf", "Portable Document Format"},
488 {APPLICATION_POSTSCRIPT
, "ps,eps,ai", "Postscript File"},
489 {APPLICATION_XJAVASCRIPT
, "js", "Javascript Source File"},
490 {APPLICATION_XJAVASCRIPT
, "jsm,mjs", "Javascript Module Source File"},
491 #ifdef MOZ_WIDGET_ANDROID
492 {"application/vnd.android.package-archive", "apk", "Android Package"},
495 // OpenDocument formats
496 {"application/vnd.oasis.opendocument.text", "odt", "OpenDocument Text"},
497 {"application/vnd.oasis.opendocument.presentation", "odp",
498 "OpenDocument Presentation"},
499 {"application/vnd.oasis.opendocument.spreadsheet", "ods",
500 "OpenDocument Spreadsheet"},
501 {"application/vnd.oasis.opendocument.graphics", "odg",
502 "OpenDocument Graphics"},
504 // Legacy Microsoft Office
505 {"application/msword", "doc", "Microsoft Word"},
506 {"application/vnd.ms-powerpoint", "ppt", "Microsoft PowerPoint"},
507 {"application/vnd.ms-excel", "xls", "Microsoft Excel"},
510 {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
511 "docx", "Microsoft Word (Open XML)"},
513 "vnd.openxmlformats-officedocument.presentationml.presentation",
514 "pptx", "Microsoft PowerPoint (Open XML)"},
515 {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
516 "xlsx", "Microsoft Excel (Open XML)"},
518 {IMAGE_ART
, "art", "ART Image"},
519 {IMAGE_BMP
, "bmp", "BMP Image"},
520 {IMAGE_GIF
, "gif", "GIF Image"},
521 {IMAGE_ICO
, "ico,cur", "ICO Image"},
522 {IMAGE_JPEG
, "jpg,jpeg,jfif,pjpeg,pjp", "JPEG Image"},
523 {IMAGE_PNG
, "png", "PNG Image"},
524 {IMAGE_APNG
, "apng", "APNG Image"},
525 {IMAGE_TIFF
, "tiff,tif", "TIFF Image"},
526 {IMAGE_XBM
, "xbm", "XBM Image"},
527 {IMAGE_SVG_XML
, "svg", "Scalable Vector Graphics"},
528 {IMAGE_WEBP
, "webp", "WebP Image"},
529 {IMAGE_AVIF
, "avif", "AV1 Image File"},
530 {IMAGE_JXL
, "jxl", "JPEG XL Image File"},
532 {MESSAGE_RFC822
, "eml", "RFC-822 data"},
533 {TEXT_PLAIN
, "txt,text", "Text File"},
534 {APPLICATION_JSON
, "json", "JavaScript Object Notation"},
535 {TEXT_VTT
, "vtt", "Web Video Text Tracks"},
536 {TEXT_CACHE_MANIFEST
, "appcache", "Application Cache Manifest"},
537 {TEXT_HTML
, "html,htm,shtml,ehtml", "HyperText Markup Language"},
538 {"application/xhtml+xml", "xhtml,xht",
539 "Extensible HyperText Markup Language"},
540 {APPLICATION_MATHML_XML
, "mml", "Mathematical Markup Language"},
541 {APPLICATION_RDF
, "rdf", "Resource Description Framework"},
542 {"text/csv", "csv", "CSV File"},
543 {TEXT_XML
, "xml,xsl,xbl", "Extensible Markup Language"},
544 {TEXT_CSS
, "css", "Style Sheet"},
545 {TEXT_VCARD
, "vcf,vcard", "Contact Information"},
546 {TEXT_CALENDAR
, "ics,ical,ifb,icalendar", "iCalendar"},
547 {VIDEO_OGG
, "ogv,ogg", "Ogg Video"},
548 {APPLICATION_OGG
, "ogg", "Ogg Video"},
549 {AUDIO_OGG
, "oga", "Ogg Audio"},
550 {AUDIO_OGG
, "opus", "Opus Audio"},
551 {VIDEO_WEBM
, "webm", "Web Media Video"},
552 {AUDIO_WEBM
, "webm", "Web Media Audio"},
553 {AUDIO_MP3
, "mp3,mpega,mp2", "MPEG Audio"},
554 {VIDEO_MP4
, "mp4,m4a,m4b", "MPEG-4 Video"},
555 {AUDIO_MP4
, "m4a,m4b", "MPEG-4 Audio"},
556 {VIDEO_RAW
, "yuv", "Raw YUV Video"},
557 {AUDIO_WAV
, "wav", "Waveform Audio"},
558 {VIDEO_3GPP
, "3gpp,3gp", "3GPP Video"},
559 {VIDEO_3GPP2
, "3g2", "3GPP2 Video"},
560 {AUDIO_AAC
, "aac", "AAC Audio"},
561 {AUDIO_FLAC
, "flac", "FLAC Audio"},
562 {AUDIO_MIDI
, "mid", "Standard MIDI Audio"},
563 {APPLICATION_WASM
, "wasm", "WebAssembly Module"}};
565 static const nsDefaultMimeTypeEntry sForbiddenPrimaryExtensions
[] = {
566 {IMAGE_JPEG
, "jfif"}};
569 * File extensions for which decoding should be disabled.
570 * NOTE: These MUST be lower-case and ASCII.
572 static const nsDefaultMimeTypeEntry nonDecodableExtensions
[] = {
573 {APPLICATION_GZIP
, "gz"},
574 {APPLICATION_GZIP
, "tgz"},
575 {APPLICATION_ZIP
, "zip"},
576 {APPLICATION_COMPRESS
, "z"},
577 {APPLICATION_GZIP
, "svgz"}};
580 * Mimetypes for which we enforce using a known extension.
582 * In addition to this list, we do this for all audio/, video/ and
585 static const char* forcedExtensionMimetypes
[] = {
586 APPLICATION_PDF
, APPLICATION_OGG
, APPLICATION_WASM
,
587 TEXT_CALENDAR
, TEXT_CSS
, TEXT_VCARD
};
590 * Primary extensions of types whose descriptions should be overwritten.
591 * This extension is concatenated with "ExtHandlerDescription" to look up the
592 * description in unknownContentType.properties.
593 * NOTE: These MUST be lower-case and ASCII.
595 static const char* descriptionOverwriteExtensions
[] = {
596 "avif", "jxl", "pdf", "svg", "webp", "xml",
599 static StaticRefPtr
<nsExternalHelperAppService
> sExtHelperAppSvcSingleton
;
602 * In child processes, return an nsOSHelperAppServiceChild for remoting
603 * OS calls to the parent process. In the parent process itself, use
604 * nsOSHelperAppService.
607 already_AddRefed
<nsExternalHelperAppService
>
608 nsExternalHelperAppService::GetSingleton() {
609 if (!sExtHelperAppSvcSingleton
) {
610 if (XRE_IsParentProcess()) {
611 sExtHelperAppSvcSingleton
= new nsOSHelperAppService();
613 sExtHelperAppSvcSingleton
= new nsOSHelperAppServiceChild();
615 ClearOnShutdown(&sExtHelperAppSvcSingleton
);
618 return do_AddRef(sExtHelperAppSvcSingleton
);
621 NS_IMPL_ISUPPORTS(nsExternalHelperAppService
, nsIExternalHelperAppService
,
622 nsPIExternalAppLauncher
, nsIExternalProtocolService
,
623 nsIMIMEService
, nsIObserver
, nsISupportsWeakReference
)
625 nsExternalHelperAppService::nsExternalHelperAppService() {}
626 nsresult
nsExternalHelperAppService::Init() {
627 // Add an observer for profile change
628 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
629 if (!obs
) return NS_ERROR_FAILURE
;
631 nsresult rv
= obs
->AddObserver(this, "profile-before-change", true);
632 NS_ENSURE_SUCCESS(rv
, rv
);
633 return obs
->AddObserver(this, "last-pb-context-exited", true);
636 nsExternalHelperAppService::~nsExternalHelperAppService() {}
638 nsresult
nsExternalHelperAppService::DoContentContentProcessHelper(
639 const nsACString
& aMimeContentType
, nsIChannel
* aChannel
,
640 BrowsingContext
* aContentContext
, bool aForceSave
,
641 nsIInterfaceRequestor
* aWindowContext
,
642 nsIStreamListener
** aStreamListener
) {
643 NS_ENSURE_ARG_POINTER(aChannel
);
645 // We need to get a hold of a ContentChild so that we can begin forwarding
646 // this data to the parent. In the HTTP case, this is unfortunate, since
647 // we're actually passing data from parent->child->parent wastefully, but
648 // the Right Fix will eventually be to short-circuit those channels on the
649 // parent side based on some sort of subscription concept.
650 using mozilla::dom::ContentChild
;
651 using mozilla::dom::ExternalHelperAppChild
;
652 ContentChild
* child
= ContentChild::GetSingleton();
654 return NS_ERROR_FAILURE
;
658 nsCOMPtr
<nsIURI
> uri
;
659 int64_t contentLength
= -1;
660 bool wasFileChannel
= false;
661 uint32_t contentDisposition
= -1;
662 nsAutoString fileName
;
663 nsCOMPtr
<nsILoadInfo
> loadInfo
;
665 aChannel
->GetURI(getter_AddRefs(uri
));
666 aChannel
->GetContentLength(&contentLength
);
667 aChannel
->GetContentDisposition(&contentDisposition
);
668 aChannel
->GetContentDispositionFilename(fileName
);
669 aChannel
->GetContentDispositionHeader(disp
);
670 loadInfo
= aChannel
->LoadInfo();
672 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(aChannel
));
673 wasFileChannel
= fileChan
!= nullptr;
675 nsCOMPtr
<nsIURI
> referrer
;
676 NS_GetReferrerFromChannel(aChannel
, getter_AddRefs(referrer
));
678 mozilla::net::LoadInfoArgs loadInfoArgs
;
679 MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo
, &loadInfoArgs
));
681 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(aChannel
));
682 // Determine whether a new window was opened specifically for this request
683 bool shouldCloseWindow
= false;
685 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
,
689 // Now we build a protocol for forwarding our data to the parent. The
690 // protocol will act as a listener on the child-side and create a "real"
691 // helperAppService listener on the parent-side, via another call to
693 RefPtr
<ExternalHelperAppChild
> childListener
= new ExternalHelperAppChild();
694 MOZ_ALWAYS_TRUE(child
->SendPExternalHelperAppConstructor(
695 childListener
, uri
, loadInfoArgs
, nsCString(aMimeContentType
), disp
,
696 contentDisposition
, fileName
, aForceSave
, contentLength
, wasFileChannel
,
697 referrer
, aContentContext
, shouldCloseWindow
));
699 NS_ADDREF(*aStreamListener
= childListener
);
701 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
703 SanitizeFileName(fileName
, 0);
705 RefPtr
<nsExternalAppHandler
> handler
=
706 new nsExternalAppHandler(nullptr, u
""_ns
, aContentContext
, aWindowContext
,
707 this, fileName
, reason
, aForceSave
);
709 return NS_ERROR_OUT_OF_MEMORY
;
712 childListener
->SetHandler(handler
);
716 NS_IMETHODIMP
nsExternalHelperAppService::CreateListener(
717 const nsACString
& aMimeContentType
, nsIChannel
* aChannel
,
718 BrowsingContext
* aContentContext
, bool aForceSave
,
719 nsIInterfaceRequestor
* aWindowContext
,
720 nsIStreamListener
** aStreamListener
) {
721 MOZ_ASSERT(!XRE_IsContentProcess());
722 NS_ENSURE_ARG_POINTER(aChannel
);
724 nsAutoString fileName
;
725 nsAutoCString fileExtension
;
726 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
728 uint32_t contentDisposition
= -1;
729 aChannel
->GetContentDisposition(&contentDisposition
);
730 if (contentDisposition
== nsIChannel::DISPOSITION_ATTACHMENT
) {
731 reason
= nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
;
734 *aStreamListener
= nullptr;
736 // Get the file extension and name that we will need later
737 nsCOMPtr
<nsIURI
> uri
;
738 bool allowURLExtension
=
739 GetFileNameFromChannel(aChannel
, fileName
, getter_AddRefs(uri
));
741 uint32_t flags
= VALIDATE_ALLOW_EMPTY
;
742 if (aMimeContentType
.Equals(APPLICATION_GUESS_FROM_EXT
,
743 nsCaseInsensitiveCStringComparator
)) {
744 flags
|= VALIDATE_GUESS_FROM_EXTENSION
;
747 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
748 fileName
, aMimeContentType
, uri
, nullptr, flags
, allowURLExtension
);
750 LOG("Type/Ext lookup found 0x%p\n", mimeInfo
.get());
752 // No mimeinfo -> we can't continue. probably OOM.
754 return NS_ERROR_OUT_OF_MEMORY
;
757 if (flags
& VALIDATE_GUESS_FROM_EXTENSION
) {
758 // Replace the content type with what was guessed.
759 nsAutoCString mimeType
;
760 mimeInfo
->GetMIMEType(mimeType
);
761 aChannel
->SetContentType(mimeType
);
763 if (reason
== nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
764 reason
= nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
768 nsAutoString extension
;
769 int32_t dotidx
= fileName
.RFind(u
".");
771 extension
= Substring(fileName
, dotidx
+ 1);
774 // NB: ExternalHelperAppParent depends on this listener always being an
775 // nsExternalAppHandler. If this changes, make sure to update that code.
776 nsExternalAppHandler
* handler
= new nsExternalAppHandler(
777 mimeInfo
, extension
, aContentContext
, aWindowContext
, this, fileName
,
780 return NS_ERROR_OUT_OF_MEMORY
;
783 NS_ADDREF(*aStreamListener
= handler
);
787 NS_IMETHODIMP
nsExternalHelperAppService::DoContent(
788 const nsACString
& aMimeContentType
, nsIChannel
* aChannel
,
789 nsIInterfaceRequestor
* aContentContext
, bool aForceSave
,
790 nsIInterfaceRequestor
* aWindowContext
,
791 nsIStreamListener
** aStreamListener
) {
792 // Scripted interface requestors cannot return an instance of the
793 // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
794 // get to the window via `nsIDOMWindow`. Unfortunately, at that point we
795 // don't know whether the thing we got is an inner or outer window, so have to
796 // work with either one.
797 RefPtr
<BrowsingContext
> bc
;
798 nsCOMPtr
<nsIDOMWindow
> domWindow
= do_GetInterface(aContentContext
);
799 if (nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= do_QueryInterface(domWindow
)) {
800 bc
= outerWindow
->GetBrowsingContext();
801 } else if (nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
802 do_QueryInterface(domWindow
)) {
803 bc
= innerWindow
->GetBrowsingContext();
806 if (XRE_IsContentProcess()) {
807 return DoContentContentProcessHelper(aMimeContentType
, aChannel
, bc
,
808 aForceSave
, aWindowContext
,
812 nsresult rv
= CreateListener(aMimeContentType
, aChannel
, bc
, aForceSave
,
813 aWindowContext
, aStreamListener
);
817 NS_IMETHODIMP
nsExternalHelperAppService::ApplyDecodingForExtension(
818 const nsACString
& aExtension
, const nsACString
& aEncodingType
,
819 bool* aApplyDecoding
) {
820 *aApplyDecoding
= true;
822 for (i
= 0; i
< ArrayLength(nonDecodableExtensions
); ++i
) {
823 if (aExtension
.LowerCaseEqualsASCII(
824 nonDecodableExtensions
[i
].mFileExtension
) &&
825 aEncodingType
.LowerCaseEqualsASCII(
826 nonDecodableExtensions
[i
].mMimeType
)) {
827 *aApplyDecoding
= false;
834 nsresult
nsExternalHelperAppService::GetFileTokenForPath(
835 const char16_t
* aPlatformAppPath
, nsIFile
** aFile
) {
836 nsDependentString
platformAppPath(aPlatformAppPath
);
837 // First, check if we have an absolute path
838 nsIFile
* localFile
= nullptr;
839 nsresult rv
= NS_NewLocalFile(platformAppPath
, true, &localFile
);
840 if (NS_SUCCEEDED(rv
)) {
843 if (NS_FAILED((*aFile
)->Exists(&exists
)) || !exists
) {
845 return NS_ERROR_FILE_NOT_FOUND
;
850 // Second, check if file exists in mozilla program directory
851 rv
= NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR
, aFile
);
852 if (NS_SUCCEEDED(rv
)) {
853 rv
= (*aFile
)->Append(platformAppPath
);
854 if (NS_SUCCEEDED(rv
)) {
856 rv
= (*aFile
)->Exists(&exists
);
857 if (NS_SUCCEEDED(rv
) && exists
) return NS_OK
;
862 return NS_ERROR_NOT_AVAILABLE
;
865 //////////////////////////////////////////////////////////////////////////////////////////////////////
866 // begin external protocol service default implementation...
867 //////////////////////////////////////////////////////////////////////////////////////////////////////
868 NS_IMETHODIMP
nsExternalHelperAppService::ExternalProtocolHandlerExists(
869 const char* aProtocolScheme
, bool* aHandlerExists
) {
870 nsCOMPtr
<nsIHandlerInfo
> handlerInfo
;
871 nsresult rv
= GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme
),
872 getter_AddRefs(handlerInfo
));
873 if (NS_SUCCEEDED(rv
)) {
874 // See if we have any known possible handler apps for this
875 nsCOMPtr
<nsIMutableArray
> possibleHandlers
;
876 handlerInfo
->GetPossibleApplicationHandlers(
877 getter_AddRefs(possibleHandlers
));
880 possibleHandlers
->GetLength(&length
);
882 *aHandlerExists
= true;
887 // if not, fall back on an os-based handler
888 return OSProtocolHandlerExists(aProtocolScheme
, aHandlerExists
);
891 NS_IMETHODIMP
nsExternalHelperAppService::IsExposedProtocol(
892 const char* aProtocolScheme
, bool* aResult
) {
893 // check the per protocol setting first. it always takes precedence.
894 // if not set, then use the global setting.
896 nsAutoCString
prefName("network.protocol-handler.expose.");
897 prefName
+= aProtocolScheme
;
899 if (NS_SUCCEEDED(Preferences::GetBool(prefName
.get(), &val
))) {
904 // by default, no protocol is exposed. i.e., by default all link clicks must
905 // go through the external protocol service. most applications override this
907 *aResult
= Preferences::GetBool("network.protocol-handler.expose-all", false);
912 static const char kExternalProtocolPrefPrefix
[] =
913 "network.protocol-handler.external.";
914 static const char kExternalProtocolDefaultPref
[] =
915 "network.protocol-handler.external-default";
918 nsresult
nsExternalHelperAppService::EscapeURI(nsIURI
* aURI
, nsIURI
** aResult
) {
925 if (spec
.Find("%00") != -1) return NS_ERROR_MALFORMED_URI
;
927 nsAutoCString escapedSpec
;
928 nsresult rv
= NS_EscapeURL(spec
, esc_AlwaysCopy
| esc_ExtHandler
, escapedSpec
,
930 NS_ENSURE_SUCCESS(rv
, rv
);
932 nsCOMPtr
<nsIIOService
> ios(do_GetIOService());
933 return ios
->NewURI(escapedSpec
, nullptr, nullptr, aResult
);
936 bool ExternalProtocolIsBlockedBySandbox(
937 BrowsingContext
* aBrowsingContext
,
938 const bool aHasValidUserGestureActivation
) {
939 if (!StaticPrefs::dom_block_external_protocol_navigation_from_sandbox()) {
943 if (!aBrowsingContext
|| aBrowsingContext
->IsTop()) {
947 uint32_t sandboxFlags
= aBrowsingContext
->GetSandboxFlags();
949 if (sandboxFlags
== SANDBOXED_NONE
) {
953 if (!(sandboxFlags
& SANDBOXED_AUXILIARY_NAVIGATION
)) {
957 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION
)) {
961 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION_CUSTOM_PROTOCOLS
)) {
965 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION
) &&
966 aHasValidUserGestureActivation
) {
974 nsExternalHelperAppService::LoadURI(nsIURI
* aURI
,
975 nsIPrincipal
* aTriggeringPrincipal
,
976 nsIPrincipal
* aRedirectPrincipal
,
977 BrowsingContext
* aBrowsingContext
,
978 bool aTriggeredExternally
,
979 bool aHasValidUserGestureActivation
) {
980 NS_ENSURE_ARG_POINTER(aURI
);
982 if (XRE_IsContentProcess()) {
983 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
984 aURI
, aTriggeringPrincipal
, aRedirectPrincipal
, aBrowsingContext
,
985 aTriggeredExternally
, aHasValidUserGestureActivation
);
989 // Prevent sandboxed BrowsingContexts from navigating to external protocols.
990 // This only uses the sandbox flags of the target BrowsingContext of the
991 // load. The navigating document's CSP sandbox flags do not apply.
992 if (aBrowsingContext
&&
993 ExternalProtocolIsBlockedBySandbox(aBrowsingContext
,
994 aHasValidUserGestureActivation
)) {
995 // Log an error to the web console of the sandboxed BrowsingContext.
996 nsAutoString localizedMsg
;
1000 AutoTArray
<nsString
, 1> params
= {NS_ConvertUTF8toUTF16(spec
)};
1001 nsresult rv
= nsContentUtils::FormatLocalizedString(
1002 nsContentUtils::eSECURITY_PROPERTIES
, "SandboxBlockedCustomProtocols",
1003 params
, localizedMsg
);
1004 NS_ENSURE_SUCCESS(rv
, rv
);
1006 // Log to the the parent window of the iframe. If there is no parent, fall
1007 // back to the iframe window itself.
1008 WindowContext
* windowContext
= aBrowsingContext
->GetParentWindowContext();
1009 if (!windowContext
) {
1010 windowContext
= aBrowsingContext
->GetCurrentWindowContext();
1013 // Skip logging if we still don't have a WindowContext.
1014 NS_ENSURE_TRUE(windowContext
, NS_ERROR_FAILURE
);
1016 nsContentUtils::ReportToConsoleByWindowID(
1017 localizedMsg
, nsIScriptError::errorFlag
, "Security"_ns
,
1018 windowContext
->InnerWindowId(),
1019 windowContext
->Canonical()->GetDocumentURI());
1024 nsCOMPtr
<nsIURI
> escapedURI
;
1025 nsresult rv
= EscapeURI(aURI
, getter_AddRefs(escapedURI
));
1026 NS_ENSURE_SUCCESS(rv
, rv
);
1028 nsAutoCString scheme
;
1029 escapedURI
->GetScheme(scheme
);
1030 if (scheme
.IsEmpty()) return NS_OK
; // must have a scheme
1032 // Deny load if the prefs say to do so
1033 nsAutoCString
externalPref(kExternalProtocolPrefPrefix
);
1034 externalPref
+= scheme
;
1035 bool allowLoad
= false;
1036 if (NS_FAILED(Preferences::GetBool(externalPref
.get(), &allowLoad
))) {
1037 // no scheme-specific value, check the default
1039 Preferences::GetBool(kExternalProtocolDefaultPref
, &allowLoad
))) {
1040 return NS_OK
; // missing default pref
1045 return NS_OK
; // explicitly denied
1048 // Now check if the principal is allowed to access the navigated context.
1049 // We allow navigating subframes, even if not same-origin - non-external
1050 // links can always navigate everywhere, so this is a minor additional
1051 // restriction, only aiming to prevent some types of spoofing attacks
1052 // from otherwise disjoint browsingcontext trees.
1053 if (aBrowsingContext
&& aTriggeringPrincipal
&&
1054 !StaticPrefs::security_allow_disjointed_external_uri_loads() &&
1055 // Add-on principals are always allowed:
1056 !BasePrincipal::Cast(aTriggeringPrincipal
)->AddonPolicy() &&
1057 // As is chrome code:
1058 !aTriggeringPrincipal
->IsSystemPrincipal()) {
1059 RefPtr
<BrowsingContext
> bc
= aBrowsingContext
;
1060 WindowGlobalParent
* wgp
= bc
->Canonical()->GetCurrentWindowGlobal();
1061 bool foundAccessibleFrame
= false;
1063 // Also allow this load if the target is a toplevel BC and contains a
1064 // non-web-controlled about:blank document
1065 if (bc
->IsTop() && !bc
->HadOriginalOpener() && wgp
) {
1066 RefPtr
<nsIURI
> uri
= wgp
->GetDocumentURI();
1067 foundAccessibleFrame
=
1068 uri
&& uri
->GetSpecOrDefault().EqualsLiteral("about:blank");
1071 while (!foundAccessibleFrame
) {
1073 foundAccessibleFrame
=
1074 aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal());
1076 // We have to get the parent via the bc, because there may not
1077 // be a window global for the innermost bc; see bug 1650162.
1078 BrowsingContext
* parent
= bc
->GetParent();
1083 wgp
= parent
->Canonical()->GetCurrentWindowGlobal();
1086 if (!foundAccessibleFrame
) {
1087 // See if this navigation could have come from a subframe.
1088 nsTArray
<RefPtr
<BrowsingContext
>> contexts
;
1089 aBrowsingContext
->GetAllBrowsingContextsInSubtree(contexts
);
1090 for (const auto& kid
: contexts
) {
1091 wgp
= kid
->Canonical()->GetCurrentWindowGlobal();
1092 if (wgp
&& aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal())) {
1093 foundAccessibleFrame
= true;
1099 if (!foundAccessibleFrame
) {
1100 return NS_OK
; // deny the load.
1104 nsCOMPtr
<nsIHandlerInfo
> handler
;
1105 rv
= GetProtocolHandlerInfo(scheme
, getter_AddRefs(handler
));
1106 NS_ENSURE_SUCCESS(rv
, rv
);
1108 nsCOMPtr
<nsIContentDispatchChooser
> chooser
=
1109 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv
);
1110 NS_ENSURE_SUCCESS(rv
, rv
);
1112 return chooser
->HandleURI(
1113 handler
, escapedURI
,
1114 aRedirectPrincipal
? aRedirectPrincipal
: aTriggeringPrincipal
,
1115 aBrowsingContext
, aTriggeredExternally
);
1118 //////////////////////////////////////////////////////////////////////////////////////////////////////
1119 // Methods related to deleting temporary files on exit
1120 //////////////////////////////////////////////////////////////////////////////////////////////////////
1123 nsresult
nsExternalHelperAppService::DeleteTemporaryFileHelper(
1124 nsIFile
* aTemporaryFile
, nsCOMArray
<nsIFile
>& aFileList
) {
1125 bool isFile
= false;
1127 // as a safety measure, make sure the nsIFile is really a file and not a
1128 // directory object.
1129 aTemporaryFile
->IsFile(&isFile
);
1130 if (!isFile
) return NS_OK
;
1132 aFileList
.AppendObject(aTemporaryFile
);
1138 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile
* aTemporaryFile
) {
1139 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryFilesList
);
1143 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1144 nsIFile
* aTemporaryFile
) {
1145 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryPrivateFilesList
);
1148 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1149 nsCOMArray
<nsIFile
>& fileList
) {
1150 int32_t numEntries
= fileList
.Count();
1152 for (int32_t index
= 0; index
< numEntries
; index
++) {
1153 localFile
= fileList
[index
];
1155 // First make the file writable, since the temp file is probably readonly.
1156 localFile
->SetPermissions(0600);
1157 localFile
->Remove(false);
1164 void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1165 ExpungeTemporaryFilesHelper(mTemporaryFilesList
);
1168 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1169 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList
);
1172 static const char kExternalWarningPrefPrefix
[] =
1173 "network.protocol-handler.warn-external.";
1174 static const char kExternalWarningDefaultPref
[] =
1175 "network.protocol-handler.warn-external-default";
1178 nsExternalHelperAppService::GetProtocolHandlerInfo(
1179 const nsACString
& aScheme
, nsIHandlerInfo
** aHandlerInfo
) {
1180 // XXX enterprise customers should be able to turn this support off with a
1181 // single master pref (maybe use one of the "exposed" prefs here?)
1184 nsresult rv
= GetProtocolHandlerInfoFromOS(aScheme
, &exists
, aHandlerInfo
);
1185 if (NS_FAILED(rv
)) {
1186 // Either it knows nothing, or we ran out of memory
1187 return NS_ERROR_FAILURE
;
1190 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1191 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1193 bool hasHandler
= false;
1194 (void)handlerSvc
->Exists(*aHandlerInfo
, &hasHandler
);
1196 rv
= handlerSvc
->FillHandlerInfo(*aHandlerInfo
, ""_ns
);
1197 if (NS_SUCCEEDED(rv
)) return NS_OK
;
1201 return SetProtocolHandlerDefaults(*aHandlerInfo
, exists
);
1205 nsExternalHelperAppService::SetProtocolHandlerDefaults(
1206 nsIHandlerInfo
* aHandlerInfo
, bool aOSHandlerExists
) {
1207 // this type isn't in our database, so we've only got an OS default handler,
1210 if (aOSHandlerExists
) {
1211 // we've got a default, so use it
1212 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::useSystemDefault
);
1214 // whether or not to ask the user depends on the warning preference
1215 nsAutoCString scheme
;
1216 aHandlerInfo
->GetType(scheme
);
1218 nsAutoCString
warningPref(kExternalWarningPrefPrefix
);
1219 warningPref
+= scheme
;
1221 if (NS_FAILED(Preferences::GetBool(warningPref
.get(), &warn
))) {
1222 // no scheme-specific value, check the default
1223 warn
= Preferences::GetBool(kExternalWarningDefaultPref
, true);
1225 aHandlerInfo
->SetAlwaysAskBeforeHandling(warn
);
1227 // If no OS default existed, we set the preferred action to alwaysAsk.
1228 // This really means not initialized (i.e. there's no available handler)
1229 // to all the code...
1230 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::alwaysAsk
);
1236 // XPCOM profile change observer
1238 nsExternalHelperAppService::Observe(nsISupports
* aSubject
, const char* aTopic
,
1239 const char16_t
* someData
) {
1240 if (!strcmp(aTopic
, "profile-before-change")) {
1241 ExpungeTemporaryFiles();
1242 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
1243 ExpungeTemporaryPrivateFiles();
1248 //////////////////////////////////////////////////////////////////////////////////////////////////////
1249 // begin external app handler implementation
1250 //////////////////////////////////////////////////////////////////////////////////////////////////////
1252 NS_IMPL_ADDREF(nsExternalAppHandler
)
1253 NS_IMPL_RELEASE(nsExternalAppHandler
)
1255 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler
)
1256 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStreamListener
)
1257 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
1258 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
1259 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher
)
1260 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
1261 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver
)
1262 NS_INTERFACE_MAP_ENTRY(nsINamed
)
1263 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler
)
1264 NS_INTERFACE_MAP_END
1266 nsExternalAppHandler::nsExternalAppHandler(
1267 nsIMIMEInfo
* aMIMEInfo
, const nsAString
& aFileExtension
,
1268 BrowsingContext
* aBrowsingContext
, nsIInterfaceRequestor
* aWindowContext
,
1269 nsExternalHelperAppService
* aExtProtSvc
,
1270 const nsAString
& aSuggestedFileName
, uint32_t aReason
, bool aForceSave
)
1271 : mMimeInfo(aMIMEInfo
),
1272 mBrowsingContext(aBrowsingContext
),
1273 mWindowContext(aWindowContext
),
1274 mSuggestedFileName(aSuggestedFileName
),
1275 mForceSave(aForceSave
),
1276 mForceSaveInternallyHandled(false),
1278 mStopRequestIssued(false),
1279 mIsFileChannel(false),
1280 mShouldCloseWindow(false),
1281 mHandleInternally(false),
1282 mDialogShowing(false),
1284 mTempFileIsExecutable(false),
1285 mTimeDownloadStarted(0),
1289 mDialogProgressListener(nullptr),
1292 mExtProtSvc(aExtProtSvc
) {
1293 // make sure the extention includes the '.'
1294 if (!aFileExtension
.IsEmpty() && aFileExtension
.First() != '.') {
1295 mFileExtension
= char16_t('.');
1297 mFileExtension
.Append(aFileExtension
);
1299 mBufferSize
= Preferences::GetUint("network.buffer.cache.size", 4096);
1302 nsExternalAppHandler::~nsExternalAppHandler() {
1303 MOZ_ASSERT(!mSaver
, "Saver should hold a reference to us until deleted");
1306 void nsExternalAppHandler::DidDivertRequest(nsIRequest
* request
) {
1307 MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1308 // Remove our request from the child loadGroup
1309 RetargetLoadNotifications(request
);
1312 NS_IMETHODIMP
nsExternalAppHandler::SetWebProgressListener(
1313 nsIWebProgressListener2
* aWebProgressListener
) {
1314 // This is always called by nsHelperDlg.js. Go ahead and register the
1315 // progress listener. At this point, we don't have mTransfer.
1316 mDialogProgressListener
= aWebProgressListener
;
1320 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFile(nsIFile
** aTarget
) {
1321 if (mFinalFileDestination
)
1322 *aTarget
= mFinalFileDestination
;
1324 *aTarget
= mTempFile
;
1326 NS_IF_ADDREF(*aTarget
);
1330 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec
) {
1331 // Use the real target if it's been set
1332 if (mFinalFileDestination
) return mFinalFileDestination
->IsExecutable(aExec
);
1334 // Otherwise, use the stored executable-ness of the temporary
1335 *aExec
= mTempFileIsExecutable
;
1339 NS_IMETHODIMP
nsExternalAppHandler::GetTimeDownloadStarted(PRTime
* aTime
) {
1340 *aTime
= mTimeDownloadStarted
;
1344 NS_IMETHODIMP
nsExternalAppHandler::GetContentLength(int64_t* aContentLength
) {
1345 *aContentLength
= mContentLength
;
1349 NS_IMETHODIMP
nsExternalAppHandler::GetBrowsingContextId(
1350 uint64_t* aBrowsingContextId
) {
1351 *aBrowsingContextId
= mBrowsingContext
? mBrowsingContext
->Id() : 0;
1355 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest
* request
) {
1356 // we are going to run the downloading of the helper app in our own little
1357 // docloader / load group context. so go ahead and force the creation of a
1358 // load group and doc loader for us to use...
1359 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1360 if (!aChannel
) return;
1362 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
1364 nsCOMPtr
<nsILoadGroup
> oldLoadGroup
;
1365 aChannel
->GetLoadGroup(getter_AddRefs(oldLoadGroup
));
1368 oldLoadGroup
->RemoveRequest(request
, nullptr, NS_BINDING_RETARGETED
);
1371 aChannel
->SetLoadGroup(nullptr);
1372 aChannel
->SetNotificationCallbacks(nullptr);
1374 nsCOMPtr
<nsIPrivateBrowsingChannel
> pbChannel
= do_QueryInterface(aChannel
);
1376 pbChannel
->SetPrivate(isPrivate
);
1380 nsresult
nsExternalAppHandler::SetUpTempFile(nsIChannel
* aChannel
) {
1381 // First we need to try to get the destination directory for the temporary
1383 nsresult rv
= GetDownloadDirectory(getter_AddRefs(mTempFile
));
1384 NS_ENSURE_SUCCESS(rv
, rv
);
1386 // At this point, we do not have a filename for the temp file. For security
1387 // purposes, this cannot be predictable, so we must use a cryptographic
1388 // quality PRNG to generate one.
1389 nsAutoCString tempLeafName
;
1390 rv
= GenerateRandomName(tempLeafName
);
1391 NS_ENSURE_SUCCESS(rv
, rv
);
1393 // now append our extension.
1395 mMimeInfo
->GetPrimaryExtension(ext
);
1396 if (!ext
.IsEmpty()) {
1397 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1398 if (ext
.First() != '.') tempLeafName
.Append('.');
1399 tempLeafName
.Append(ext
);
1402 // We need to temporarily create a dummy file with the correct
1403 // file extension to determine the executable-ness, so do this before adding
1404 // the extra .part extension.
1405 nsCOMPtr
<nsIFile
> dummyFile
;
1406 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dummyFile
));
1407 NS_ENSURE_SUCCESS(rv
, rv
);
1409 // Set the file name without .part
1410 rv
= dummyFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1411 NS_ENSURE_SUCCESS(rv
, rv
);
1412 rv
= dummyFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1413 NS_ENSURE_SUCCESS(rv
, rv
);
1415 // Store executable-ness then delete
1416 dummyFile
->IsExecutable(&mTempFileIsExecutable
);
1417 dummyFile
->Remove(false);
1419 // Add an additional .part to prevent the OS from running this file in the
1420 // default application.
1421 tempLeafName
.AppendLiteral(".part");
1423 rv
= mTempFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1424 // make this file unique!!!
1425 NS_ENSURE_SUCCESS(rv
, rv
);
1426 rv
= mTempFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1427 NS_ENSURE_SUCCESS(rv
, rv
);
1429 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1430 // This is a bit broken in the case when createUnique actually had to append
1431 // some numbers, because then we now have a filename like foo.bar-1.part and
1432 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1433 // this. But the other options are all bad too.... Ideally we'd have a way
1434 // to tell createUnique to put its unique marker before the extension that
1435 // comes before ".part" or something.
1436 rv
= mTempFile
->GetLeafName(mTempLeafName
);
1437 NS_ENSURE_SUCCESS(rv
, rv
);
1439 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName
, u
".part"_ns
),
1440 NS_ERROR_UNEXPECTED
);
1442 // Strip off the ".part" from mTempLeafName
1443 mTempLeafName
.Truncate(mTempLeafName
.Length() - ArrayLength(".part") + 1);
1445 MOZ_ASSERT(!mSaver
, "Output file initialization called more than once!");
1447 do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID
, &rv
);
1448 NS_ENSURE_SUCCESS(rv
, rv
);
1450 rv
= mSaver
->SetObserver(this);
1451 if (NS_FAILED(rv
)) {
1456 rv
= mSaver
->EnableSha256();
1457 NS_ENSURE_SUCCESS(rv
, rv
);
1459 rv
= mSaver
->EnableSignatureInfo();
1460 NS_ENSURE_SUCCESS(rv
, rv
);
1461 LOG("Enabled hashing and signature verification");
1463 rv
= mSaver
->SetTarget(mTempFile
, false);
1464 NS_ENSURE_SUCCESS(rv
, rv
);
1469 void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1470 nsIRequest
* aRequest
) {
1471 MOZ_ASSERT(aRequest
);
1473 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface(aRequest
);
1478 // Turn off content encoding conversions if needed
1479 bool applyConversion
= true;
1481 // First, check to see if conversion is already disabled. If so, we
1482 // have nothing to do here.
1483 encChannel
->GetApplyConversion(&applyConversion
);
1484 if (!applyConversion
) {
1488 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(mSourceUrl
));
1490 nsAutoCString extension
;
1491 sourceURL
->GetFileExtension(extension
);
1492 if (!extension
.IsEmpty()) {
1493 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
1494 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
1497 nsresult rv
= encEnum
->HasMore(&hasMore
);
1498 if (NS_SUCCEEDED(rv
) && hasMore
) {
1499 nsAutoCString encType
;
1500 rv
= encEnum
->GetNext(encType
);
1501 if (NS_SUCCEEDED(rv
) && !encType
.IsEmpty()) {
1502 MOZ_ASSERT(mExtProtSvc
);
1503 mExtProtSvc
->ApplyDecodingForExtension(extension
, encType
,
1511 encChannel
->SetApplyConversion(applyConversion
);
1514 already_AddRefed
<nsIInterfaceRequestor
>
1515 nsExternalAppHandler::GetDialogParent() {
1516 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= mWindowContext
;
1518 if (!dialogParent
&& mBrowsingContext
) {
1519 dialogParent
= do_QueryInterface(mBrowsingContext
->GetDOMWindow());
1521 if (!dialogParent
&& mBrowsingContext
&& XRE_IsParentProcess()) {
1522 RefPtr
<Element
> element
= mBrowsingContext
->Top()->GetEmbedderElement();
1524 dialogParent
= do_QueryInterface(element
->OwnerDoc()->GetWindow());
1527 return dialogParent
.forget();
1530 NS_IMETHODIMP
nsExternalAppHandler::OnStartRequest(nsIRequest
* request
) {
1531 MOZ_ASSERT(request
, "OnStartRequest without request?");
1533 // Set mTimeDownloadStarted here as the download has already started and
1534 // we want to record the start time before showing the filepicker.
1535 mTimeDownloadStarted
= PR_Now();
1539 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1542 nsAutoCString MIMEType
;
1544 mMimeInfo
->GetMIMEType(MIMEType
);
1548 aChannel
->GetURI(getter_AddRefs(mSourceUrl
));
1549 // HTTPS-Only/HTTPS-FirstMode tries to upgrade connections to https. Once
1550 // the download is in progress we set that flag so that timeout counter
1551 // measures do not kick in.
1552 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1553 bool isPrivateWin
= loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
1554 if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin
) ||
1555 nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin
)) {
1556 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
1557 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS
;
1558 loadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
1562 if (!mForceSave
&& StaticPrefs::browser_download_enable_spam_prevention() &&
1563 IsDownloadSpam(aChannel
)) {
1567 mDownloadClassification
=
1568 nsContentSecurityUtils::ClassifyDownload(aChannel
, MIMEType
);
1570 if (mDownloadClassification
== nsITransfer::DOWNLOAD_FORBIDDEN
) {
1571 // If the download is rated as forbidden,
1572 // cancel the request so no ui knows about this.
1574 request
->Cancel(NS_ERROR_ABORT
);
1578 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(request
));
1579 mIsFileChannel
= fileChan
!= nullptr;
1580 if (!mIsFileChannel
) {
1581 // It's possible that this request came from the child process and the
1582 // file channel actually lives there. If this returns true, then our
1583 // mSourceUrl will be an nsIFileURL anyway.
1584 nsCOMPtr
<dom::nsIExternalHelperAppParent
> parent(
1585 do_QueryInterface(request
));
1586 mIsFileChannel
= parent
&& parent
->WasFileChannel();
1589 // Get content length
1591 aChannel
->GetContentLength(&mContentLength
);
1594 if (mBrowsingContext
) {
1595 mMaybeCloseWindowHelper
= new MaybeCloseWindowHelper(mBrowsingContext
);
1596 mMaybeCloseWindowHelper
->SetShouldCloseWindow(mShouldCloseWindow
);
1597 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(request
, &rv
));
1598 // Determine whether a new window was opened specifically for this request
1602 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
, &tmp
))) {
1603 mMaybeCloseWindowHelper
->SetShouldCloseWindow(tmp
);
1608 // retarget all load notifications to our docloader instead of the original
1609 // window's docloader...
1610 RetargetLoadNotifications(request
);
1612 // Close the underlying DOMWindow if it was opened specifically for the
1613 // download. We don't run this in the content process, since we have
1614 // an instance running in the parent as well, which will handle this
1616 if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper
) {
1617 mBrowsingContext
= mMaybeCloseWindowHelper
->MaybeCloseWindow();
1620 // In an IPC setting, we're allowing the child process, here, to make
1621 // decisions about decoding the channel (e.g. decompression). It will
1622 // still forward the decoded (uncompressed) data back to the parent.
1623 // Con: Uncompressed data means more IPC overhead.
1624 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1625 // Parent process doesn't need to expect CPU time on decompression.
1626 MaybeApplyDecodingForExtension(aChannel
);
1628 // At this point, the child process has done everything it can usefully do
1629 // for OnStartRequest.
1630 if (XRE_IsContentProcess()) {
1634 rv
= SetUpTempFile(aChannel
);
1635 if (NS_FAILED(rv
)) {
1636 nsresult transferError
= rv
;
1638 rv
= CreateFailedTransfer();
1639 if (NS_FAILED(rv
)) {
1640 LOG("Failed to create transfer to report failure."
1641 "Will fallback to prompter!");
1645 request
->Cancel(transferError
);
1648 if (mTempFile
) mTempFile
->GetPath(path
);
1650 SendStatusChange(kWriteError
, transferError
, request
, path
);
1655 // Inform channel it is open on behalf of a download to throttle it during
1656 // page loads and prevent its caching.
1657 nsCOMPtr
<nsIHttpChannelInternal
> httpInternal
= do_QueryInterface(aChannel
);
1659 rv
= httpInternal
->SetChannelIsForDownload(true);
1660 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1663 if (mSourceUrl
->SchemeIs("data")) {
1664 // In case we're downloading a data:// uri
1665 // we don't want to apply AllowTopLevelNavigationToDataURI.
1666 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1667 loadInfo
->SetForceAllowDataURI(true);
1670 // now that the temp file is set up, find out if we need to invoke a dialog
1671 // asking the user what they want us to do with this content...
1673 // We can get here for three reasons: "can't handle", "sniffed type", or
1674 // "server sent content-disposition:attachment". In the first case we want
1675 // to honor the user's "always ask" pref; in the other two cases we want to
1676 // honor it only if the default action is "save". Opening attachments in
1677 // helper apps by default breaks some websites (especially if the attachment
1678 // is one part of a multipart document). Opening sniffed content in helper
1679 // apps by default introduces security holes that we'd rather not have.
1681 // So let's find out whether the user wants to be prompted. If he does not,
1682 // check mReason and the preferred action to see what we should do.
1684 bool alwaysAsk
= true;
1685 mMimeInfo
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1687 // But we *don't* ask if this mimeInfo didn't come from
1688 // our user configuration datastore and the user has said
1689 // at some point in the distant past that they don't
1690 // want to be asked. The latter fact would have been
1691 // stored in pref strings back in the old days.
1693 bool mimeTypeIsInDatastore
= false;
1694 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1695 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1697 handlerSvc
->Exists(mMimeInfo
, &mimeTypeIsInDatastore
);
1699 if (!handlerSvc
|| !mimeTypeIsInDatastore
) {
1700 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF
,
1702 // Don't need to ask after all.
1704 // Make sure action matches pref (save to disk).
1705 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
1706 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF
,
1708 // Don't need to ask after all.
1712 } else if (MIMEType
.EqualsLiteral("text/plain")) {
1714 mMimeInfo
->GetPrimaryExtension(ext
);
1715 // If people are sending us ApplicationReputation-eligible files with
1716 // text/plain mimetypes, enforce asking the user what to do.
1717 if (!ext
.IsEmpty()) {
1718 nsAutoCString
dummyFileName("f");
1719 if (ext
.First() != '.') {
1720 dummyFileName
.Append(".");
1722 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1723 nsCOMPtr
<nsIApplicationReputationService
> appRep
=
1724 components::ApplicationReputation::Service();
1725 appRep
->IsBinary(dummyFileName
+ ext
, &alwaysAsk
);
1729 int32_t action
= nsIMIMEInfo::saveToDisk
;
1730 mMimeInfo
->GetPreferredAction(&action
);
1732 bool forcePrompt
= mReason
== nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
1734 // OK, now check why we're here
1735 if (!alwaysAsk
&& forcePrompt
) {
1736 // Force asking if we're not saving. See comment back when we fetched the
1737 // alwaysAsk boolean for details.
1738 alwaysAsk
= (action
!= nsIMIMEInfo::saveToDisk
);
1741 bool shouldAutomaticallyHandleInternally
=
1742 action
== nsIMIMEInfo::handleInternally
;
1745 uint32_t disposition
= -1;
1746 aChannel
->GetContentDisposition(&disposition
);
1747 mForceSaveInternallyHandled
=
1748 shouldAutomaticallyHandleInternally
&&
1749 disposition
== nsIChannel::DISPOSITION_ATTACHMENT
&&
1750 mozilla::StaticPrefs::
1751 browser_download_force_save_internally_handled_attachments();
1754 // If we're not asking, check we actually know what to do:
1756 alwaysAsk
= action
!= nsIMIMEInfo::saveToDisk
&&
1757 action
!= nsIMIMEInfo::useHelperApp
&&
1758 action
!= nsIMIMEInfo::useSystemDefault
&&
1759 !shouldAutomaticallyHandleInternally
;
1762 // If we're handling with the OS default and we are that default, force
1763 // asking, so we don't end up in an infinite loop:
1764 if (!alwaysAsk
&& action
== nsIMIMEInfo::useSystemDefault
) {
1765 bool areOSDefault
= false;
1766 alwaysAsk
= NS_SUCCEEDED(mMimeInfo
->IsCurrentAppOSDefault(&areOSDefault
)) &&
1768 } else if (!alwaysAsk
&& action
== nsIMIMEInfo::useHelperApp
) {
1769 nsCOMPtr
<nsIHandlerApp
> preferredApp
;
1770 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(preferredApp
));
1771 nsCOMPtr
<nsILocalHandlerApp
> handlerApp
= do_QueryInterface(preferredApp
);
1773 nsCOMPtr
<nsIFile
> executable
;
1774 handlerApp
->GetExecutable(getter_AddRefs(executable
));
1775 nsCOMPtr
<nsIFile
> ourselves
;
1777 // Despite the name, this really just fetches an nsIFile...
1778 NS_SUCCEEDED(NS_GetSpecialDirectory(XRE_EXECUTABLE_FILE
,
1779 getter_AddRefs(ourselves
)))) {
1780 ourselves
= nsMIMEInfoBase::GetCanonicalExecutable(ourselves
);
1781 executable
= nsMIMEInfoBase::GetCanonicalExecutable(executable
);
1782 bool isSameApp
= false;
1784 NS_FAILED(executable
->Equals(ourselves
, &isSameApp
)) || isSameApp
;
1789 // if we were told that we _must_ save to disk without asking, all the stuff
1790 // before this is irrelevant; override it
1791 if (mForceSave
|| mForceSaveInternallyHandled
) {
1793 action
= nsIMIMEInfo::saveToDisk
;
1794 shouldAutomaticallyHandleInternally
= false;
1796 // Additionally, if we are asked by the OS to open a local file,
1797 // automatically downloading it to create a second copy of that file doesn't
1798 // really make sense. We should ask the user what they want to do.
1799 if (mSourceUrl
->SchemeIs("file") && !alwaysAsk
&&
1800 action
== nsIMIMEInfo::saveToDisk
) {
1804 // If adding new checks, make sure this is the last check before telemetry
1805 // and going ahead with opening the file!
1807 /* We need to see whether the file we've got here could be
1808 * executable. If it could, we had better not try to open it!
1809 * We can skip this check, though, if we have a setting to open in a
1812 if (!alwaysAsk
&& action
!= nsIMIMEInfo::saveToDisk
&&
1813 !shouldAutomaticallyHandleInternally
) {
1814 nsCOMPtr
<nsIHandlerApp
> prefApp
;
1815 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(prefApp
));
1816 if (action
!= nsIMIMEInfo::useHelperApp
|| !prefApp
) {
1817 nsCOMPtr
<nsIFile
> fileToTest
;
1818 GetTargetFile(getter_AddRefs(fileToTest
));
1821 rv
= fileToTest
->IsExecutable(&isExecutable
);
1822 if (NS_FAILED(rv
) || mTempFileIsExecutable
||
1823 isExecutable
) { // checking NS_FAILED, because paranoia is good
1826 } else { // Paranoia is good here too, though this really should not
1829 "GetDownloadInfo returned a null file after the temp file has been "
1838 // Display the dialog
1839 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1840 NS_ENSURE_SUCCESS(rv
, rv
);
1842 // this will create a reference cycle (the dialog holds a reference to us as
1843 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1844 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
1845 // Don't pop up the downloads panel since we're already going to pop up the
1846 // UCT dialog for basically the same effect.
1847 mDialogShowing
= true;
1848 rv
= mDialog
->Show(this, dialogParent
, mReason
);
1850 // what do we do if the dialog failed? I guess we should call Cancel and
1851 // abort the load....
1853 // We need to do the save/open immediately, then.
1854 if (action
== nsIMIMEInfo::useHelperApp
||
1855 action
== nsIMIMEInfo::useSystemDefault
||
1856 shouldAutomaticallyHandleInternally
) {
1857 // Check if the file is local, in which case just launch it from where it
1858 // is. Otherwise, set the file to launch once it's finished downloading.
1859 rv
= mIsFileChannel
? LaunchLocalFile()
1860 : SetDownloadToLaunch(
1861 shouldAutomaticallyHandleInternally
, nullptr);
1863 rv
= PromptForSaveDestination();
1869 bool nsExternalAppHandler::IsDownloadSpam(nsIChannel
* aChannel
) {
1870 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1871 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
1872 mozilla::services::GetPermissionManager();
1873 nsCOMPtr
<nsIPrincipal
> principal
= loadInfo
->TriggeringPrincipal();
1874 bool exactHostMatch
= false;
1875 constexpr auto type
= "automatic-download"_ns
;
1876 nsCOMPtr
<nsIPermission
> permission
;
1878 permissionManager
->GetPermissionObject(principal
, type
, exactHostMatch
,
1879 getter_AddRefs(permission
));
1882 uint32_t capability
;
1883 permission
->GetCapability(&capability
);
1884 if (capability
== nsIPermissionManager::DENY_ACTION
) {
1886 aChannel
->Cancel(NS_ERROR_ABORT
);
1889 if (capability
== nsIPermissionManager::ALLOW_ACTION
) {
1892 // If no action is set (i.e: null), we set PROMPT_ACTION by default,
1893 // which will notify the Downloads UI to open the panel on the next request.
1894 if (capability
== nsIPermissionManager::PROMPT_ACTION
) {
1895 nsCOMPtr
<nsIObserverService
> observerService
=
1896 mozilla::services::GetObserverService();
1897 RefPtr
<BrowsingContext
> browsingContext
;
1898 loadInfo
->GetBrowsingContext(getter_AddRefs(browsingContext
));
1900 nsAutoCString cStringURI
;
1901 loadInfo
->TriggeringPrincipal()->GetPrePath(cStringURI
);
1902 observerService
->NotifyObservers(
1903 browsingContext
, "blocked-automatic-download",
1904 NS_ConvertASCIItoUTF16(cStringURI
.get()).get());
1905 // FIXME: In order to escape memory leaks, currently we cancel blocked
1906 // downloads. This is temporary solution, because download data should be
1907 // kept in order to restart the blocked download.
1909 aChannel
->Cancel(NS_ERROR_ABORT
);
1914 if (!loadInfo
->GetHasValidUserGestureActivation()) {
1915 permissionManager
->AddFromPrincipal(
1916 principal
, type
, nsIPermissionManager::PROMPT_ACTION
,
1917 nsIPermissionManager::EXPIRE_NEVER
, 0 /* expire time */);
1923 // Convert error info into proper message text and send OnStatusChange
1924 // notification to the dialog progress listener or nsITransfer implementation.
1925 void nsExternalAppHandler::SendStatusChange(ErrorType type
, nsresult rv
,
1926 nsIRequest
* aRequest
,
1927 const nsString
& path
) {
1928 const char* msgId
= nullptr;
1930 case NS_ERROR_OUT_OF_MEMORY
:
1935 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1936 // Out of space on target volume.
1940 case NS_ERROR_FILE_READ_ONLY
:
1941 // Attempt to write to read/only file.
1945 case NS_ERROR_FILE_ACCESS_DENIED
:
1946 if (type
== kWriteError
) {
1947 // Attempt to write without sufficient permissions.
1948 #if defined(ANDROID)
1949 // On Android this means the SD card is present but
1950 // unavailable (read-only).
1951 msgId
= "SDAccessErrorCardReadOnly";
1953 msgId
= "accessError";
1956 msgId
= "launchError";
1960 case NS_ERROR_FILE_NOT_FOUND
:
1961 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
1962 // Helper app not found, let's verify this happened on launch
1963 if (type
== kLaunchError
) {
1964 msgId
= "helperAppNotFound";
1967 #if defined(ANDROID)
1968 else if (type
== kWriteError
) {
1969 // On Android this means the SD card is missing (not in
1971 msgId
= "SDAccessErrorCardMissing";
1978 // Generic read/write/launch error message.
1981 msgId
= "readError";
1984 msgId
= "writeError";
1987 msgId
= "launchError";
1994 nsExternalHelperAppService::sLog
, LogLevel::Error
,
1995 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32
"\n",
1996 msgId
, type
, mDialogProgressListener
.get(), mTransfer
.get(),
1997 static_cast<uint32_t>(rv
)));
1999 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Error
,
2000 (" path='%s'\n", NS_ConvertUTF16toUTF8(path
).get()));
2002 // Get properties file bundle and extract status string.
2003 nsCOMPtr
<nsIStringBundleService
> stringService
=
2004 mozilla::components::StringBundle::Service();
2005 if (stringService
) {
2006 nsCOMPtr
<nsIStringBundle
> bundle
;
2007 if (NS_SUCCEEDED(stringService
->CreateBundle(
2008 "chrome://global/locale/nsWebBrowserPersist.properties",
2009 getter_AddRefs(bundle
)))) {
2010 nsAutoString msgText
;
2011 AutoTArray
<nsString
, 1> strings
= {path
};
2012 if (NS_SUCCEEDED(bundle
->FormatStringFromName(msgId
, strings
, msgText
))) {
2013 if (mDialogProgressListener
) {
2014 // We have a listener, let it handle the error.
2015 mDialogProgressListener
->OnStatusChange(
2016 nullptr, (type
== kReadError
) ? aRequest
: nullptr, rv
,
2018 } else if (mTransfer
) {
2019 mTransfer
->OnStatusChange(nullptr,
2020 (type
== kReadError
) ? aRequest
: nullptr,
2022 } else if (XRE_IsParentProcess()) {
2023 // We don't have a listener. Simply show the alert ourselves.
2024 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2026 nsCOMPtr
<nsIPrompt
> prompter(do_GetInterface(dialogParent
, &qiRv
));
2028 bundle
->FormatStringFromName("title", strings
, title
);
2031 nsExternalHelperAppService::sLog
, LogLevel::Debug
,
2032 ("mBrowsingContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
2033 ", title='%s', msg='%s'",
2034 mBrowsingContext
.get(), prompter
.get(),
2035 static_cast<uint32_t>(qiRv
), NS_ConvertUTF16toUTF8(title
).get(),
2036 NS_ConvertUTF16toUTF8(msgText
).get()));
2038 // If we didn't have a prompter we will try and get a window
2039 // instead, get it's docshell and use it to alert the user.
2041 nsCOMPtr
<nsPIDOMWindowOuter
> window(do_GetInterface(dialogParent
));
2042 if (!window
|| !window
->GetDocShell()) {
2046 prompter
= do_GetInterface(window
->GetDocShell(), &qiRv
);
2048 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Debug
,
2049 ("No prompter from mBrowsingContext, using DocShell, "
2050 "window=0x%p, docShell=0x%p, "
2051 "prompter=0x%p, qi rv=0x%08" PRIX32
,
2052 window
.get(), window
->GetDocShell(), prompter
.get(),
2053 static_cast<uint32_t>(qiRv
)));
2055 // If we still don't have a prompter, there's nothing else we
2056 // can do so just return.
2058 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Error
,
2059 ("No prompter from DocShell, no way to alert user"));
2064 // We should always have a prompter at this point.
2065 prompter
->Alert(title
.get(), msgText
.get());
2073 nsExternalAppHandler::OnDataAvailable(nsIRequest
* request
,
2074 nsIInputStream
* inStr
,
2075 uint64_t sourceOffset
, uint32_t count
) {
2076 nsresult rv
= NS_OK
;
2077 // first, check to see if we've been canceled....
2078 if (mCanceled
|| !mSaver
) {
2079 // then go cancel our underlying channel too
2080 return request
->Cancel(NS_BINDING_ABORTED
);
2083 // read the data out of the stream and write it to the temp file.
2087 nsCOMPtr
<nsIStreamListener
> saver
= do_QueryInterface(mSaver
);
2088 rv
= saver
->OnDataAvailable(request
, inStr
, sourceOffset
, count
);
2089 if (NS_SUCCEEDED(rv
)) {
2090 // Send progress notification.
2092 mTransfer
->OnProgressChange64(nullptr, request
, mProgress
,
2093 mContentLength
, mProgress
,
2097 // An error occurred, notify listener.
2098 nsAutoString tempFilePath
;
2100 mTempFile
->GetPath(tempFilePath
);
2102 SendStatusChange(kReadError
, rv
, request
, tempFilePath
);
2104 // Cancel the download.
2111 NS_IMETHODIMP
nsExternalAppHandler::OnStopRequest(nsIRequest
* request
,
2113 LOG("nsExternalAppHandler::OnStopRequest\n"
2114 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08" PRIX32
"\n",
2115 mCanceled
, mTransfer
.get(), static_cast<uint32_t>(aStatus
));
2117 mStopRequestIssued
= true;
2119 // Cancel if the request did not complete successfully.
2120 if (!mCanceled
&& NS_FAILED(aStatus
)) {
2121 // Send error notification.
2122 nsAutoString tempFilePath
;
2123 if (mTempFile
) mTempFile
->GetPath(tempFilePath
);
2124 SendStatusChange(kReadError
, aStatus
, request
, tempFilePath
);
2129 // first, check to see if we've been canceled....
2130 if (mCanceled
|| !mSaver
) {
2134 return mSaver
->Finish(NS_OK
);
2138 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver
* aSaver
,
2144 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver
* aSaver
,
2146 LOG("nsExternalAppHandler::OnSaveComplete\n"
2147 " aSaver=0x%p, aStatus=0x%08" PRIX32
", mCanceled=%d, mTransfer=0x%p\n",
2148 aSaver
, static_cast<uint32_t>(aStatus
), mCanceled
, mTransfer
.get());
2151 // Save the hash and signature information
2152 (void)mSaver
->GetSha256Hash(mHash
);
2153 (void)mSaver
->GetSignatureInfo(mSignatureInfo
);
2155 // Free the reference that the saver keeps on us, even if we couldn't get
2159 // Save the redirect information.
2160 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2162 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
2163 nsresult rv
= NS_OK
;
2164 nsCOMPtr
<nsIMutableArray
> redirectChain
=
2165 do_CreateInstance(NS_ARRAY_CONTRACTID
, &rv
);
2166 NS_ENSURE_SUCCESS(rv
, rv
);
2167 LOG("nsExternalAppHandler: Got %zu redirects\n",
2168 loadInfo
->RedirectChain().Length());
2169 for (nsIRedirectHistoryEntry
* entry
: loadInfo
->RedirectChain()) {
2170 redirectChain
->AppendElement(entry
);
2172 mRedirects
= redirectChain
;
2175 if (NS_FAILED(aStatus
)) {
2177 mTempFile
->GetPath(path
);
2179 // It may happen when e10s is enabled that there will be no transfer
2180 // object available to communicate status as expected by the system.
2181 // Let's try and create a temporary transfer object to take care of this
2182 // for us, we'll fall back to using the prompt service if we absolutely
2185 // We don't care if this fails.
2186 CreateFailedTransfer();
2189 SendStatusChange(kWriteError
, aStatus
, nullptr, path
);
2190 if (!mCanceled
) Cancel(aStatus
);
2195 // Notify the transfer object that we are done if the user has chosen an
2196 // action. If the user hasn't chosen an action, the progress listener
2197 // (nsITransfer) will be notified in CreateTransfer.
2199 NotifyTransfer(aStatus
);
2205 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus
) {
2206 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2207 MOZ_ASSERT(mTransfer
, "We must have an nsITransfer");
2209 LOG("Notifying progress listener");
2211 if (NS_SUCCEEDED(aStatus
)) {
2212 (void)mTransfer
->SetSha256Hash(mHash
);
2213 (void)mTransfer
->SetSignatureInfo(mSignatureInfo
);
2214 (void)mTransfer
->SetRedirects(mRedirects
);
2215 (void)mTransfer
->OnProgressChange64(
2216 nullptr, nullptr, mProgress
, mContentLength
, mProgress
, mContentLength
);
2219 (void)mTransfer
->OnStateChange(nullptr, nullptr,
2220 nsIWebProgressListener::STATE_STOP
|
2221 nsIWebProgressListener::STATE_IS_REQUEST
|
2222 nsIWebProgressListener::STATE_IS_NETWORK
,
2225 // This nsITransfer object holds a reference to us (we are its observer), so
2226 // we need to release the reference to break a reference cycle (and therefore
2227 // to prevent leaking). We do this even if the previous calls failed.
2228 mTransfer
= nullptr;
2231 NS_IMETHODIMP
nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo
** aMIMEInfo
) {
2232 *aMIMEInfo
= mMimeInfo
;
2233 NS_ADDREF(*aMIMEInfo
);
2237 NS_IMETHODIMP
nsExternalAppHandler::GetSource(nsIURI
** aSourceURI
) {
2238 NS_ENSURE_ARG(aSourceURI
);
2239 *aSourceURI
= mSourceUrl
;
2240 NS_IF_ADDREF(*aSourceURI
);
2244 NS_IMETHODIMP
nsExternalAppHandler::GetSuggestedFileName(
2245 nsAString
& aSuggestedFileName
) {
2246 aSuggestedFileName
= mSuggestedFileName
;
2250 nsresult
nsExternalAppHandler::CreateTransfer() {
2251 LOG("nsExternalAppHandler::CreateTransfer");
2253 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2254 // We are back from the helper app dialog (where the user chooses to save or
2255 // open), but we aren't done processing the load. in this case, throw up a
2256 // progress dialog so the user can see what's going on.
2257 // Also, release our reference to mDialog. We don't need it anymore, and we
2258 // need to break the reference cycle.
2260 if (!mDialogProgressListener
) {
2261 NS_WARNING("The dialog should nullify the dialog progress listener");
2263 // In case of a non acceptable download, we need to cancel the request and
2264 // pass a FailedTransfer for the Download UI.
2265 if (mDownloadClassification
!= nsITransfer::DOWNLOAD_ACCEPTABLE
) {
2267 mRequest
->Cancel(NS_ERROR_ABORT
);
2269 mSaver
->Finish(NS_ERROR_ABORT
);
2272 return CreateFailedTransfer();
2276 // We must be able to create an nsITransfer object. If not, it doesn't matter
2277 // much that we can't launch the helper application or save to disk. Work on
2278 // a local copy rather than mTransfer until we know we succeeded, to make it
2279 // clearer that this function is re-entrant.
2280 nsCOMPtr
<nsITransfer
> transfer
=
2281 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2282 NS_ENSURE_SUCCESS(rv
, rv
);
2284 // Initialize the download
2285 nsCOMPtr
<nsIURI
> target
;
2286 rv
= NS_NewFileURI(getter_AddRefs(target
), mFinalFileDestination
);
2287 NS_ENSURE_SUCCESS(rv
, rv
);
2289 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2290 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mRequest
);
2291 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= nullptr;
2293 referrerInfo
= httpChannel
->GetReferrerInfo();
2296 if (mBrowsingContext
) {
2297 rv
= transfer
->InitWithBrowsingContext(
2298 mSourceUrl
, target
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
, mTempFile
,
2299 this, channel
&& NS_UsePrivateBrowsing(channel
),
2300 mDownloadClassification
, referrerInfo
, !mDialogShowing
,
2301 mBrowsingContext
, mHandleInternally
, nullptr);
2303 rv
= transfer
->Init(mSourceUrl
, nullptr, target
, u
""_ns
, mMimeInfo
,
2304 mTimeDownloadStarted
, mTempFile
, this,
2305 channel
&& NS_UsePrivateBrowsing(channel
),
2306 mDownloadClassification
, referrerInfo
, !mDialogShowing
);
2308 mDialogShowing
= false;
2310 NS_ENSURE_SUCCESS(rv
, rv
);
2312 // If we were cancelled since creating the transfer, just return. It is
2313 // always ok to return NS_OK if we are cancelled. Callers of this function
2314 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2319 rv
= transfer
->OnStateChange(nullptr, mRequest
,
2320 nsIWebProgressListener::STATE_START
|
2321 nsIWebProgressListener::STATE_IS_REQUEST
|
2322 nsIWebProgressListener::STATE_IS_NETWORK
,
2324 NS_ENSURE_SUCCESS(rv
, rv
);
2331 // Finally, save the transfer to mTransfer.
2332 mTransfer
= transfer
;
2335 // While we were bringing up the progress dialog, we actually finished
2336 // processing the url. If that's the case then mStopRequestIssued will be
2337 // true and OnSaveComplete has been called.
2338 if (mStopRequestIssued
&& !mSaver
&& mTransfer
) {
2339 NotifyTransfer(NS_OK
);
2345 nsresult
nsExternalAppHandler::CreateFailedTransfer() {
2347 nsCOMPtr
<nsITransfer
> transfer
=
2348 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2349 NS_ENSURE_SUCCESS(rv
, rv
);
2351 // We won't pass the temp file to the transfer, so if we have one it needs to
2355 mSaver
->Finish(NS_BINDING_ABORTED
);
2358 mTempFile
->Remove(false);
2361 nsCOMPtr
<nsIURI
> pseudoTarget
;
2362 if (!mFinalFileDestination
) {
2363 // If we don't have a download directory we're kinda screwed but it's OK
2364 // we'll still report the error via the prompter.
2365 nsCOMPtr
<nsIFile
> pseudoFile
;
2366 rv
= GetDownloadDirectory(getter_AddRefs(pseudoFile
), true);
2367 NS_ENSURE_SUCCESS(rv
, rv
);
2369 // Append the default suggested filename. If the user restarts the transfer
2370 // we will re-trigger a filename check anyway to ensure that it is unique.
2371 rv
= pseudoFile
->Append(mSuggestedFileName
);
2372 NS_ENSURE_SUCCESS(rv
, rv
);
2374 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), pseudoFile
);
2375 NS_ENSURE_SUCCESS(rv
, rv
);
2377 // Initialize the target, if present
2378 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), mFinalFileDestination
);
2379 NS_ENSURE_SUCCESS(rv
, rv
);
2382 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2383 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mRequest
);
2384 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= nullptr;
2386 referrerInfo
= httpChannel
->GetReferrerInfo();
2389 if (mBrowsingContext
) {
2390 rv
= transfer
->InitWithBrowsingContext(
2391 mSourceUrl
, pseudoTarget
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
,
2392 mTempFile
, this, channel
&& NS_UsePrivateBrowsing(channel
),
2393 mDownloadClassification
, referrerInfo
, true, mBrowsingContext
,
2394 mHandleInternally
, httpChannel
);
2396 rv
= transfer
->Init(mSourceUrl
, nullptr, pseudoTarget
, u
""_ns
, mMimeInfo
,
2397 mTimeDownloadStarted
, mTempFile
, this,
2398 channel
&& NS_UsePrivateBrowsing(channel
),
2399 mDownloadClassification
, referrerInfo
, true);
2401 NS_ENSURE_SUCCESS(rv
, rv
);
2403 // Our failed transfer is ready.
2404 mTransfer
= std::move(transfer
);
2409 nsresult
nsExternalAppHandler::SaveDestinationAvailable(nsIFile
* aFile
,
2410 bool aDialogWasShown
) {
2412 if (aDialogWasShown
) {
2413 mDialogShowing
= true;
2415 ContinueSave(aFile
);
2417 Cancel(NS_BINDING_ABORTED
);
2423 void nsExternalAppHandler::RequestSaveDestination(
2424 const nsString
& aDefaultFile
, const nsString
& aFileExtension
) {
2425 // Display the dialog
2426 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2427 // "AutoDownload" w/o showing a prompt
2428 nsresult rv
= NS_OK
;
2430 // Get helper app launcher dialog.
2431 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
2433 Cancel(NS_BINDING_ABORTED
);
2438 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we
2439 // can't unescape it because the dialog is implemented by a JS component which
2440 // doesn't have a window so no unescape routine is defined...
2442 // Now, be sure to keep |this| alive, and the dialog
2443 // If we don't do this, users that close the helper app dialog while the file
2444 // picker is up would cause Cancel() to be called, and the dialog would be
2445 // released, which would release this object too, which would crash.
2447 RefPtr
<nsExternalAppHandler
> kungFuDeathGrip(this);
2448 nsCOMPtr
<nsIHelperAppLauncherDialog
> dlg(mDialog
);
2449 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2451 rv
= dlg
->PromptForSaveToFileAsync(this, dialogParent
, aDefaultFile
.get(),
2452 aFileExtension
.get(), mForceSave
);
2453 if (NS_FAILED(rv
)) {
2454 Cancel(NS_BINDING_ABORTED
);
2458 // PromptForSaveDestination should only be called by the helper app dialog which
2459 // allows the user to say launch with application or save to disk.
2460 NS_IMETHODIMP
nsExternalAppHandler::PromptForSaveDestination() {
2461 if (mCanceled
) return NS_OK
;
2463 if (mForceSave
|| mForceSaveInternallyHandled
) {
2464 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
2467 if (mSuggestedFileName
.IsEmpty()) {
2468 RequestSaveDestination(mTempLeafName
, mFileExtension
);
2470 nsAutoString fileExt
;
2471 int32_t pos
= mSuggestedFileName
.RFindChar('.');
2473 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
2475 if (fileExt
.IsEmpty()) {
2476 fileExt
= mFileExtension
;
2479 RequestSaveDestination(mSuggestedFileName
, fileExt
);
2484 nsresult
nsExternalAppHandler::ContinueSave(nsIFile
* aNewFileLocation
) {
2485 if (mCanceled
) return NS_OK
;
2487 MOZ_ASSERT(aNewFileLocation
, "Must be called with a non-null file");
2489 int32_t action
= nsIMIMEInfo::saveToDisk
;
2490 mMimeInfo
->GetPreferredAction(&action
);
2491 mHandleInternally
= action
== nsIMIMEInfo::handleInternally
;
2493 nsresult rv
= NS_OK
;
2494 nsCOMPtr
<nsIFile
> fileToUse
= aNewFileLocation
;
2495 mFinalFileDestination
= fileToUse
;
2497 // Move what we have in the final directory, but append .part
2498 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2499 // saver if we are done (Finish has been called) but OnSaverComplete has
2501 if (mFinalFileDestination
&& mSaver
&& !mStopRequestIssued
) {
2502 nsCOMPtr
<nsIFile
> movedFile
;
2503 mFinalFileDestination
->Clone(getter_AddRefs(movedFile
));
2505 nsAutoCString randomChars
;
2506 rv
= GenerateRandomName(randomChars
);
2507 if (NS_SUCCEEDED(rv
)) {
2508 // Get the leaf name, strip any extensions, then
2509 // add random bytes, followed by the extensions and '.part'.
2510 nsAutoString leafName
;
2511 mFinalFileDestination
->GetLeafName(leafName
);
2512 auto nameWithoutExtensionLength
= leafName
.FindChar('.');
2513 nsAutoString
extensions(u
"");
2514 if (nameWithoutExtensionLength
== kNotFound
) {
2515 nameWithoutExtensionLength
= leafName
.Length();
2517 extensions
= Substring(leafName
, nameWithoutExtensionLength
);
2519 leafName
.Truncate(nameWithoutExtensionLength
);
2521 nsAutoString suffix
= u
"."_ns
+ NS_ConvertASCIItoUTF16(randomChars
) +
2522 extensions
+ u
".part"_ns
;
2524 // Deal with MAX_PATH on Windows. Worth noting that the original
2525 // path for mFinalFileDestination must be valid for us to get
2526 // here: either SetDownloadToLaunch or the caller of
2527 // SaveDestinationAvailable has called CreateUnique or similar
2528 // to ensure both a unique name and one that isn't too long.
2529 // The only issue is we're making it longer to get the part
2532 mFinalFileDestination
->GetPath(path
);
2533 CheckedInt
<uint16_t> fullPathLength
=
2534 CheckedInt
<uint16_t>(path
.Length()) + 1 + randomChars
.Length() +
2535 ArrayLength(".part");
2536 if (!fullPathLength
.isValid()) {
2537 leafName
.Truncate();
2538 } else if (fullPathLength
.value() > MAX_PATH
) {
2539 int32_t leafNameRemaining
=
2540 (int32_t)leafName
.Length() - (fullPathLength
.value() - MAX_PATH
);
2541 leafName
.Truncate(std::max(leafNameRemaining
, 0));
2544 leafName
.Append(suffix
);
2545 movedFile
->SetLeafName(leafName
);
2547 rv
= mSaver
->SetTarget(movedFile
, true);
2548 if (NS_FAILED(rv
)) {
2550 mTempFile
->GetPath(path
);
2551 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2556 mTempFile
= movedFile
;
2561 // The helper app dialog has told us what to do and we have a final file
2563 rv
= CreateTransfer();
2564 // If we fail to create the transfer, Cancel.
2565 if (NS_FAILED(rv
)) {
2573 // SetDownloadToLaunch should only be called by the helper app dialog which
2574 // allows the user to say launch with application or save to disk.
2575 NS_IMETHODIMP
nsExternalAppHandler::SetDownloadToLaunch(
2576 bool aHandleInternally
, nsIFile
* aNewFileLocation
) {
2577 if (mCanceled
) return NS_OK
;
2579 mHandleInternally
= aHandleInternally
;
2581 // Now that the user has elected to launch the downloaded file with a helper
2582 // app, we're justified in removing the 'salted' name. We'll rename to what
2583 // was specified in mSuggestedFileName after the download is done prior to
2584 // launching the helper app. So that any existing file of that name won't be
2585 // overwritten we call CreateUnique(). Also note that we use the same
2586 // directory as originally downloaded so the download can be renamed in place
2588 nsCOMPtr
<nsIFile
> fileToUse
;
2589 if (aNewFileLocation
) {
2590 fileToUse
= aNewFileLocation
;
2592 (void)GetDownloadDirectory(getter_AddRefs(fileToUse
));
2594 if (mSuggestedFileName
.IsEmpty()) {
2595 // Keep using the leafname of the temp file, since we're just starting a
2597 mSuggestedFileName
= mTempLeafName
;
2601 // Ensure we don't double-append the file extension if it matches:
2602 if (StringEndsWith(mSuggestedFileName
, mFileExtension
,
2603 nsCaseInsensitiveStringComparator
)) {
2604 fileToUse
->Append(mSuggestedFileName
);
2606 fileToUse
->Append(mSuggestedFileName
+ mFileExtension
);
2609 fileToUse
->Append(mSuggestedFileName
);
2613 nsresult rv
= fileToUse
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
2614 if (NS_SUCCEEDED(rv
)) {
2615 mFinalFileDestination
= fileToUse
;
2616 // launch the progress window now that the user has picked the desired
2618 rv
= CreateTransfer();
2619 if (NS_FAILED(rv
)) {
2623 // Cancel the download and report an error. We do not want to end up in
2624 // a state where it appears that we have a normal download that is
2625 // pointing to a file that we did not actually create.
2627 mTempFile
->GetPath(path
);
2628 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2634 nsresult
nsExternalAppHandler::LaunchLocalFile() {
2635 nsCOMPtr
<nsIFileURL
> fileUrl(do_QueryInterface(mSourceUrl
));
2639 Cancel(NS_BINDING_ABORTED
);
2640 nsCOMPtr
<nsIFile
> file
;
2641 nsresult rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2643 if (NS_SUCCEEDED(rv
)) {
2644 rv
= mMimeInfo
->LaunchWithFile(file
);
2645 if (NS_SUCCEEDED(rv
)) return NS_OK
;
2648 if (file
) file
->GetPath(path
);
2649 // If we get here, an error happened
2650 SendStatusChange(kLaunchError
, rv
, nullptr, path
);
2654 NS_IMETHODIMP
nsExternalAppHandler::Cancel(nsresult aReason
) {
2655 NS_ENSURE_ARG(NS_FAILED(aReason
));
2663 // We are still writing to the target file. Give the saver a chance to
2664 // close the target file, then notify the transfer object if necessary in
2665 // the OnSaveComplete callback.
2666 mSaver
->Finish(aReason
);
2669 if (mStopRequestIssued
&& mTempFile
) {
2670 // This branch can only happen when the user cancels the helper app dialog
2671 // when the request has completed. The temp file has to be removed here,
2672 // because mSaver has been released at that time with the temp file left.
2673 (void)mTempFile
->Remove(false);
2676 // Notify the transfer object that the download has been canceled, if the
2677 // user has already chosen an action and we didn't notify already.
2679 NotifyTransfer(aReason
);
2683 // Break our reference cycle with the helper app dialog (set up in
2686 mDialogShowing
= false;
2690 // Release the listener, to break the reference cycle with it (we are the
2691 // observer of the listener).
2692 mDialogProgressListener
= nullptr;
2697 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char* prefName
,
2698 const char* aContentType
) {
2699 // Search the obsolete pref strings.
2700 nsAutoCString prefCString
;
2701 Preferences::GetCString(prefName
, prefCString
);
2702 if (prefCString
.IsEmpty()) {
2703 // Default is true, if not found in the pref string.
2707 NS_UnescapeURL(prefCString
);
2708 nsACString::const_iterator start
, end
;
2709 prefCString
.BeginReading(start
);
2710 prefCString
.EndReading(end
);
2711 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType
), start
,
2716 nsExternalAppHandler::GetName(nsACString
& aName
) {
2717 aName
.AssignLiteral("nsExternalAppHandler");
2721 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2722 // The following section contains our nsIMIMEService implementation and related
2725 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2727 // nsIMIMEService methods
2728 NS_IMETHODIMP
nsExternalHelperAppService::GetFromTypeAndExtension(
2729 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
2730 nsIMIMEInfo
** _retval
) {
2731 MOZ_ASSERT(!aMIMEType
.IsEmpty() || !aFileExt
.IsEmpty(),
2732 "Give me something to work with");
2733 MOZ_DIAGNOSTIC_ASSERT(aFileExt
.FindChar('\0') == kNotFound
,
2734 "The extension should never contain null characters");
2735 LOG("Getting mimeinfo from type '%s' ext '%s'\n",
2736 PromiseFlatCString(aMIMEType
).get(), PromiseFlatCString(aFileExt
).get());
2740 // OK... we need a type. Get one.
2741 nsAutoCString
typeToUse(aMIMEType
);
2742 if (typeToUse
.IsEmpty()) {
2743 nsresult rv
= GetTypeFromExtension(aFileExt
, typeToUse
);
2744 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
2747 // We promise to only send lower case mime types to the OS
2748 ToLowerCase(typeToUse
);
2750 // First, ask the OS for a mime info
2752 nsresult rv
= GetMIMEInfoFromOS(typeToUse
, aFileExt
, &found
, _retval
);
2753 if (NS_WARN_IF(NS_FAILED(rv
))) {
2757 LOG("OS gave back 0x%p - found: %i\n", *_retval
, found
);
2758 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2759 if (!*_retval
) return NS_ERROR_OUT_OF_MEMORY
;
2761 // The handler service can make up for bad mime types by checking the file
2762 // extension. If the mime type is known (in extras or in the handler
2763 // service), we stop it doing so by flipping this bool to true.
2764 bool trustMIMEType
= false;
2766 // Check extras - not everything we support will be known by the OS store,
2767 // unfortunately, and it may even miss some extensions that we know should
2768 // be accepted. We only do this for non-octet-stream mimetypes, because
2769 // our information for octet-stream would lead to us trying to open all such
2770 // files as Binary file with exe, com or bin extension regardless of the
2772 if (!typeToUse
.Equals(APPLICATION_OCTET_STREAM
,
2773 nsCaseInsensitiveCStringComparator
)) {
2774 rv
= FillMIMEInfoForMimeTypeFromExtras(typeToUse
, !found
, *_retval
);
2775 LOG("Searched extras (by type), rv 0x%08" PRIX32
"\n",
2776 static_cast<uint32_t>(rv
));
2777 trustMIMEType
= NS_SUCCEEDED(rv
);
2778 found
= found
|| NS_SUCCEEDED(rv
);
2781 // Now, let's see if we can find something in our datastore.
2782 // This will not overwrite the OS information that interests us
2783 // (i.e. default application, default app. description)
2784 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
2785 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2787 bool hasHandler
= false;
2788 (void)handlerSvc
->Exists(*_retval
, &hasHandler
);
2790 rv
= handlerSvc
->FillHandlerInfo(*_retval
, ""_ns
);
2791 LOG("Data source: Via type: retval 0x%08" PRIx32
"\n",
2792 static_cast<uint32_t>(rv
));
2793 trustMIMEType
= trustMIMEType
|| NS_SUCCEEDED(rv
);
2795 rv
= NS_ERROR_NOT_AVAILABLE
;
2798 found
= found
|| NS_SUCCEEDED(rv
);
2801 // If we still haven't found anything, try finding a match for
2802 // an extension in extras first:
2803 if (!found
&& !aFileExt
.IsEmpty()) {
2804 rv
= FillMIMEInfoForExtensionFromExtras(aFileExt
, *_retval
);
2805 LOG("Searched extras (by ext), rv 0x%08" PRIX32
"\n",
2806 static_cast<uint32_t>(rv
));
2809 // Then check the handler service - but only do so if we really do not know
2810 // the mimetype. This avoids overwriting good mimetype info with bad file
2812 if ((!found
|| !trustMIMEType
) && handlerSvc
&& !aFileExt
.IsEmpty()) {
2813 nsAutoCString overrideType
;
2814 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, overrideType
);
2815 if (NS_SUCCEEDED(rv
) && !overrideType
.IsEmpty()) {
2816 // We can't check handlerSvc->Exists() here, because we have a
2817 // overideType. That's ok, it just results in some console noise.
2818 // (If there's no handler for the override type, it throws)
2819 rv
= handlerSvc
->FillHandlerInfo(*_retval
, overrideType
);
2820 LOG("Data source: Via ext: retval 0x%08" PRIx32
"\n",
2821 static_cast<uint32_t>(rv
));
2822 found
= found
|| NS_SUCCEEDED(rv
);
2826 // If we still don't have a match, at least set the file description
2827 // to `${aFileExt} File` if it's empty:
2828 if (!found
&& !aFileExt
.IsEmpty()) {
2829 // XXXzpao This should probably be localized
2830 nsAutoCString
desc(aFileExt
);
2831 desc
.AppendLiteral(" File");
2832 (*_retval
)->SetDescription(NS_ConvertUTF8toUTF16(desc
));
2833 LOG("Falling back to 'File' file description\n");
2836 // Sometimes, OSes give us bad data. We have a set of forbidden extensions
2837 // for some MIME types. If the primary extension is forbidden,
2838 // overwrite it with a known-good one. See bug 1571247 for context.
2839 nsAutoCString primaryExtension
;
2840 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2841 if (!primaryExtension
.EqualsIgnoreCase(PromiseFlatCString(aFileExt
).get())) {
2842 if (MaybeReplacePrimaryExtension(primaryExtension
, *_retval
)) {
2843 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2847 // Finally, check if we got a file extension and if yes, if it is an
2848 // extension on the mimeinfo, in which case we want it to be the primary one
2849 if (!aFileExt
.IsEmpty()) {
2850 bool matches
= false;
2851 (*_retval
)->ExtensionExists(aFileExt
, &matches
);
2852 LOG("Extension '%s' matches mime info: %i\n",
2853 PromiseFlatCString(aFileExt
).get(), matches
);
2855 nsAutoCString fileExt
;
2856 ToLowerCase(aFileExt
, fileExt
);
2857 (*_retval
)->SetPrimaryExtension(fileExt
);
2858 primaryExtension
= fileExt
;
2862 // Overwrite with a generic description if the primary extension for the
2863 // type is in our list; these are file formats supported by Firefox and
2864 // we don't want other brands positioning themselves as the sole viewer
2866 if (!primaryExtension
.IsEmpty()) {
2867 for (const char* ext
: descriptionOverwriteExtensions
) {
2868 if (primaryExtension
.Equals(ext
)) {
2869 nsCOMPtr
<nsIStringBundleService
> bundleService
=
2870 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
2871 NS_ENSURE_SUCCESS(rv
, rv
);
2872 nsCOMPtr
<nsIStringBundle
> unknownContentTypeBundle
;
2873 rv
= bundleService
->CreateBundle(
2874 "chrome://mozapps/locale/downloads/unknownContentType.properties",
2875 getter_AddRefs(unknownContentTypeBundle
));
2876 if (NS_SUCCEEDED(rv
)) {
2877 nsAutoCString
stringName(ext
);
2878 stringName
.AppendLiteral("ExtHandlerDescription");
2879 nsAutoString handlerDescription
;
2880 rv
= unknownContentTypeBundle
->GetStringFromName(stringName
.get(),
2881 handlerDescription
);
2882 if (NS_SUCCEEDED(rv
)) {
2883 (*_retval
)->SetDescription(handlerDescription
);
2891 if (LOG_ENABLED()) {
2893 (*_retval
)->GetMIMEType(type
);
2895 LOG("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type
.get(),
2896 primaryExtension
.get());
2902 bool nsExternalHelperAppService::GetMIMETypeFromDefaultForExtension(
2903 const nsACString
& aExtension
, nsACString
& aMIMEType
) {
2904 // First of all, check our default entries
2905 for (auto& entry
: defaultMimeEntries
) {
2906 if (aExtension
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
2907 aMIMEType
= entry
.mMimeType
;
2915 nsExternalHelperAppService::GetTypeFromExtension(const nsACString
& aFileExt
,
2916 nsACString
& aContentType
) {
2917 // OK. We want to try the following sources of mimetype information, in this
2919 // 1. defaultMimeEntries array
2920 // 2. OS-provided information
2921 // 3. our "extras" array
2922 // 4. Information from plugins
2923 // 5. The "ext-to-type-mapping" category
2924 // Note that, we are intentionally not looking at the handler service, because
2925 // that can be affected by websites, which leads to undesired behavior.
2927 // Early return if called with an empty extension parameter
2928 if (aFileExt
.IsEmpty()) {
2929 return NS_ERROR_NOT_AVAILABLE
;
2932 // First of all, check our default entries
2933 if (GetMIMETypeFromDefaultForExtension(aFileExt
, aContentType
)) {
2938 if (GetMIMETypeFromOSForExtension(aFileExt
, aContentType
)) {
2942 // Check extras array.
2943 bool found
= GetTypeFromExtras(aFileExt
, aContentType
);
2948 // Let's see if an extension added something
2949 nsCOMPtr
<nsICategoryManager
> catMan(
2950 do_GetService("@mozilla.org/categorymanager;1"));
2952 // The extension in the category entry is always stored as lowercase
2953 nsAutoCString
lowercaseFileExt(aFileExt
);
2954 ToLowerCase(lowercaseFileExt
);
2955 // Read the MIME type from the category entry, if available
2958 catMan
->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt
, type
);
2959 if (NS_SUCCEEDED(rv
)) {
2960 aContentType
= type
;
2965 return NS_ERROR_NOT_AVAILABLE
;
2968 NS_IMETHODIMP
nsExternalHelperAppService::GetPrimaryExtension(
2969 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
2970 nsACString
& _retval
) {
2971 NS_ENSURE_ARG(!aMIMEType
.IsEmpty());
2973 nsCOMPtr
<nsIMIMEInfo
> mi
;
2975 GetFromTypeAndExtension(aMIMEType
, aFileExt
, getter_AddRefs(mi
));
2976 if (NS_FAILED(rv
)) return rv
;
2978 return mi
->GetPrimaryExtension(_retval
);
2981 NS_IMETHODIMP
nsExternalHelperAppService::GetDefaultTypeFromURI(
2982 nsIURI
* aURI
, nsACString
& aContentType
) {
2983 NS_ENSURE_ARG_POINTER(aURI
);
2984 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
2985 aContentType
.Truncate();
2987 // Now try to get an nsIURL so we don't have to do our own parsing
2988 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
2990 return NS_ERROR_NOT_AVAILABLE
;
2994 rv
= url
->GetFileExtension(ext
);
2995 if (NS_FAILED(rv
)) {
2999 if (!ext
.IsEmpty()) {
3000 UnescapeFragment(ext
, url
, ext
);
3002 if (GetMIMETypeFromDefaultForExtension(ext
, aContentType
)) {
3007 return NS_ERROR_NOT_AVAILABLE
;
3010 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromURI(
3011 nsIURI
* aURI
, nsACString
& aContentType
) {
3012 NS_ENSURE_ARG_POINTER(aURI
);
3013 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
3014 aContentType
.Truncate();
3016 // First look for a file to use. If we have one, we just use that.
3017 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
);
3019 nsCOMPtr
<nsIFile
> file
;
3020 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
3021 if (NS_SUCCEEDED(rv
)) {
3022 rv
= GetTypeFromFile(file
, aContentType
);
3023 if (NS_SUCCEEDED(rv
)) {
3024 // we got something!
3030 // Now try to get an nsIURL so we don't have to do our own parsing
3031 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
3034 rv
= url
->GetFileExtension(ext
);
3035 if (NS_FAILED(rv
)) return rv
;
3036 if (ext
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
3038 UnescapeFragment(ext
, url
, ext
);
3040 return GetTypeFromExtension(ext
, aContentType
);
3043 // no url, let's give the raw spec a shot
3044 nsAutoCString specStr
;
3045 rv
= aURI
->GetSpec(specStr
);
3046 if (NS_FAILED(rv
)) return rv
;
3047 UnescapeFragment(specStr
, aURI
, specStr
);
3049 // find the file extension (if any)
3050 int32_t extLoc
= specStr
.RFindChar('.');
3051 int32_t specLength
= specStr
.Length();
3052 if (-1 != extLoc
&& extLoc
!= specLength
- 1 &&
3053 // nothing over 20 chars long can be sanely considered an
3054 // extension.... Dat dere would be just data.
3055 specLength
- extLoc
< 20) {
3056 return GetTypeFromExtension(Substring(specStr
, extLoc
+ 1), aContentType
);
3059 // We found no information; say so.
3060 return NS_ERROR_NOT_AVAILABLE
;
3063 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromFile(
3064 nsIFile
* aFile
, nsACString
& aContentType
) {
3065 NS_ENSURE_ARG_POINTER(aFile
);
3068 // Get the Extension
3069 nsAutoString fileName
;
3070 rv
= aFile
->GetLeafName(fileName
);
3071 if (NS_FAILED(rv
)) return rv
;
3073 nsAutoCString fileExt
;
3074 if (!fileName
.IsEmpty()) {
3075 int32_t len
= fileName
.Length();
3076 for (int32_t i
= len
; i
>= 0; i
--) {
3077 if (fileName
[i
] == char16_t('.')) {
3078 CopyUTF16toUTF8(Substring(fileName
, i
+ 1), fileExt
);
3084 if (fileExt
.IsEmpty()) return NS_ERROR_FAILURE
;
3086 return GetTypeFromExtension(fileExt
, aContentType
);
3089 nsresult
nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
3090 const nsACString
& aContentType
, bool aOverwriteDescription
,
3091 nsIMIMEInfo
* aMIMEInfo
) {
3092 NS_ENSURE_ARG(aMIMEInfo
);
3094 NS_ENSURE_ARG(!aContentType
.IsEmpty());
3096 // Look for default entry with matching mime type.
3097 nsAutoCString
MIMEType(aContentType
);
3098 ToLowerCase(MIMEType
);
3099 for (auto entry
: extraMimeEntries
) {
3100 if (MIMEType
.Equals(entry
.mMimeType
)) {
3101 // This is the one. Set attributes appropriately.
3102 nsDependentCString
extensions(entry
.mFileExtensions
);
3103 nsACString::const_iterator start
, end
;
3104 extensions
.BeginReading(start
);
3105 extensions
.EndReading(end
);
3106 while (start
!= end
) {
3107 nsACString::const_iterator cursor
= start
;
3108 mozilla::Unused
<< FindCharInReadable(',', cursor
, end
);
3109 aMIMEInfo
->AppendExtension(Substring(start
, cursor
));
3110 // If a comma was found, skip it for the next search.
3111 start
= cursor
!= end
? ++cursor
: cursor
;
3115 aMIMEInfo
->GetDescription(desc
);
3116 if (aOverwriteDescription
|| desc
.IsEmpty()) {
3117 aMIMEInfo
->SetDescription(NS_ConvertASCIItoUTF16(entry
.mDescription
));
3123 return NS_ERROR_NOT_AVAILABLE
;
3126 nsresult
nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
3127 const nsACString
& aExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3129 bool found
= GetTypeFromExtras(aExtension
, type
);
3130 if (!found
) return NS_ERROR_NOT_AVAILABLE
;
3131 return FillMIMEInfoForMimeTypeFromExtras(type
, true, aMIMEInfo
);
3134 bool nsExternalHelperAppService::MaybeReplacePrimaryExtension(
3135 const nsACString
& aPrimaryExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3136 for (const auto& entry
: sForbiddenPrimaryExtensions
) {
3137 if (aPrimaryExtension
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
3138 nsDependentCString
mime(entry
.mMimeType
);
3139 for (const auto& extraEntry
: extraMimeEntries
) {
3140 if (mime
.LowerCaseEqualsASCII(extraEntry
.mMimeType
)) {
3141 nsDependentCString
goodExts(extraEntry
.mFileExtensions
);
3142 int32_t commaPos
= goodExts
.FindChar(',');
3143 commaPos
= commaPos
== kNotFound
? goodExts
.Length() : commaPos
;
3144 auto goodExt
= Substring(goodExts
, 0, commaPos
);
3145 aMIMEInfo
->SetPrimaryExtension(goodExt
);
3154 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString
& aExtension
,
3155 nsACString
& aMIMEType
) {
3156 NS_ASSERTION(!aExtension
.IsEmpty(), "Empty aExtension parameter!");
3158 // Look for default entry with matching extension.
3159 nsDependentCString::const_iterator start
, end
, iter
;
3160 int32_t numEntries
= ArrayLength(extraMimeEntries
);
3161 for (int32_t index
= 0; index
< numEntries
; index
++) {
3162 nsDependentCString
extList(extraMimeEntries
[index
].mFileExtensions
);
3163 extList
.BeginReading(start
);
3164 extList
.EndReading(end
);
3166 while (start
!= end
) {
3167 FindCharInReadable(',', iter
, end
);
3168 if (Substring(start
, iter
)
3169 .Equals(aExtension
, nsCaseInsensitiveCStringComparator
)) {
3170 aMIMEType
= extraMimeEntries
[index
].mMimeType
;
3183 bool nsExternalHelperAppService::GetMIMETypeFromOSForExtension(
3184 const nsACString
& aExtension
, nsACString
& aMIMEType
) {
3186 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
3188 GetMIMEInfoFromOS(""_ns
, aExtension
, &found
, getter_AddRefs(mimeInfo
));
3189 return NS_SUCCEEDED(rv
) && found
&& mimeInfo
&&
3190 NS_SUCCEEDED(mimeInfo
->GetMIMEType(aMIMEType
));
3193 nsresult
nsExternalHelperAppService::GetMIMEInfoFromOS(
3194 const nsACString
& aMIMEType
, const nsACString
& aFileExt
, bool* aFound
,
3195 nsIMIMEInfo
** aMIMEInfo
) {
3196 *aMIMEInfo
= nullptr;
3198 return NS_ERROR_NOT_IMPLEMENTED
;
3201 nsresult
nsExternalHelperAppService::UpdateDefaultAppInfo(
3202 nsIMIMEInfo
* aMIMEInfo
) {
3203 return NS_ERROR_NOT_IMPLEMENTED
;
3206 bool nsExternalHelperAppService::GetFileNameFromChannel(nsIChannel
* aChannel
,
3207 nsAString
& aFileName
,
3213 aChannel
->GetURI(aURI
);
3214 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(*aURI
);
3216 // Check if we have a POST request, in which case we don't want to use
3217 // the url's extension
3218 bool allowURLExt
= !net::ChannelIsPost(aChannel
);
3220 // Check if we had a query string - we don't want to check the URL
3221 // extension if a query is present in the URI
3222 // If we already know we don't want to check the URL extension, don't
3223 // bother checking the query
3224 if (url
&& allowURLExt
) {
3225 nsAutoCString query
;
3227 // We only care about the query for HTTP and HTTPS URLs
3228 if (url
->SchemeIs("http") || url
->SchemeIs("https")) {
3229 url
->GetQuery(query
);
3232 // Only get the extension if the query is empty; if it isn't, then the
3233 // extension likely belongs to a cgi script and isn't helpful
3234 allowURLExt
= query
.IsEmpty();
3237 aChannel
->GetContentDispositionFilename(aFileName
);
3243 nsExternalHelperAppService::GetValidFileName(nsIChannel
* aChannel
,
3244 const nsACString
& aType
,
3245 nsIURI
* aOriginalURI
,
3247 nsAString
& aOutFileName
) {
3248 nsCOMPtr
<nsIURI
> uri
;
3249 bool allowURLExtension
=
3250 GetFileNameFromChannel(aChannel
, aOutFileName
, getter_AddRefs(uri
));
3252 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
3253 aOutFileName
, aType
, uri
, aOriginalURI
, aFlags
, allowURLExtension
);
3258 nsExternalHelperAppService::ValidateFileNameForSaving(
3259 const nsAString
& aFileName
, const nsACString
& aType
, uint32_t aFlags
,
3260 nsAString
& aOutFileName
) {
3261 nsAutoString
fileName(aFileName
);
3263 // Just sanitize the filename only.
3264 if (aFlags
& VALIDATE_SANITIZE_ONLY
) {
3265 SanitizeFileName(fileName
, aFlags
);
3267 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
3268 fileName
, aType
, nullptr, nullptr, aFlags
, true);
3271 aOutFileName
= fileName
;
3275 already_AddRefed
<nsIMIMEInfo
>
3276 nsExternalHelperAppService::ValidateFileNameForSaving(
3277 nsAString
& aFileName
, const nsACString
& aMimeType
, nsIURI
* aURI
,
3278 nsIURI
* aOriginalURI
, uint32_t aFlags
, bool aAllowURLExtension
) {
3279 nsAutoString
fileName(aFileName
);
3280 nsAutoCString extension
;
3281 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
3283 bool isBinaryType
= aMimeType
.EqualsLiteral(APPLICATION_OCTET_STREAM
) ||
3284 aMimeType
.EqualsLiteral(BINARY_OCTET_STREAM
) ||
3285 aMimeType
.EqualsLiteral("application/x-msdownload");
3287 // We don't want to save hidden files starting with a dot, so remove any
3288 // leading periods. This is done first, so that the remainder will be
3289 // treated as the filename, and not an extension.
3290 // Also, Windows ignores terminating dots. So we have to as well, so
3291 // that our security checks do "the right thing"
3294 bool urlIsFile
= !!aURI
&& aURI
->SchemeIs("file");
3296 // We get the mime service here even though we're the default implementation
3297 // of it, so it's possible to override only the mime service and not need to
3298 // reimplement the whole external helper app service itself.
3299 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
3301 if (fileName
.IsEmpty()) {
3302 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
3303 // Try to extract the file name from the url and use that as a first
3304 // pass as the leaf name of our temp file...
3306 nsAutoCString leafName
;
3307 url
->GetFileName(leafName
);
3308 if (!leafName
.IsEmpty()) {
3309 if (NS_FAILED(UnescapeFragment(leafName
, url
, fileName
))) {
3310 CopyUTF8toUTF16(leafName
, fileName
); // use escaped name instead
3315 // Only get the extension from the URL if allowed, or if this
3316 // is a binary type in which case the type might not be valid
3318 if (aAllowURLExtension
|| isBinaryType
|| urlIsFile
) {
3319 url
->GetFileExtension(extension
);
3323 // Determine the current extension for the filename.
3324 int32_t dotidx
= fileName
.RFind(u
".");
3326 CopyUTF16toUTF8(Substring(fileName
, dotidx
+ 1), extension
);
3330 if (aFlags
& VALIDATE_GUESS_FROM_EXTENSION
) {
3331 nsAutoCString mimeType
;
3332 if (!extension
.IsEmpty()) {
3333 mimeService
->GetFromTypeAndExtension(EmptyCString(), extension
,
3334 getter_AddRefs(mimeInfo
));
3336 mimeInfo
->GetMIMEType(mimeType
);
3340 if (mimeType
.IsEmpty()) {
3341 // Extension lookup gave us no useful match, so use octet-stream
3343 mimeService
->GetFromTypeAndExtension(
3344 nsLiteralCString(APPLICATION_OCTET_STREAM
), extension
,
3345 getter_AddRefs(mimeInfo
));
3347 } else if (!aMimeType
.IsEmpty()) {
3348 // If this is a binary type, include the extension as a hint to get
3349 // the mime info. For other types, the mime type itself should be
3351 // Unfortunately, on Windows, the mimetype is usually insufficient.
3352 // Compensate at least on `file` URLs by trusting the extension -
3353 // that's likely what we used to get the mimetype in the first place.
3354 // The special case for application/ogg is because that type could
3355 // actually be used for a video which can better be determined by the
3356 // extension. This is tested by browser_save_video.js.
3358 isBinaryType
|| urlIsFile
|| aMimeType
.EqualsLiteral(APPLICATION_OGG
);
3359 mimeService
->GetFromTypeAndExtension(
3360 aMimeType
, useExtension
? extension
: EmptyCString(),
3361 getter_AddRefs(mimeInfo
));
3363 // But if no primary extension was returned, this mime type is probably
3364 // an unknown type. Look it up again but this time supply the extension.
3365 nsAutoCString primaryExtension
;
3366 mimeInfo
->GetPrimaryExtension(primaryExtension
);
3367 if (primaryExtension
.IsEmpty()) {
3368 mimeService
->GetFromTypeAndExtension(aMimeType
, extension
,
3369 getter_AddRefs(mimeInfo
));
3375 // If an empty filename is allowed, then return early. It will be saved
3376 // using the filename of the temporary file that was created for the download.
3377 if (aFlags
& VALIDATE_ALLOW_EMPTY
&& fileName
.IsEmpty()) {
3378 aFileName
.Truncate();
3379 return mimeInfo
.forget();
3382 // This section modifies the extension on the filename if it isn't valid for
3383 // the given content type.
3385 bool isValidExtension
;
3386 if (extension
.IsEmpty() ||
3387 NS_FAILED(mimeInfo
->ExtensionExists(extension
, &isValidExtension
)) ||
3388 !isValidExtension
) {
3389 // Skip these checks for text and binary, so we don't append the unneeded
3390 // .txt or other extension.
3391 if (aMimeType
.EqualsLiteral(TEXT_PLAIN
) || isBinaryType
) {
3392 extension
.Truncate();
3394 nsAutoCString
originalExtension(extension
);
3395 // If an original url was supplied, see if it has a valid extension.
3396 bool useOldExtension
= false;
3398 nsCOMPtr
<nsIURL
> originalURL(do_QueryInterface(aOriginalURI
));
3400 nsAutoCString uriExtension
;
3401 originalURL
->GetFileExtension(uriExtension
);
3402 if (!uriExtension
.IsEmpty()) {
3403 mimeInfo
->ExtensionExists(uriExtension
, &useOldExtension
);
3404 if (useOldExtension
) {
3405 extension
= uriExtension
;
3411 if (!useOldExtension
) {
3412 // If the filename doesn't have a valid extension, or we don't know
3413 // the extension, try to use the primary extension for the type. If we
3414 // don't know the primary extension for the type, just continue with
3415 // the existing extension, or leave the filename with no extension.
3416 nsAutoCString primaryExtension
;
3417 mimeInfo
->GetPrimaryExtension(primaryExtension
);
3418 if (!primaryExtension
.IsEmpty()) {
3419 extension
= primaryExtension
;
3423 // If an suitable extension was found, we will append to or replace the
3424 // existing extension.
3425 if (!extension
.IsEmpty()) {
3426 ModifyExtensionType modify
= ShouldModifyExtension(
3427 mimeInfo
, aFlags
& VALIDATE_FORCE_APPEND_EXTENSION
,
3429 if (modify
== ModifyExtension_Replace
) {
3430 int32_t dotidx
= fileName
.RFind(u
".");
3432 // Remove the existing extension and replace it.
3433 fileName
.Truncate(dotidx
);
3437 // Otherwise, just append the proper extension to the end of the
3438 // filename, adding to the invalid extension that might already be
3440 if (modify
!= ModifyExtension_Ignore
) {
3441 fileName
.AppendLiteral(".");
3442 fileName
.Append(NS_ConvertUTF8toUTF16(extension
));
3449 CheckDefaultFileName(fileName
, aFlags
);
3451 // Make the filename safe for the filesystem.
3452 SanitizeFileName(fileName
, aFlags
);
3454 aFileName
= fileName
;
3455 return mimeInfo
.forget();
3458 void nsExternalHelperAppService::CheckDefaultFileName(nsAString
& aFileName
,
3460 // If no filename is present, use a default filename.
3461 if (!(aFlags
& VALIDATE_NO_DEFAULT_FILENAME
) &&
3462 (aFileName
.Length() == 0 || aFileName
.RFind(u
".") == 0)) {
3463 nsCOMPtr
<nsIStringBundleService
> stringService
=
3464 mozilla::components::StringBundle::Service();
3465 if (stringService
) {
3466 nsCOMPtr
<nsIStringBundle
> bundle
;
3467 if (NS_SUCCEEDED(stringService
->CreateBundle(
3468 "chrome://global/locale/contentAreaCommands.properties",
3469 getter_AddRefs(bundle
)))) {
3470 nsAutoString defaultFileName
;
3471 bundle
->GetStringFromName("UntitledSaveFileName", defaultFileName
);
3472 // Append any existing extension to the default filename.
3473 aFileName
= defaultFileName
+ aFileName
;
3477 // Use 'Untitled' as a last resort.
3478 if (!aFileName
.Length()) {
3479 aFileName
.AssignLiteral("Untitled");
3484 void nsExternalHelperAppService::SanitizeFileName(nsAString
& aFileName
,
3486 nsAutoString
fileName(aFileName
);
3488 // Replace known invalid characters.
3489 fileName
.ReplaceChar(u
"" KNOWN_PATH_SEPARATORS
, u
'_');
3490 fileName
.ReplaceChar(u
"" FILE_ILLEGAL_CHARACTERS
, u
' ');
3491 fileName
.StripChar(char16_t(0));
3493 const char16_t
*startStr
, *endStr
;
3494 fileName
.BeginReading(startStr
);
3495 fileName
.EndReading(endStr
);
3497 // True if multiple consecutive whitespace characters should
3498 // be replaced by single space ' '.
3499 bool collapseWhitespace
= !(aFlags
& VALIDATE_DONT_COLLAPSE_WHITESPACE
);
3501 // The maximum filename length differs based on the platform:
3502 // Windows (FAT/NTFS) stores filenames as a maximum of 255 UTF-16 code units.
3503 // Mac (APFS) stores filenames with a maximum 255 of UTF-8 code units.
3504 // Linux (ext3/ext4...) stores filenames with a maximum 255 bytes.
3505 // So here we just use the maximum of 255 bytes.
3506 // 0 means don't truncate at a maximum size.
3507 const uint32_t maxBytes
=
3508 (aFlags
& VALIDATE_DONT_TRUNCATE
) ? 0 : kDefaultMaxFileNameLength
;
3510 // True if the last character added was whitespace.
3511 bool lastWasWhitespace
= false;
3513 // Length of the filename that fits into the maximum size excluding the
3514 // extension and period.
3515 int32_t longFileNameEnd
= -1;
3517 // Index of the last character added that was not a character that can be
3518 // trimmed off of the end of the string. Trimmable characters are whitespace,
3519 // periods and the vowel separator u'\u180e'. If all the characters after this
3520 // point are trimmable characters, truncate the string to this point after
3521 // iterating over the filename.
3522 int32_t lastNonTrimmable
= -1;
3524 // The number of bytes that the string would occupy if encoded in UTF-8.
3525 uint32_t bytesLength
= 0;
3527 // The length of the extension in bytes.
3528 uint32_t extensionBytesLength
= 0;
3530 // This algorithm iterates over each character in the string and appends it
3531 // or a replacement character if needed to outFileName.
3532 nsAutoString outFileName
;
3533 while (startStr
< endStr
) {
3535 char32_t nextChar
= UTF16CharEnumerator::NextChar(&startStr
, endStr
, &err
);
3540 // nulls are already stripped out above.
3541 MOZ_ASSERT(nextChar
!= char16_t(0));
3543 auto unicodeCategory
= unicode::GetGeneralCategory(nextChar
);
3544 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_CONTROL
||
3545 unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR
||
3546 unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR
) {
3547 // Skip over any control characters and separators.
3551 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR
||
3552 nextChar
== u
'\ufeff') {
3553 // Trim out any whitespace characters at the beginning of the filename,
3554 // and only add whitespace in the middle of the filename if the last
3555 // character was not whitespace or if we are not collapsing whitespace.
3556 if (!outFileName
.IsEmpty() &&
3557 (!lastWasWhitespace
|| !collapseWhitespace
)) {
3558 // Allow the ideographic space if it is present, otherwise replace with
3560 if (nextChar
!= u
'\u3000') {
3563 lastWasWhitespace
= true;
3565 lastWasWhitespace
= true;
3569 lastWasWhitespace
= false;
3570 if (nextChar
== '.' || nextChar
== u
'\u180e') {
3571 // Don't add any periods or vowel separators at the beginning of the
3572 // string. Note also that lastNonTrimmable is not adjusted in this
3573 // case, because periods and vowel separators are included in the
3574 // set of characters to trim at the end of the filename.
3575 if (outFileName
.IsEmpty()) {
3579 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3580 // Replace formatting characters with an underscore.
3584 // Don't truncate surrogate pairs in the middle.
3586 int32_t(outFileName
.Length()) +
3587 (NS_IS_HIGH_SURROGATE(H_SURROGATE(nextChar
)) ? 2 : 1);
3592 // UTF16CharEnumerator already converts surrogate pairs, so we can use
3593 // a simple computation of byte length here.
3594 uint32_t charBytesLength
= nextChar
< 0x80 ? 1
3595 : nextChar
< 0x800 ? 2
3596 : nextChar
< 0x10000 ? 3
3598 bytesLength
+= charBytesLength
;
3599 if (bytesLength
> maxBytes
) {
3600 if (longFileNameEnd
== -1) {
3601 longFileNameEnd
= int32_t(outFileName
.Length());
3605 // If we encounter a period, it could be the start of an extension, so
3606 // start counting the number of bytes in the extension. If another period
3607 // is found, start again since we want to use the last extension found.
3608 if (nextChar
== u
'.') {
3609 extensionBytesLength
= 1; // 1 byte for the period.
3610 } else if (extensionBytesLength
) {
3611 extensionBytesLength
+= charBytesLength
;
3615 AppendUCS4ToUTF16(nextChar
, outFileName
);
3618 // If the filename is longer than the maximum allowed filename size,
3619 // truncate it, but preserve the desired extension that is currently
3621 if (bytesLength
> maxBytes
&& !outFileName
.IsEmpty()) {
3622 // Get the sanitized extension from the filename without the dot.
3623 nsAutoString extension
;
3624 int32_t dotidx
= outFileName
.RFind(u
".");
3626 extension
= Substring(outFileName
, dotidx
+ 1);
3629 // There are two ways in which the filename should be truncated:
3630 // - If the filename was too long, truncate the name at the length
3632 // This position is indicated by longFileNameEnd.
3633 // - lastNonTrimmable will indicate the last character that was not
3634 // whitespace, a period, or a vowel separator at the end of the
3635 // the string, so the string should be truncated there as well.
3636 // If both apply, use the earliest position.
3637 if (lastNonTrimmable
>= 0) {
3638 // Subtract off the amount for the extension and the period.
3639 // Note that the extension length is in bytes but longFileNameEnd is in
3640 // characters, but if they don't match, it just means we crop off
3641 // more than is necessary. This is OK since it is better than cropping
3643 longFileNameEnd
-= extensionBytesLength
;
3644 if (longFileNameEnd
<= 0) {
3645 // This is extremely unlikely, but if the extension is larger than the
3646 // maximum size, just get rid of it. In this case, the extension
3647 // wouldn't have been an ordinary one we would want to preserve (such
3648 // as .html or .png) so just truncate off the file wherever the first
3650 int32_t dotidx
= outFileName
.Find(u
".");
3651 outFileName
.Truncate(dotidx
> 0 ? dotidx
: 1);
3653 outFileName
.Truncate(std::min(longFileNameEnd
, lastNonTrimmable
));
3655 // Now that the filename has been truncated, re-append the extension
3657 if (!extension
.IsEmpty()) {
3658 if (outFileName
.Last() != '.') {
3659 outFileName
.AppendLiteral(".");
3662 outFileName
.Append(extension
);
3666 } else if (lastNonTrimmable
>= 0) {
3667 // Otherwise, the filename wasn't too long, so just trim off the
3668 // extra whitespace and periods at the end.
3669 outFileName
.Truncate(lastNonTrimmable
);
3673 if (nsLocalFile::CheckForReservedFileName(outFileName
)) {
3674 outFileName
.Truncate();
3675 CheckDefaultFileName(outFileName
, aFlags
);
3680 if (!(aFlags
& VALIDATE_ALLOW_INVALID_FILENAMES
)) {
3681 // If the extension is one these types, replace it with .download, as these
3682 // types of files can have significance on Windows or Linux.
3683 // This happens for any file, not just those with the shortcut mime type.
3684 if (StringEndsWith(outFileName
, u
".lnk"_ns
,
3685 nsCaseInsensitiveStringComparator
) ||
3686 StringEndsWith(outFileName
, u
".local"_ns
,
3687 nsCaseInsensitiveStringComparator
) ||
3688 StringEndsWith(outFileName
, u
".url"_ns
,
3689 nsCaseInsensitiveStringComparator
) ||
3690 StringEndsWith(outFileName
, u
".scf"_ns
,
3691 nsCaseInsensitiveStringComparator
) ||
3692 StringEndsWith(outFileName
, u
".desktop"_ns
,
3693 nsCaseInsensitiveStringComparator
)) {
3694 outFileName
.AppendLiteral(".download");
3698 aFileName
= outFileName
;
3701 nsExternalHelperAppService::ModifyExtensionType
3702 nsExternalHelperAppService::ShouldModifyExtension(nsIMIMEInfo
* aMimeInfo
,
3704 const nsCString
& aFileExt
) {
3705 nsAutoCString MIMEType
;
3706 if (!aMimeInfo
|| NS_FAILED(aMimeInfo
->GetMIMEType(MIMEType
))) {
3707 return ModifyExtension_Append
;
3710 // Determine whether the extensions should be appended or replaced depending
3711 // on the content type.
3712 bool canForce
= StringBeginsWith(MIMEType
, "image/"_ns
) ||
3713 StringBeginsWith(MIMEType
, "audio/"_ns
) ||
3714 StringBeginsWith(MIMEType
, "video/"_ns
) || aFileExt
.IsEmpty();
3717 for (const char* mime
: forcedExtensionMimetypes
) {
3718 if (MIMEType
.Equals(mime
)) {
3719 if (!StaticPrefs::browser_download_sanitize_non_media_extensions()) {
3720 return ModifyExtension_Ignore
;
3728 return aForceAppend
? ModifyExtension_Append
: ModifyExtension_Ignore
;
3732 // If we get here, we know for sure the mimetype allows us to modify the
3733 // existing extension, if it's wrong. Return whether we should replace it
3735 bool knownExtension
= false;
3736 // Note that aFileExt is either empty or consists of an extension
3737 // excluding the dot.
3738 if (aFileExt
.IsEmpty() ||
3739 (NS_SUCCEEDED(aMimeInfo
->ExtensionExists(aFileExt
, &knownExtension
)) &&
3741 return ModifyExtension_Replace
;
3744 return ModifyExtension_Append
;