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 255
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.jsm.
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", "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
, nsIRequest
* aRequest
,
640 BrowsingContext
* aContentContext
, bool aForceSave
,
641 nsIInterfaceRequestor
* aWindowContext
,
642 nsIStreamListener
** aStreamListener
) {
643 // We need to get a hold of a ContentChild so that we can begin forwarding
644 // this data to the parent. In the HTTP case, this is unfortunate, since
645 // we're actually passing data from parent->child->parent wastefully, but
646 // the Right Fix will eventually be to short-circuit those channels on the
647 // parent side based on some sort of subscription concept.
648 using mozilla::dom::ContentChild
;
649 using mozilla::dom::ExternalHelperAppChild
;
650 ContentChild
* child
= ContentChild::GetSingleton();
652 return NS_ERROR_FAILURE
;
656 nsCOMPtr
<nsIURI
> uri
;
657 int64_t contentLength
= -1;
658 bool wasFileChannel
= false;
659 uint32_t contentDisposition
= -1;
660 nsAutoString fileName
;
661 nsCOMPtr
<nsILoadInfo
> loadInfo
;
663 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
665 channel
->GetURI(getter_AddRefs(uri
));
666 channel
->GetContentLength(&contentLength
);
667 channel
->GetContentDisposition(&contentDisposition
);
668 channel
->GetContentDispositionFilename(fileName
);
669 channel
->GetContentDispositionHeader(disp
);
670 loadInfo
= channel
->LoadInfo();
672 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(aRequest
));
673 wasFileChannel
= fileChan
!= nullptr;
676 nsCOMPtr
<nsIURI
> referrer
;
677 NS_GetReferrerFromChannel(channel
, getter_AddRefs(referrer
));
679 Maybe
<mozilla::net::LoadInfoArgs
> loadInfoArgs
;
680 MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo
, &loadInfoArgs
));
682 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(aRequest
));
683 // Determine whether a new window was opened specifically for this request
684 bool shouldCloseWindow
= false;
686 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
,
690 // Now we build a protocol for forwarding our data to the parent. The
691 // protocol will act as a listener on the child-side and create a "real"
692 // helperAppService listener on the parent-side, via another call to
694 RefPtr
<ExternalHelperAppChild
> childListener
= new ExternalHelperAppChild();
695 MOZ_ALWAYS_TRUE(child
->SendPExternalHelperAppConstructor(
696 childListener
, uri
, loadInfoArgs
, nsCString(aMimeContentType
), disp
,
697 contentDisposition
, fileName
, aForceSave
, contentLength
, wasFileChannel
,
698 referrer
, aContentContext
, shouldCloseWindow
));
700 NS_ADDREF(*aStreamListener
= childListener
);
702 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
704 SanitizeFileName(fileName
, 0);
706 RefPtr
<nsExternalAppHandler
> handler
=
707 new nsExternalAppHandler(nullptr, u
""_ns
, aContentContext
, aWindowContext
,
708 this, fileName
, reason
, aForceSave
);
710 return NS_ERROR_OUT_OF_MEMORY
;
713 childListener
->SetHandler(handler
);
717 NS_IMETHODIMP
nsExternalHelperAppService::CreateListener(
718 const nsACString
& aMimeContentType
, nsIRequest
* aRequest
,
719 BrowsingContext
* aContentContext
, bool aForceSave
,
720 nsIInterfaceRequestor
* aWindowContext
,
721 nsIStreamListener
** aStreamListener
) {
722 MOZ_ASSERT(!XRE_IsContentProcess());
724 nsAutoString fileName
;
725 nsAutoCString fileExtension
;
726 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
728 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
730 uint32_t contentDisposition
= -1;
731 channel
->GetContentDisposition(&contentDisposition
);
732 if (contentDisposition
== nsIChannel::DISPOSITION_ATTACHMENT
) {
733 reason
= nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
;
737 *aStreamListener
= nullptr;
739 // Get the file extension and name that we will need later
740 nsCOMPtr
<nsIURI
> uri
;
741 bool allowURLExtension
=
742 GetFileNameFromChannel(channel
, fileName
, getter_AddRefs(uri
));
744 uint32_t flags
= VALIDATE_ALLOW_EMPTY
;
745 if (aMimeContentType
.Equals(APPLICATION_GUESS_FROM_EXT
,
746 nsCaseInsensitiveCStringComparator
)) {
747 flags
|= VALIDATE_GUESS_FROM_EXTENSION
;
750 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
751 fileName
, aMimeContentType
, uri
, nullptr, flags
, allowURLExtension
);
753 LOG("Type/Ext lookup found 0x%p\n", mimeInfo
.get());
755 // No mimeinfo -> we can't continue. probably OOM.
757 return NS_ERROR_OUT_OF_MEMORY
;
760 if (flags
& VALIDATE_GUESS_FROM_EXTENSION
) {
762 // Replace the content type with what was guessed.
763 nsAutoCString mimeType
;
764 mimeInfo
->GetMIMEType(mimeType
);
765 channel
->SetContentType(mimeType
);
768 if (reason
== nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
769 reason
= nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
773 nsAutoString extension
;
774 int32_t dotidx
= fileName
.RFind(u
".");
776 extension
= Substring(fileName
, dotidx
+ 1);
779 // NB: ExternalHelperAppParent depends on this listener always being an
780 // nsExternalAppHandler. If this changes, make sure to update that code.
781 nsExternalAppHandler
* handler
= new nsExternalAppHandler(
782 mimeInfo
, extension
, aContentContext
, aWindowContext
, this, fileName
,
785 return NS_ERROR_OUT_OF_MEMORY
;
788 NS_ADDREF(*aStreamListener
= handler
);
792 NS_IMETHODIMP
nsExternalHelperAppService::DoContent(
793 const nsACString
& aMimeContentType
, nsIRequest
* aRequest
,
794 nsIInterfaceRequestor
* aContentContext
, bool aForceSave
,
795 nsIInterfaceRequestor
* aWindowContext
,
796 nsIStreamListener
** aStreamListener
) {
797 // Scripted interface requestors cannot return an instance of the
798 // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
799 // get to the window via `nsIDOMWindow`. Unfortunately, at that point we
800 // don't know whether the thing we got is an inner or outer window, so have to
801 // work with either one.
802 RefPtr
<BrowsingContext
> bc
;
803 nsCOMPtr
<nsIDOMWindow
> domWindow
= do_GetInterface(aContentContext
);
804 if (nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= do_QueryInterface(domWindow
)) {
805 bc
= outerWindow
->GetBrowsingContext();
806 } else if (nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
807 do_QueryInterface(domWindow
)) {
808 bc
= innerWindow
->GetBrowsingContext();
811 if (XRE_IsContentProcess()) {
812 return DoContentContentProcessHelper(aMimeContentType
, aRequest
, bc
,
813 aForceSave
, aWindowContext
,
817 nsresult rv
= CreateListener(aMimeContentType
, aRequest
, bc
, aForceSave
,
818 aWindowContext
, aStreamListener
);
822 NS_IMETHODIMP
nsExternalHelperAppService::ApplyDecodingForExtension(
823 const nsACString
& aExtension
, const nsACString
& aEncodingType
,
824 bool* aApplyDecoding
) {
825 *aApplyDecoding
= true;
827 for (i
= 0; i
< ArrayLength(nonDecodableExtensions
); ++i
) {
828 if (aExtension
.LowerCaseEqualsASCII(
829 nonDecodableExtensions
[i
].mFileExtension
) &&
830 aEncodingType
.LowerCaseEqualsASCII(
831 nonDecodableExtensions
[i
].mMimeType
)) {
832 *aApplyDecoding
= false;
839 nsresult
nsExternalHelperAppService::GetFileTokenForPath(
840 const char16_t
* aPlatformAppPath
, nsIFile
** aFile
) {
841 nsDependentString
platformAppPath(aPlatformAppPath
);
842 // First, check if we have an absolute path
843 nsIFile
* localFile
= nullptr;
844 nsresult rv
= NS_NewLocalFile(platformAppPath
, true, &localFile
);
845 if (NS_SUCCEEDED(rv
)) {
848 if (NS_FAILED((*aFile
)->Exists(&exists
)) || !exists
) {
850 return NS_ERROR_FILE_NOT_FOUND
;
855 // Second, check if file exists in mozilla program directory
856 rv
= NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR
, aFile
);
857 if (NS_SUCCEEDED(rv
)) {
858 rv
= (*aFile
)->Append(platformAppPath
);
859 if (NS_SUCCEEDED(rv
)) {
861 rv
= (*aFile
)->Exists(&exists
);
862 if (NS_SUCCEEDED(rv
) && exists
) return NS_OK
;
867 return NS_ERROR_NOT_AVAILABLE
;
870 //////////////////////////////////////////////////////////////////////////////////////////////////////
871 // begin external protocol service default implementation...
872 //////////////////////////////////////////////////////////////////////////////////////////////////////
873 NS_IMETHODIMP
nsExternalHelperAppService::ExternalProtocolHandlerExists(
874 const char* aProtocolScheme
, bool* aHandlerExists
) {
875 nsCOMPtr
<nsIHandlerInfo
> handlerInfo
;
876 nsresult rv
= GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme
),
877 getter_AddRefs(handlerInfo
));
878 if (NS_SUCCEEDED(rv
)) {
879 // See if we have any known possible handler apps for this
880 nsCOMPtr
<nsIMutableArray
> possibleHandlers
;
881 handlerInfo
->GetPossibleApplicationHandlers(
882 getter_AddRefs(possibleHandlers
));
885 possibleHandlers
->GetLength(&length
);
887 *aHandlerExists
= true;
892 // if not, fall back on an os-based handler
893 return OSProtocolHandlerExists(aProtocolScheme
, aHandlerExists
);
896 NS_IMETHODIMP
nsExternalHelperAppService::IsExposedProtocol(
897 const char* aProtocolScheme
, bool* aResult
) {
898 // check the per protocol setting first. it always takes precedence.
899 // if not set, then use the global setting.
901 nsAutoCString
prefName("network.protocol-handler.expose.");
902 prefName
+= aProtocolScheme
;
904 if (NS_SUCCEEDED(Preferences::GetBool(prefName
.get(), &val
))) {
909 // by default, no protocol is exposed. i.e., by default all link clicks must
910 // go through the external protocol service. most applications override this
912 *aResult
= Preferences::GetBool("network.protocol-handler.expose-all", false);
917 static const char kExternalProtocolPrefPrefix
[] =
918 "network.protocol-handler.external.";
919 static const char kExternalProtocolDefaultPref
[] =
920 "network.protocol-handler.external-default";
923 nsresult
nsExternalHelperAppService::EscapeURI(nsIURI
* aURI
, nsIURI
** aResult
) {
930 if (spec
.Find("%00") != -1) return NS_ERROR_MALFORMED_URI
;
932 nsAutoCString escapedSpec
;
933 nsresult rv
= NS_EscapeURL(spec
, esc_AlwaysCopy
| esc_ExtHandler
, escapedSpec
,
935 NS_ENSURE_SUCCESS(rv
, rv
);
937 nsCOMPtr
<nsIIOService
> ios(do_GetIOService());
938 return ios
->NewURI(escapedSpec
, nullptr, nullptr, aResult
);
941 bool ExternalProtocolIsBlockedBySandbox(
942 BrowsingContext
* aBrowsingContext
,
943 const bool aHasValidUserGestureActivation
) {
944 if (!StaticPrefs::dom_block_external_protocol_navigation_from_sandbox()) {
948 if (!aBrowsingContext
|| aBrowsingContext
->IsTop()) {
952 uint32_t sandboxFlags
= aBrowsingContext
->GetSandboxFlags();
954 if (sandboxFlags
== SANDBOXED_NONE
) {
958 if (!(sandboxFlags
& SANDBOXED_AUXILIARY_NAVIGATION
)) {
962 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION
)) {
966 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION_CUSTOM_PROTOCOLS
)) {
970 if (!(sandboxFlags
& SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION
) &&
971 aHasValidUserGestureActivation
) {
979 nsExternalHelperAppService::LoadURI(nsIURI
* aURI
,
980 nsIPrincipal
* aTriggeringPrincipal
,
981 nsIPrincipal
* aRedirectPrincipal
,
982 BrowsingContext
* aBrowsingContext
,
983 bool aTriggeredExternally
,
984 bool aHasValidUserGestureActivation
) {
985 NS_ENSURE_ARG_POINTER(aURI
);
987 if (XRE_IsContentProcess()) {
988 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
989 aURI
, aTriggeringPrincipal
, aRedirectPrincipal
, aBrowsingContext
,
990 aTriggeredExternally
, aHasValidUserGestureActivation
);
994 // Prevent sandboxed BrowsingContexts from navigating to external protocols.
995 // This only uses the sandbox flags of the target BrowsingContext of the
996 // load. The navigating document's CSP sandbox flags do not apply.
997 if (aBrowsingContext
&&
998 ExternalProtocolIsBlockedBySandbox(aBrowsingContext
,
999 aHasValidUserGestureActivation
)) {
1000 // Log an error to the web console of the sandboxed BrowsingContext.
1001 nsAutoString localizedMsg
;
1003 aURI
->GetSpec(spec
);
1005 AutoTArray
<nsString
, 1> params
= {NS_ConvertUTF8toUTF16(spec
)};
1006 nsresult rv
= nsContentUtils::FormatLocalizedString(
1007 nsContentUtils::eSECURITY_PROPERTIES
, "SandboxBlockedCustomProtocols",
1008 params
, localizedMsg
);
1009 NS_ENSURE_SUCCESS(rv
, rv
);
1011 // Log to the the parent window of the iframe. If there is no parent, fall
1012 // back to the iframe window itself.
1013 WindowContext
* windowContext
= aBrowsingContext
->GetParentWindowContext();
1014 if (!windowContext
) {
1015 windowContext
= aBrowsingContext
->GetCurrentWindowContext();
1018 // Skip logging if we still don't have a WindowContext.
1019 NS_ENSURE_TRUE(windowContext
, NS_ERROR_FAILURE
);
1021 nsContentUtils::ReportToConsoleByWindowID(
1022 localizedMsg
, nsIScriptError::errorFlag
, "Security"_ns
,
1023 windowContext
->InnerWindowId(),
1024 windowContext
->Canonical()->GetDocumentURI());
1029 nsCOMPtr
<nsIURI
> escapedURI
;
1030 nsresult rv
= EscapeURI(aURI
, getter_AddRefs(escapedURI
));
1031 NS_ENSURE_SUCCESS(rv
, rv
);
1033 nsAutoCString scheme
;
1034 escapedURI
->GetScheme(scheme
);
1035 if (scheme
.IsEmpty()) return NS_OK
; // must have a scheme
1037 // Deny load if the prefs say to do so
1038 nsAutoCString
externalPref(kExternalProtocolPrefPrefix
);
1039 externalPref
+= scheme
;
1040 bool allowLoad
= false;
1041 if (NS_FAILED(Preferences::GetBool(externalPref
.get(), &allowLoad
))) {
1042 // no scheme-specific value, check the default
1044 Preferences::GetBool(kExternalProtocolDefaultPref
, &allowLoad
))) {
1045 return NS_OK
; // missing default pref
1050 return NS_OK
; // explicitly denied
1053 // Now check if the principal is allowed to access the navigated context.
1054 // We allow navigating subframes, even if not same-origin - non-external
1055 // links can always navigate everywhere, so this is a minor additional
1056 // restriction, only aiming to prevent some types of spoofing attacks
1057 // from otherwise disjoint browsingcontext trees.
1058 if (aBrowsingContext
&& aTriggeringPrincipal
&&
1059 !StaticPrefs::security_allow_disjointed_external_uri_loads() &&
1060 // Add-on principals are always allowed:
1061 !BasePrincipal::Cast(aTriggeringPrincipal
)->AddonPolicy() &&
1062 // As is chrome code:
1063 !aTriggeringPrincipal
->IsSystemPrincipal()) {
1064 RefPtr
<BrowsingContext
> bc
= aBrowsingContext
;
1065 WindowGlobalParent
* wgp
= bc
->Canonical()->GetCurrentWindowGlobal();
1066 bool foundAccessibleFrame
= false;
1068 // Also allow this load if the target is a toplevel BC and contains a
1069 // non-web-controlled about:blank document
1070 if (bc
->IsTop() && !bc
->HadOriginalOpener() && wgp
) {
1071 RefPtr
<nsIURI
> uri
= wgp
->GetDocumentURI();
1072 foundAccessibleFrame
=
1073 uri
&& uri
->GetSpecOrDefault().EqualsLiteral("about:blank");
1076 while (!foundAccessibleFrame
) {
1078 foundAccessibleFrame
=
1079 aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal());
1081 // We have to get the parent via the bc, because there may not
1082 // be a window global for the innermost bc; see bug 1650162.
1083 BrowsingContext
* parent
= bc
->GetParent();
1088 wgp
= parent
->Canonical()->GetCurrentWindowGlobal();
1091 if (!foundAccessibleFrame
) {
1092 // See if this navigation could have come from a subframe.
1093 nsTArray
<RefPtr
<BrowsingContext
>> contexts
;
1094 aBrowsingContext
->GetAllBrowsingContextsInSubtree(contexts
);
1095 for (const auto& kid
: contexts
) {
1096 wgp
= kid
->Canonical()->GetCurrentWindowGlobal();
1097 if (wgp
&& aTriggeringPrincipal
->Subsumes(wgp
->DocumentPrincipal())) {
1098 foundAccessibleFrame
= true;
1104 if (!foundAccessibleFrame
) {
1105 return NS_OK
; // deny the load.
1109 nsCOMPtr
<nsIHandlerInfo
> handler
;
1110 rv
= GetProtocolHandlerInfo(scheme
, getter_AddRefs(handler
));
1111 NS_ENSURE_SUCCESS(rv
, rv
);
1113 nsCOMPtr
<nsIContentDispatchChooser
> chooser
=
1114 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv
);
1115 NS_ENSURE_SUCCESS(rv
, rv
);
1117 return chooser
->HandleURI(
1118 handler
, escapedURI
,
1119 aRedirectPrincipal
? aRedirectPrincipal
: aTriggeringPrincipal
,
1120 aBrowsingContext
, aTriggeredExternally
);
1123 //////////////////////////////////////////////////////////////////////////////////////////////////////
1124 // Methods related to deleting temporary files on exit
1125 //////////////////////////////////////////////////////////////////////////////////////////////////////
1128 nsresult
nsExternalHelperAppService::DeleteTemporaryFileHelper(
1129 nsIFile
* aTemporaryFile
, nsCOMArray
<nsIFile
>& aFileList
) {
1130 bool isFile
= false;
1132 // as a safety measure, make sure the nsIFile is really a file and not a
1133 // directory object.
1134 aTemporaryFile
->IsFile(&isFile
);
1135 if (!isFile
) return NS_OK
;
1137 aFileList
.AppendObject(aTemporaryFile
);
1143 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile
* aTemporaryFile
) {
1144 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryFilesList
);
1148 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1149 nsIFile
* aTemporaryFile
) {
1150 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryPrivateFilesList
);
1153 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1154 nsCOMArray
<nsIFile
>& fileList
) {
1155 int32_t numEntries
= fileList
.Count();
1157 for (int32_t index
= 0; index
< numEntries
; index
++) {
1158 localFile
= fileList
[index
];
1160 // First make the file writable, since the temp file is probably readonly.
1161 localFile
->SetPermissions(0600);
1162 localFile
->Remove(false);
1169 void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1170 ExpungeTemporaryFilesHelper(mTemporaryFilesList
);
1173 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1174 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList
);
1177 static const char kExternalWarningPrefPrefix
[] =
1178 "network.protocol-handler.warn-external.";
1179 static const char kExternalWarningDefaultPref
[] =
1180 "network.protocol-handler.warn-external-default";
1183 nsExternalHelperAppService::GetProtocolHandlerInfo(
1184 const nsACString
& aScheme
, nsIHandlerInfo
** aHandlerInfo
) {
1185 // XXX enterprise customers should be able to turn this support off with a
1186 // single master pref (maybe use one of the "exposed" prefs here?)
1189 nsresult rv
= GetProtocolHandlerInfoFromOS(aScheme
, &exists
, aHandlerInfo
);
1190 if (NS_FAILED(rv
)) {
1191 // Either it knows nothing, or we ran out of memory
1192 return NS_ERROR_FAILURE
;
1195 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1196 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1198 bool hasHandler
= false;
1199 (void)handlerSvc
->Exists(*aHandlerInfo
, &hasHandler
);
1201 rv
= handlerSvc
->FillHandlerInfo(*aHandlerInfo
, ""_ns
);
1202 if (NS_SUCCEEDED(rv
)) return NS_OK
;
1206 return SetProtocolHandlerDefaults(*aHandlerInfo
, exists
);
1210 nsExternalHelperAppService::SetProtocolHandlerDefaults(
1211 nsIHandlerInfo
* aHandlerInfo
, bool aOSHandlerExists
) {
1212 // this type isn't in our database, so we've only got an OS default handler,
1215 if (aOSHandlerExists
) {
1216 // we've got a default, so use it
1217 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::useSystemDefault
);
1219 // whether or not to ask the user depends on the warning preference
1220 nsAutoCString scheme
;
1221 aHandlerInfo
->GetType(scheme
);
1223 nsAutoCString
warningPref(kExternalWarningPrefPrefix
);
1224 warningPref
+= scheme
;
1226 if (NS_FAILED(Preferences::GetBool(warningPref
.get(), &warn
))) {
1227 // no scheme-specific value, check the default
1228 warn
= Preferences::GetBool(kExternalWarningDefaultPref
, true);
1230 aHandlerInfo
->SetAlwaysAskBeforeHandling(warn
);
1232 // If no OS default existed, we set the preferred action to alwaysAsk.
1233 // This really means not initialized (i.e. there's no available handler)
1234 // to all the code...
1235 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::alwaysAsk
);
1241 // XPCOM profile change observer
1243 nsExternalHelperAppService::Observe(nsISupports
* aSubject
, const char* aTopic
,
1244 const char16_t
* someData
) {
1245 if (!strcmp(aTopic
, "profile-before-change")) {
1246 ExpungeTemporaryFiles();
1247 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
1248 ExpungeTemporaryPrivateFiles();
1253 //////////////////////////////////////////////////////////////////////////////////////////////////////
1254 // begin external app handler implementation
1255 //////////////////////////////////////////////////////////////////////////////////////////////////////
1257 NS_IMPL_ADDREF(nsExternalAppHandler
)
1258 NS_IMPL_RELEASE(nsExternalAppHandler
)
1260 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler
)
1261 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStreamListener
)
1262 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
1263 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
1264 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher
)
1265 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
1266 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver
)
1267 NS_INTERFACE_MAP_ENTRY(nsINamed
)
1268 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler
)
1269 NS_INTERFACE_MAP_END
1271 nsExternalAppHandler::nsExternalAppHandler(
1272 nsIMIMEInfo
* aMIMEInfo
, const nsAString
& aFileExtension
,
1273 BrowsingContext
* aBrowsingContext
, nsIInterfaceRequestor
* aWindowContext
,
1274 nsExternalHelperAppService
* aExtProtSvc
,
1275 const nsAString
& aSuggestedFileName
, uint32_t aReason
, bool aForceSave
)
1276 : mMimeInfo(aMIMEInfo
),
1277 mBrowsingContext(aBrowsingContext
),
1278 mWindowContext(aWindowContext
),
1279 mSuggestedFileName(aSuggestedFileName
),
1280 mForceSave(aForceSave
),
1282 mStopRequestIssued(false),
1283 mIsFileChannel(false),
1284 mShouldCloseWindow(false),
1285 mHandleInternally(false),
1286 mDialogShowing(false),
1288 mTempFileIsExecutable(false),
1289 mTimeDownloadStarted(0),
1293 mDialogProgressListener(nullptr),
1296 mExtProtSvc(aExtProtSvc
) {
1297 // make sure the extention includes the '.'
1298 if (!aFileExtension
.IsEmpty() && aFileExtension
.First() != '.') {
1299 mFileExtension
= char16_t('.');
1301 mFileExtension
.Append(aFileExtension
);
1303 mBufferSize
= Preferences::GetUint("network.buffer.cache.size", 4096);
1306 nsExternalAppHandler::~nsExternalAppHandler() {
1307 MOZ_ASSERT(!mSaver
, "Saver should hold a reference to us until deleted");
1310 void nsExternalAppHandler::DidDivertRequest(nsIRequest
* request
) {
1311 MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1312 // Remove our request from the child loadGroup
1313 RetargetLoadNotifications(request
);
1316 NS_IMETHODIMP
nsExternalAppHandler::SetWebProgressListener(
1317 nsIWebProgressListener2
* aWebProgressListener
) {
1318 // This is always called by nsHelperDlg.js. Go ahead and register the
1319 // progress listener. At this point, we don't have mTransfer.
1320 mDialogProgressListener
= aWebProgressListener
;
1324 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFile(nsIFile
** aTarget
) {
1325 if (mFinalFileDestination
)
1326 *aTarget
= mFinalFileDestination
;
1328 *aTarget
= mTempFile
;
1330 NS_IF_ADDREF(*aTarget
);
1334 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec
) {
1335 // Use the real target if it's been set
1336 if (mFinalFileDestination
) return mFinalFileDestination
->IsExecutable(aExec
);
1338 // Otherwise, use the stored executable-ness of the temporary
1339 *aExec
= mTempFileIsExecutable
;
1343 NS_IMETHODIMP
nsExternalAppHandler::GetTimeDownloadStarted(PRTime
* aTime
) {
1344 *aTime
= mTimeDownloadStarted
;
1348 NS_IMETHODIMP
nsExternalAppHandler::GetContentLength(int64_t* aContentLength
) {
1349 *aContentLength
= mContentLength
;
1353 NS_IMETHODIMP
nsExternalAppHandler::GetBrowsingContextId(
1354 uint64_t* aBrowsingContextId
) {
1355 *aBrowsingContextId
= mBrowsingContext
? mBrowsingContext
->Id() : 0;
1359 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest
* request
) {
1360 // we are going to run the downloading of the helper app in our own little
1361 // docloader / load group context. so go ahead and force the creation of a
1362 // load group and doc loader for us to use...
1363 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1364 if (!aChannel
) return;
1366 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
1368 nsCOMPtr
<nsILoadGroup
> oldLoadGroup
;
1369 aChannel
->GetLoadGroup(getter_AddRefs(oldLoadGroup
));
1372 oldLoadGroup
->RemoveRequest(request
, nullptr, NS_BINDING_RETARGETED
);
1375 aChannel
->SetLoadGroup(nullptr);
1376 aChannel
->SetNotificationCallbacks(nullptr);
1378 nsCOMPtr
<nsIPrivateBrowsingChannel
> pbChannel
= do_QueryInterface(aChannel
);
1380 pbChannel
->SetPrivate(isPrivate
);
1384 nsresult
nsExternalAppHandler::SetUpTempFile(nsIChannel
* aChannel
) {
1385 // First we need to try to get the destination directory for the temporary
1387 nsresult rv
= GetDownloadDirectory(getter_AddRefs(mTempFile
));
1388 NS_ENSURE_SUCCESS(rv
, rv
);
1390 // At this point, we do not have a filename for the temp file. For security
1391 // purposes, this cannot be predictable, so we must use a cryptographic
1392 // quality PRNG to generate one.
1393 nsAutoCString tempLeafName
;
1394 rv
= GenerateRandomName(tempLeafName
);
1395 NS_ENSURE_SUCCESS(rv
, rv
);
1397 // now append our extension.
1399 mMimeInfo
->GetPrimaryExtension(ext
);
1400 if (!ext
.IsEmpty()) {
1401 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1402 if (ext
.First() != '.') tempLeafName
.Append('.');
1403 tempLeafName
.Append(ext
);
1406 // We need to temporarily create a dummy file with the correct
1407 // file extension to determine the executable-ness, so do this before adding
1408 // the extra .part extension.
1409 nsCOMPtr
<nsIFile
> dummyFile
;
1410 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dummyFile
));
1411 NS_ENSURE_SUCCESS(rv
, rv
);
1413 // Set the file name without .part
1414 rv
= dummyFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1415 NS_ENSURE_SUCCESS(rv
, rv
);
1416 rv
= dummyFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1417 NS_ENSURE_SUCCESS(rv
, rv
);
1419 // Store executable-ness then delete
1420 dummyFile
->IsExecutable(&mTempFileIsExecutable
);
1421 dummyFile
->Remove(false);
1423 // Add an additional .part to prevent the OS from running this file in the
1424 // default application.
1425 tempLeafName
.AppendLiteral(".part");
1427 rv
= mTempFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1428 // make this file unique!!!
1429 NS_ENSURE_SUCCESS(rv
, rv
);
1430 rv
= mTempFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1431 NS_ENSURE_SUCCESS(rv
, rv
);
1433 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1434 // This is a bit broken in the case when createUnique actually had to append
1435 // some numbers, because then we now have a filename like foo.bar-1.part and
1436 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1437 // this. But the other options are all bad too.... Ideally we'd have a way
1438 // to tell createUnique to put its unique marker before the extension that
1439 // comes before ".part" or something.
1440 rv
= mTempFile
->GetLeafName(mTempLeafName
);
1441 NS_ENSURE_SUCCESS(rv
, rv
);
1443 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName
, u
".part"_ns
),
1444 NS_ERROR_UNEXPECTED
);
1446 // Strip off the ".part" from mTempLeafName
1447 mTempLeafName
.Truncate(mTempLeafName
.Length() - ArrayLength(".part") + 1);
1449 MOZ_ASSERT(!mSaver
, "Output file initialization called more than once!");
1451 do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID
, &rv
);
1452 NS_ENSURE_SUCCESS(rv
, rv
);
1454 rv
= mSaver
->SetObserver(this);
1455 if (NS_FAILED(rv
)) {
1460 rv
= mSaver
->EnableSha256();
1461 NS_ENSURE_SUCCESS(rv
, rv
);
1463 rv
= mSaver
->EnableSignatureInfo();
1464 NS_ENSURE_SUCCESS(rv
, rv
);
1465 LOG("Enabled hashing and signature verification");
1467 rv
= mSaver
->SetTarget(mTempFile
, false);
1468 NS_ENSURE_SUCCESS(rv
, rv
);
1473 void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1474 nsIRequest
* aRequest
) {
1475 MOZ_ASSERT(aRequest
);
1477 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface(aRequest
);
1482 // Turn off content encoding conversions if needed
1483 bool applyConversion
= true;
1485 // First, check to see if conversion is already disabled. If so, we
1486 // have nothing to do here.
1487 encChannel
->GetApplyConversion(&applyConversion
);
1488 if (!applyConversion
) {
1492 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(mSourceUrl
));
1494 nsAutoCString extension
;
1495 sourceURL
->GetFileExtension(extension
);
1496 if (!extension
.IsEmpty()) {
1497 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
1498 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
1501 nsresult rv
= encEnum
->HasMore(&hasMore
);
1502 if (NS_SUCCEEDED(rv
) && hasMore
) {
1503 nsAutoCString encType
;
1504 rv
= encEnum
->GetNext(encType
);
1505 if (NS_SUCCEEDED(rv
) && !encType
.IsEmpty()) {
1506 MOZ_ASSERT(mExtProtSvc
);
1507 mExtProtSvc
->ApplyDecodingForExtension(extension
, encType
,
1515 encChannel
->SetApplyConversion(applyConversion
);
1518 already_AddRefed
<nsIInterfaceRequestor
>
1519 nsExternalAppHandler::GetDialogParent() {
1520 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= mWindowContext
;
1522 if (!dialogParent
&& mBrowsingContext
) {
1523 dialogParent
= do_QueryInterface(mBrowsingContext
->GetDOMWindow());
1525 if (!dialogParent
&& mBrowsingContext
&& XRE_IsParentProcess()) {
1526 RefPtr
<Element
> element
= mBrowsingContext
->Top()->GetEmbedderElement();
1528 dialogParent
= do_QueryInterface(element
->OwnerDoc()->GetWindow());
1531 return dialogParent
.forget();
1534 NS_IMETHODIMP
nsExternalAppHandler::OnStartRequest(nsIRequest
* request
) {
1535 MOZ_ASSERT(request
, "OnStartRequest without request?");
1537 // Set mTimeDownloadStarted here as the download has already started and
1538 // we want to record the start time before showing the filepicker.
1539 mTimeDownloadStarted
= PR_Now();
1543 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1546 nsAutoCString MIMEType
;
1548 mMimeInfo
->GetMIMEType(MIMEType
);
1552 aChannel
->GetURI(getter_AddRefs(mSourceUrl
));
1553 // HTTPS-Only/HTTPS-FirstMode tries to upgrade connections to https. Once
1554 // the download is in progress we set that flag so that timeout counter
1555 // measures do not kick in.
1556 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1557 bool isPrivateWin
= loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
1558 if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin
) ||
1559 nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin
)) {
1560 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
1561 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS
;
1562 loadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
1566 if (!mForceSave
&& StaticPrefs::browser_download_enable_spam_prevention() &&
1567 IsDownloadSpam(aChannel
)) {
1568 RecordDownloadTelemetry(aChannel
, "spam");
1572 mDownloadClassification
=
1573 nsContentSecurityUtils::ClassifyDownload(aChannel
, MIMEType
);
1575 if (mDownloadClassification
== nsITransfer::DOWNLOAD_FORBIDDEN
) {
1576 // If the download is rated as forbidden,
1577 // cancel the request so no ui knows about this.
1579 request
->Cancel(NS_ERROR_ABORT
);
1580 RecordDownloadTelemetry(aChannel
, "forbidden");
1584 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(request
));
1585 mIsFileChannel
= fileChan
!= nullptr;
1586 if (!mIsFileChannel
) {
1587 // It's possible that this request came from the child process and the
1588 // file channel actually lives there. If this returns true, then our
1589 // mSourceUrl will be an nsIFileURL anyway.
1590 nsCOMPtr
<dom::nsIExternalHelperAppParent
> parent(
1591 do_QueryInterface(request
));
1592 mIsFileChannel
= parent
&& parent
->WasFileChannel();
1595 // Get content length
1597 aChannel
->GetContentLength(&mContentLength
);
1600 if (mBrowsingContext
) {
1601 mMaybeCloseWindowHelper
= new MaybeCloseWindowHelper(mBrowsingContext
);
1602 mMaybeCloseWindowHelper
->SetShouldCloseWindow(mShouldCloseWindow
);
1603 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(request
, &rv
));
1604 // Determine whether a new window was opened specifically for this request
1608 props
->GetPropertyAsBool(u
"docshell.newWindowTarget"_ns
, &tmp
))) {
1609 mMaybeCloseWindowHelper
->SetShouldCloseWindow(tmp
);
1614 // retarget all load notifications to our docloader instead of the original
1615 // window's docloader...
1616 RetargetLoadNotifications(request
);
1618 // Close the underlying DOMWindow if it was opened specifically for the
1619 // download. We don't run this in the content process, since we have
1620 // an instance running in the parent as well, which will handle this
1622 if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper
) {
1623 mBrowsingContext
= mMaybeCloseWindowHelper
->MaybeCloseWindow();
1626 // In an IPC setting, we're allowing the child process, here, to make
1627 // decisions about decoding the channel (e.g. decompression). It will
1628 // still forward the decoded (uncompressed) data back to the parent.
1629 // Con: Uncompressed data means more IPC overhead.
1630 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1631 // Parent process doesn't need to expect CPU time on decompression.
1632 MaybeApplyDecodingForExtension(aChannel
);
1634 // At this point, the child process has done everything it can usefully do
1635 // for OnStartRequest.
1636 if (XRE_IsContentProcess()) {
1640 rv
= SetUpTempFile(aChannel
);
1641 if (NS_FAILED(rv
)) {
1642 nsresult transferError
= rv
;
1644 rv
= CreateFailedTransfer();
1645 if (NS_FAILED(rv
)) {
1646 LOG("Failed to create transfer to report failure."
1647 "Will fallback to prompter!");
1651 request
->Cancel(transferError
);
1654 if (mTempFile
) mTempFile
->GetPath(path
);
1656 SendStatusChange(kWriteError
, transferError
, request
, path
);
1658 RecordDownloadTelemetry(aChannel
, "savefailed");
1663 // Inform channel it is open on behalf of a download to throttle it during
1664 // page loads and prevent its caching.
1665 nsCOMPtr
<nsIHttpChannelInternal
> httpInternal
= do_QueryInterface(aChannel
);
1667 rv
= httpInternal
->SetChannelIsForDownload(true);
1668 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1671 if (mSourceUrl
->SchemeIs("data")) {
1672 // In case we're downloading a data:// uri
1673 // we don't want to apply AllowTopLevelNavigationToDataURI.
1674 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1675 loadInfo
->SetForceAllowDataURI(true);
1678 // now that the temp file is set up, find out if we need to invoke a dialog
1679 // asking the user what they want us to do with this content...
1681 // We can get here for three reasons: "can't handle", "sniffed type", or
1682 // "server sent content-disposition:attachment". In the first case we want
1683 // to honor the user's "always ask" pref; in the other two cases we want to
1684 // honor it only if the default action is "save". Opening attachments in
1685 // helper apps by default breaks some websites (especially if the attachment
1686 // is one part of a multipart document). Opening sniffed content in helper
1687 // apps by default introduces security holes that we'd rather not have.
1689 // So let's find out whether the user wants to be prompted. If he does not,
1690 // check mReason and the preferred action to see what we should do.
1692 bool alwaysAsk
= true;
1693 mMimeInfo
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1695 // But we *don't* ask if this mimeInfo didn't come from
1696 // our user configuration datastore and the user has said
1697 // at some point in the distant past that they don't
1698 // want to be asked. The latter fact would have been
1699 // stored in pref strings back in the old days.
1701 bool mimeTypeIsInDatastore
= false;
1702 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
1703 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1705 handlerSvc
->Exists(mMimeInfo
, &mimeTypeIsInDatastore
);
1707 if (!handlerSvc
|| !mimeTypeIsInDatastore
) {
1708 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF
,
1710 // Don't need to ask after all.
1712 // Make sure action matches pref (save to disk).
1713 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
1714 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF
,
1716 // Don't need to ask after all.
1720 } else if (MIMEType
.EqualsLiteral("text/plain")) {
1722 mMimeInfo
->GetPrimaryExtension(ext
);
1723 // If people are sending us ApplicationReputation-eligible files with
1724 // text/plain mimetypes, enforce asking the user what to do.
1725 if (!ext
.IsEmpty()) {
1726 nsAutoCString
dummyFileName("f");
1727 if (ext
.First() != '.') {
1728 dummyFileName
.Append(".");
1730 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1731 nsCOMPtr
<nsIApplicationReputationService
> appRep
=
1732 components::ApplicationReputation::Service();
1733 appRep
->IsBinary(dummyFileName
+ ext
, &alwaysAsk
);
1737 int32_t action
= nsIMIMEInfo::saveToDisk
;
1738 mMimeInfo
->GetPreferredAction(&action
);
1741 mReason
== nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
||
1742 (mReason
== nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
&&
1743 !StaticPrefs::browser_download_improvements_to_download_panel());
1745 // OK, now check why we're here
1746 if (!alwaysAsk
&& forcePrompt
) {
1747 // Force asking if we're not saving. See comment back when we fetched the
1748 // alwaysAsk boolean for details.
1749 alwaysAsk
= (action
!= nsIMIMEInfo::saveToDisk
);
1752 bool shouldAutomaticallyHandleInternally
=
1753 action
== nsIMIMEInfo::handleInternally
&&
1754 StaticPrefs::browser_download_improvements_to_download_panel();
1756 // If we're not asking, check we actually know what to do:
1758 alwaysAsk
= action
!= nsIMIMEInfo::saveToDisk
&&
1759 action
!= nsIMIMEInfo::useHelperApp
&&
1760 action
!= nsIMIMEInfo::useSystemDefault
&&
1761 !shouldAutomaticallyHandleInternally
;
1764 // If we're handling with the OS default and we are that default, force
1765 // asking, so we don't end up in an infinite loop:
1766 if (!alwaysAsk
&& action
== nsIMIMEInfo::useSystemDefault
) {
1767 bool areOSDefault
= false;
1768 alwaysAsk
= NS_SUCCEEDED(mMimeInfo
->IsCurrentAppOSDefault(&areOSDefault
)) &&
1770 } else if (!alwaysAsk
&& action
== nsIMIMEInfo::useHelperApp
) {
1771 nsCOMPtr
<nsIHandlerApp
> preferredApp
;
1772 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(preferredApp
));
1773 nsCOMPtr
<nsILocalHandlerApp
> handlerApp
= do_QueryInterface(preferredApp
);
1775 nsCOMPtr
<nsIFile
> executable
;
1776 handlerApp
->GetExecutable(getter_AddRefs(executable
));
1777 nsCOMPtr
<nsIFile
> ourselves
;
1779 // Despite the name, this really just fetches an nsIFile...
1780 NS_SUCCEEDED(NS_GetSpecialDirectory(XRE_EXECUTABLE_FILE
,
1781 getter_AddRefs(ourselves
)))) {
1782 ourselves
= nsMIMEInfoBase::GetCanonicalExecutable(ourselves
);
1783 executable
= nsMIMEInfoBase::GetCanonicalExecutable(executable
);
1784 bool isSameApp
= false;
1786 NS_FAILED(executable
->Equals(ourselves
, &isSameApp
)) || isSameApp
;
1791 // if we were told that we _must_ save to disk without asking, all the stuff
1792 // before this is irrelevant; override it
1795 action
= nsIMIMEInfo::saveToDisk
;
1796 shouldAutomaticallyHandleInternally
= false;
1798 // Additionally, if we are asked by the OS to open a local file,
1799 // automatically downloading it to create a second copy of that file doesn't
1800 // really make sense. We should ask the user what they want to do.
1801 if (mSourceUrl
->SchemeIs("file") && !alwaysAsk
&&
1802 action
== nsIMIMEInfo::saveToDisk
) {
1806 // If adding new checks, make sure this is the last check before telemetry
1807 // and going ahead with opening the file!
1809 /* We need to see whether the file we've got here could be
1810 * executable. If it could, we had better not try to open it!
1811 * We can skip this check, though, if we have a setting to open in a
1814 if (!alwaysAsk
&& action
!= nsIMIMEInfo::saveToDisk
&&
1815 !shouldAutomaticallyHandleInternally
) {
1816 nsCOMPtr
<nsIHandlerApp
> prefApp
;
1817 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(prefApp
));
1818 if (action
!= nsIMIMEInfo::useHelperApp
|| !prefApp
) {
1819 nsCOMPtr
<nsIFile
> fileToTest
;
1820 GetTargetFile(getter_AddRefs(fileToTest
));
1823 rv
= fileToTest
->IsExecutable(&isExecutable
);
1824 if (NS_FAILED(rv
) || mTempFileIsExecutable
||
1825 isExecutable
) { // checking NS_FAILED, because paranoia is good
1828 } else { // Paranoia is good here too, though this really should not
1831 "GetDownloadInfo returned a null file after the temp file has been "
1839 nsAutoCString actionTelem
;
1841 actionTelem
.AssignLiteral("ask");
1842 } else if (shouldAutomaticallyHandleInternally
) {
1843 actionTelem
.AssignLiteral("internal");
1844 } else if (action
== nsIMIMEInfo::useHelperApp
||
1845 action
== nsIMIMEInfo::useSystemDefault
) {
1846 actionTelem
.AssignLiteral("external");
1848 actionTelem
.AssignLiteral("save");
1851 RecordDownloadTelemetry(aChannel
, actionTelem
.get());
1854 // Display the dialog
1855 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1856 NS_ENSURE_SUCCESS(rv
, rv
);
1858 // this will create a reference cycle (the dialog holds a reference to us as
1859 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1860 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
1861 // Don't pop up the downloads panel since we're already going to pop up the
1862 // UCT dialog for basically the same effect.
1863 mDialogShowing
= true;
1864 rv
= mDialog
->Show(this, dialogParent
, mReason
);
1866 // what do we do if the dialog failed? I guess we should call Cancel and
1867 // abort the load....
1869 // We need to do the save/open immediately, then.
1870 if (action
== nsIMIMEInfo::useHelperApp
||
1871 action
== nsIMIMEInfo::useSystemDefault
||
1872 shouldAutomaticallyHandleInternally
) {
1873 // Check if the file is local, in which case just launch it from where it
1874 // is. Otherwise, set the file to launch once it's finished downloading.
1875 rv
= mIsFileChannel
? LaunchLocalFile()
1876 : SetDownloadToLaunch(
1877 shouldAutomaticallyHandleInternally
, nullptr);
1879 rv
= PromptForSaveDestination();
1885 void nsExternalAppHandler::RecordDownloadTelemetry(nsIChannel
* aChannel
,
1886 const char* aAction
) {
1887 // Telemetry for helper app dialog
1889 if (XRE_IsContentProcess()) {
1893 nsAutoCString reason
;
1895 case nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
:
1896 reason
.AssignLiteral("attachment");
1898 case nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
:
1899 reason
.AssignLiteral("sniffed");
1901 case nsIHelperAppLauncherDialog::REASON_CANTHANDLE
:
1903 reason
.AssignLiteral("other");
1907 nsAutoCString contentTypeTelem
;
1908 nsAutoCString contentType
;
1909 aChannel
->GetContentType(contentType
);
1910 if (contentType
.EqualsIgnoreCase(APPLICATION_PDF
)) {
1911 contentTypeTelem
.AssignLiteral("pdf");
1912 } else if (contentType
.EqualsIgnoreCase(APPLICATION_OCTET_STREAM
) ||
1913 contentType
.EqualsIgnoreCase(BINARY_OCTET_STREAM
)) {
1914 contentTypeTelem
.AssignLiteral("octetstream");
1916 contentTypeTelem
.AssignLiteral("other");
1919 CopyableTArray
<mozilla::Telemetry::EventExtraEntry
> extra(1);
1920 extra
.AppendElement(
1921 mozilla::Telemetry::EventExtraEntry
{"type"_ns
, contentTypeTelem
});
1922 extra
.AppendElement(mozilla::Telemetry::EventExtraEntry
{"reason"_ns
, reason
});
1924 mozilla::Telemetry::RecordEvent(
1925 mozilla::Telemetry::EventID::Downloads_Helpertype_Unknowntype
,
1926 mozilla::Some(aAction
), mozilla::Some(extra
));
1929 bool nsExternalAppHandler::IsDownloadSpam(nsIChannel
* aChannel
) {
1930 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1931 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
1932 mozilla::services::GetPermissionManager();
1933 nsCOMPtr
<nsIPrincipal
> principal
= loadInfo
->TriggeringPrincipal();
1934 bool exactHostMatch
= false;
1935 constexpr auto type
= "automatic-download"_ns
;
1936 nsCOMPtr
<nsIPermission
> permission
;
1938 permissionManager
->GetPermissionObject(principal
, type
, exactHostMatch
,
1939 getter_AddRefs(permission
));
1942 uint32_t capability
;
1943 permission
->GetCapability(&capability
);
1944 if (capability
== nsIPermissionManager::DENY_ACTION
) {
1946 aChannel
->Cancel(NS_ERROR_ABORT
);
1949 if (capability
== nsIPermissionManager::ALLOW_ACTION
) {
1952 // If no action is set (i.e: null), we set PROMPT_ACTION by default,
1953 // which will notify the Downloads UI to open the panel on the next request.
1954 if (capability
== nsIPermissionManager::PROMPT_ACTION
) {
1955 nsCOMPtr
<nsIObserverService
> observerService
=
1956 mozilla::services::GetObserverService();
1957 RefPtr
<BrowsingContext
> browsingContext
;
1958 loadInfo
->GetBrowsingContext(getter_AddRefs(browsingContext
));
1960 nsAutoCString cStringURI
;
1961 loadInfo
->TriggeringPrincipal()->GetPrePath(cStringURI
);
1962 observerService
->NotifyObservers(
1963 browsingContext
, "blocked-automatic-download",
1964 NS_ConvertASCIItoUTF16(cStringURI
.get()).get());
1965 // FIXME: In order to escape memory leaks, currently we cancel blocked
1966 // downloads. This is temporary solution, because download data should be
1967 // kept in order to restart the blocked download.
1969 aChannel
->Cancel(NS_ERROR_ABORT
);
1974 if (!loadInfo
->GetHasValidUserGestureActivation()) {
1975 permissionManager
->AddFromPrincipal(
1976 principal
, type
, nsIPermissionManager::PROMPT_ACTION
,
1977 nsIPermissionManager::EXPIRE_NEVER
, 0 /* expire time */);
1983 // Convert error info into proper message text and send OnStatusChange
1984 // notification to the dialog progress listener or nsITransfer implementation.
1985 void nsExternalAppHandler::SendStatusChange(ErrorType type
, nsresult rv
,
1986 nsIRequest
* aRequest
,
1987 const nsString
& path
) {
1988 const char* msgId
= nullptr;
1990 case NS_ERROR_OUT_OF_MEMORY
:
1995 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1996 // Out of space on target volume.
2000 case NS_ERROR_FILE_READ_ONLY
:
2001 // Attempt to write to read/only file.
2005 case NS_ERROR_FILE_ACCESS_DENIED
:
2006 if (type
== kWriteError
) {
2007 // Attempt to write without sufficient permissions.
2008 #if defined(ANDROID)
2009 // On Android this means the SD card is present but
2010 // unavailable (read-only).
2011 msgId
= "SDAccessErrorCardReadOnly";
2013 msgId
= "accessError";
2016 msgId
= "launchError";
2020 case NS_ERROR_FILE_NOT_FOUND
:
2021 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
2022 // Helper app not found, let's verify this happened on launch
2023 if (type
== kLaunchError
) {
2024 msgId
= "helperAppNotFound";
2027 #if defined(ANDROID)
2028 else if (type
== kWriteError
) {
2029 // On Android this means the SD card is missing (not in
2031 msgId
= "SDAccessErrorCardMissing";
2038 // Generic read/write/launch error message.
2041 msgId
= "readError";
2044 msgId
= "writeError";
2047 msgId
= "launchError";
2054 nsExternalHelperAppService::sLog
, LogLevel::Error
,
2055 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32
"\n",
2056 msgId
, type
, mDialogProgressListener
.get(), mTransfer
.get(),
2057 static_cast<uint32_t>(rv
)));
2059 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Error
,
2060 (" path='%s'\n", NS_ConvertUTF16toUTF8(path
).get()));
2062 // Get properties file bundle and extract status string.
2063 nsCOMPtr
<nsIStringBundleService
> stringService
=
2064 mozilla::components::StringBundle::Service();
2065 if (stringService
) {
2066 nsCOMPtr
<nsIStringBundle
> bundle
;
2067 if (NS_SUCCEEDED(stringService
->CreateBundle(
2068 "chrome://global/locale/nsWebBrowserPersist.properties",
2069 getter_AddRefs(bundle
)))) {
2070 nsAutoString msgText
;
2071 AutoTArray
<nsString
, 1> strings
= {path
};
2072 if (NS_SUCCEEDED(bundle
->FormatStringFromName(msgId
, strings
, msgText
))) {
2073 if (mDialogProgressListener
) {
2074 // We have a listener, let it handle the error.
2075 mDialogProgressListener
->OnStatusChange(
2076 nullptr, (type
== kReadError
) ? aRequest
: nullptr, rv
,
2078 } else if (mTransfer
) {
2079 mTransfer
->OnStatusChange(nullptr,
2080 (type
== kReadError
) ? aRequest
: nullptr,
2082 } else if (XRE_IsParentProcess()) {
2083 // We don't have a listener. Simply show the alert ourselves.
2084 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2086 nsCOMPtr
<nsIPrompt
> prompter(do_GetInterface(dialogParent
, &qiRv
));
2088 bundle
->FormatStringFromName("title", strings
, title
);
2091 nsExternalHelperAppService::sLog
, LogLevel::Debug
,
2092 ("mBrowsingContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
2093 ", title='%s', msg='%s'",
2094 mBrowsingContext
.get(), prompter
.get(),
2095 static_cast<uint32_t>(qiRv
), NS_ConvertUTF16toUTF8(title
).get(),
2096 NS_ConvertUTF16toUTF8(msgText
).get()));
2098 // If we didn't have a prompter we will try and get a window
2099 // instead, get it's docshell and use it to alert the user.
2101 nsCOMPtr
<nsPIDOMWindowOuter
> window(do_GetInterface(dialogParent
));
2102 if (!window
|| !window
->GetDocShell()) {
2106 prompter
= do_GetInterface(window
->GetDocShell(), &qiRv
);
2108 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Debug
,
2109 ("No prompter from mBrowsingContext, using DocShell, "
2110 "window=0x%p, docShell=0x%p, "
2111 "prompter=0x%p, qi rv=0x%08" PRIX32
,
2112 window
.get(), window
->GetDocShell(), prompter
.get(),
2113 static_cast<uint32_t>(qiRv
)));
2115 // If we still don't have a prompter, there's nothing else we
2116 // can do so just return.
2118 MOZ_LOG(nsExternalHelperAppService::sLog
, LogLevel::Error
,
2119 ("No prompter from DocShell, no way to alert user"));
2124 // We should always have a prompter at this point.
2125 prompter
->Alert(title
.get(), msgText
.get());
2133 nsExternalAppHandler::OnDataAvailable(nsIRequest
* request
,
2134 nsIInputStream
* inStr
,
2135 uint64_t sourceOffset
, uint32_t count
) {
2136 nsresult rv
= NS_OK
;
2137 // first, check to see if we've been canceled....
2138 if (mCanceled
|| !mSaver
) {
2139 // then go cancel our underlying channel too
2140 return request
->Cancel(NS_BINDING_ABORTED
);
2143 // read the data out of the stream and write it to the temp file.
2147 nsCOMPtr
<nsIStreamListener
> saver
= do_QueryInterface(mSaver
);
2148 rv
= saver
->OnDataAvailable(request
, inStr
, sourceOffset
, count
);
2149 if (NS_SUCCEEDED(rv
)) {
2150 // Send progress notification.
2152 mTransfer
->OnProgressChange64(nullptr, request
, mProgress
,
2153 mContentLength
, mProgress
,
2157 // An error occurred, notify listener.
2158 nsAutoString tempFilePath
;
2160 mTempFile
->GetPath(tempFilePath
);
2162 SendStatusChange(kReadError
, rv
, request
, tempFilePath
);
2164 // Cancel the download.
2171 NS_IMETHODIMP
nsExternalAppHandler::OnStopRequest(nsIRequest
* request
,
2173 LOG("nsExternalAppHandler::OnStopRequest\n"
2174 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08" PRIX32
"\n",
2175 mCanceled
, mTransfer
.get(), static_cast<uint32_t>(aStatus
));
2177 mStopRequestIssued
= true;
2179 // Cancel if the request did not complete successfully.
2180 if (!mCanceled
&& NS_FAILED(aStatus
)) {
2181 // Send error notification.
2182 nsAutoString tempFilePath
;
2183 if (mTempFile
) mTempFile
->GetPath(tempFilePath
);
2184 SendStatusChange(kReadError
, aStatus
, request
, tempFilePath
);
2189 // first, check to see if we've been canceled....
2190 if (mCanceled
|| !mSaver
) {
2194 return mSaver
->Finish(NS_OK
);
2198 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver
* aSaver
,
2204 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver
* aSaver
,
2206 LOG("nsExternalAppHandler::OnSaveComplete\n"
2207 " aSaver=0x%p, aStatus=0x%08" PRIX32
", mCanceled=%d, mTransfer=0x%p\n",
2208 aSaver
, static_cast<uint32_t>(aStatus
), mCanceled
, mTransfer
.get());
2211 // Save the hash and signature information
2212 (void)mSaver
->GetSha256Hash(mHash
);
2213 (void)mSaver
->GetSignatureInfo(mSignatureInfo
);
2215 // Free the reference that the saver keeps on us, even if we couldn't get
2219 // Save the redirect information.
2220 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2222 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
2223 nsresult rv
= NS_OK
;
2224 nsCOMPtr
<nsIMutableArray
> redirectChain
=
2225 do_CreateInstance(NS_ARRAY_CONTRACTID
, &rv
);
2226 NS_ENSURE_SUCCESS(rv
, rv
);
2227 LOG("nsExternalAppHandler: Got %zu redirects\n",
2228 loadInfo
->RedirectChain().Length());
2229 for (nsIRedirectHistoryEntry
* entry
: loadInfo
->RedirectChain()) {
2230 redirectChain
->AppendElement(entry
);
2232 mRedirects
= redirectChain
;
2235 if (NS_FAILED(aStatus
)) {
2237 mTempFile
->GetPath(path
);
2239 // It may happen when e10s is enabled that there will be no transfer
2240 // object available to communicate status as expected by the system.
2241 // Let's try and create a temporary transfer object to take care of this
2242 // for us, we'll fall back to using the prompt service if we absolutely
2245 // We don't care if this fails.
2246 CreateFailedTransfer();
2249 SendStatusChange(kWriteError
, aStatus
, nullptr, path
);
2250 if (!mCanceled
) Cancel(aStatus
);
2255 // Notify the transfer object that we are done if the user has chosen an
2256 // action. If the user hasn't chosen an action, the progress listener
2257 // (nsITransfer) will be notified in CreateTransfer.
2259 NotifyTransfer(aStatus
);
2265 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus
) {
2266 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2267 MOZ_ASSERT(mTransfer
, "We must have an nsITransfer");
2269 LOG("Notifying progress listener");
2271 if (NS_SUCCEEDED(aStatus
)) {
2272 (void)mTransfer
->SetSha256Hash(mHash
);
2273 (void)mTransfer
->SetSignatureInfo(mSignatureInfo
);
2274 (void)mTransfer
->SetRedirects(mRedirects
);
2275 (void)mTransfer
->OnProgressChange64(
2276 nullptr, nullptr, mProgress
, mContentLength
, mProgress
, mContentLength
);
2279 (void)mTransfer
->OnStateChange(nullptr, nullptr,
2280 nsIWebProgressListener::STATE_STOP
|
2281 nsIWebProgressListener::STATE_IS_REQUEST
|
2282 nsIWebProgressListener::STATE_IS_NETWORK
,
2285 // This nsITransfer object holds a reference to us (we are its observer), so
2286 // we need to release the reference to break a reference cycle (and therefore
2287 // to prevent leaking). We do this even if the previous calls failed.
2288 mTransfer
= nullptr;
2291 NS_IMETHODIMP
nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo
** aMIMEInfo
) {
2292 *aMIMEInfo
= mMimeInfo
;
2293 NS_ADDREF(*aMIMEInfo
);
2297 NS_IMETHODIMP
nsExternalAppHandler::GetSource(nsIURI
** aSourceURI
) {
2298 NS_ENSURE_ARG(aSourceURI
);
2299 *aSourceURI
= mSourceUrl
;
2300 NS_IF_ADDREF(*aSourceURI
);
2304 NS_IMETHODIMP
nsExternalAppHandler::GetSuggestedFileName(
2305 nsAString
& aSuggestedFileName
) {
2306 aSuggestedFileName
= mSuggestedFileName
;
2310 nsresult
nsExternalAppHandler::CreateTransfer() {
2311 LOG("nsExternalAppHandler::CreateTransfer");
2313 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2314 // We are back from the helper app dialog (where the user chooses to save or
2315 // open), but we aren't done processing the load. in this case, throw up a
2316 // progress dialog so the user can see what's going on.
2317 // Also, release our reference to mDialog. We don't need it anymore, and we
2318 // need to break the reference cycle.
2320 if (!mDialogProgressListener
) {
2321 NS_WARNING("The dialog should nullify the dialog progress listener");
2323 // In case of a non acceptable download, we need to cancel the request and
2324 // pass a FailedTransfer for the Download UI.
2325 if (mDownloadClassification
!= nsITransfer::DOWNLOAD_ACCEPTABLE
) {
2327 mRequest
->Cancel(NS_ERROR_ABORT
);
2329 mSaver
->Finish(NS_ERROR_ABORT
);
2332 return CreateFailedTransfer();
2336 // We must be able to create an nsITransfer object. If not, it doesn't matter
2337 // much that we can't launch the helper application or save to disk. Work on
2338 // a local copy rather than mTransfer until we know we succeeded, to make it
2339 // clearer that this function is re-entrant.
2340 nsCOMPtr
<nsITransfer
> transfer
=
2341 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2342 NS_ENSURE_SUCCESS(rv
, rv
);
2344 // Initialize the download
2345 nsCOMPtr
<nsIURI
> target
;
2346 rv
= NS_NewFileURI(getter_AddRefs(target
), mFinalFileDestination
);
2347 NS_ENSURE_SUCCESS(rv
, rv
);
2349 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2350 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mRequest
);
2351 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= nullptr;
2353 referrerInfo
= httpChannel
->GetReferrerInfo();
2356 if (mBrowsingContext
) {
2357 rv
= transfer
->InitWithBrowsingContext(
2358 mSourceUrl
, target
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
, mTempFile
,
2359 this, channel
&& NS_UsePrivateBrowsing(channel
),
2360 mDownloadClassification
, referrerInfo
, !mDialogShowing
,
2361 mBrowsingContext
, mHandleInternally
, nullptr);
2363 rv
= transfer
->Init(mSourceUrl
, nullptr, target
, u
""_ns
, mMimeInfo
,
2364 mTimeDownloadStarted
, mTempFile
, this,
2365 channel
&& NS_UsePrivateBrowsing(channel
),
2366 mDownloadClassification
, referrerInfo
, !mDialogShowing
);
2368 mDialogShowing
= false;
2370 NS_ENSURE_SUCCESS(rv
, rv
);
2372 // If we were cancelled since creating the transfer, just return. It is
2373 // always ok to return NS_OK if we are cancelled. Callers of this function
2374 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2379 rv
= transfer
->OnStateChange(nullptr, mRequest
,
2380 nsIWebProgressListener::STATE_START
|
2381 nsIWebProgressListener::STATE_IS_REQUEST
|
2382 nsIWebProgressListener::STATE_IS_NETWORK
,
2384 NS_ENSURE_SUCCESS(rv
, rv
);
2391 // Finally, save the transfer to mTransfer.
2392 mTransfer
= transfer
;
2395 // While we were bringing up the progress dialog, we actually finished
2396 // processing the url. If that's the case then mStopRequestIssued will be
2397 // true and OnSaveComplete has been called.
2398 if (mStopRequestIssued
&& !mSaver
&& mTransfer
) {
2399 NotifyTransfer(NS_OK
);
2405 nsresult
nsExternalAppHandler::CreateFailedTransfer() {
2407 nsCOMPtr
<nsITransfer
> transfer
=
2408 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2409 NS_ENSURE_SUCCESS(rv
, rv
);
2411 // We won't pass the temp file to the transfer, so if we have one it needs to
2415 mSaver
->Finish(NS_BINDING_ABORTED
);
2418 mTempFile
->Remove(false);
2421 nsCOMPtr
<nsIURI
> pseudoTarget
;
2422 if (!mFinalFileDestination
) {
2423 // If we don't have a download directory we're kinda screwed but it's OK
2424 // we'll still report the error via the prompter.
2425 nsCOMPtr
<nsIFile
> pseudoFile
;
2426 rv
= GetDownloadDirectory(getter_AddRefs(pseudoFile
), true);
2427 NS_ENSURE_SUCCESS(rv
, rv
);
2429 // Append the default suggested filename. If the user restarts the transfer
2430 // we will re-trigger a filename check anyway to ensure that it is unique.
2431 rv
= pseudoFile
->Append(mSuggestedFileName
);
2432 NS_ENSURE_SUCCESS(rv
, rv
);
2434 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), pseudoFile
);
2435 NS_ENSURE_SUCCESS(rv
, rv
);
2437 // Initialize the target, if present
2438 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), mFinalFileDestination
);
2439 NS_ENSURE_SUCCESS(rv
, rv
);
2442 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2443 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mRequest
);
2444 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= nullptr;
2446 referrerInfo
= httpChannel
->GetReferrerInfo();
2449 if (mBrowsingContext
) {
2450 rv
= transfer
->InitWithBrowsingContext(
2451 mSourceUrl
, pseudoTarget
, u
""_ns
, mMimeInfo
, mTimeDownloadStarted
,
2452 mTempFile
, this, channel
&& NS_UsePrivateBrowsing(channel
),
2453 mDownloadClassification
, referrerInfo
, true, mBrowsingContext
,
2454 mHandleInternally
, httpChannel
);
2456 rv
= transfer
->Init(mSourceUrl
, nullptr, pseudoTarget
, u
""_ns
, mMimeInfo
,
2457 mTimeDownloadStarted
, mTempFile
, this,
2458 channel
&& NS_UsePrivateBrowsing(channel
),
2459 mDownloadClassification
, referrerInfo
, true);
2461 NS_ENSURE_SUCCESS(rv
, rv
);
2463 // Our failed transfer is ready.
2464 mTransfer
= std::move(transfer
);
2469 nsresult
nsExternalAppHandler::SaveDestinationAvailable(nsIFile
* aFile
,
2470 bool aDialogWasShown
) {
2472 if (aDialogWasShown
) {
2473 mDialogShowing
= true;
2475 ContinueSave(aFile
);
2477 Cancel(NS_BINDING_ABORTED
);
2483 void nsExternalAppHandler::RequestSaveDestination(
2484 const nsString
& aDefaultFile
, const nsString
& aFileExtension
) {
2485 // Display the dialog
2486 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2487 // "AutoDownload" w/o showing a prompt
2488 nsresult rv
= NS_OK
;
2490 // Get helper app launcher dialog.
2491 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
2493 Cancel(NS_BINDING_ABORTED
);
2498 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we
2499 // can't unescape it because the dialog is implemented by a JS component which
2500 // doesn't have a window so no unescape routine is defined...
2502 // Now, be sure to keep |this| alive, and the dialog
2503 // If we don't do this, users that close the helper app dialog while the file
2504 // picker is up would cause Cancel() to be called, and the dialog would be
2505 // released, which would release this object too, which would crash.
2507 RefPtr
<nsExternalAppHandler
> kungFuDeathGrip(this);
2508 nsCOMPtr
<nsIHelperAppLauncherDialog
> dlg(mDialog
);
2509 nsCOMPtr
<nsIInterfaceRequestor
> dialogParent
= GetDialogParent();
2511 rv
= dlg
->PromptForSaveToFileAsync(this, dialogParent
, aDefaultFile
.get(),
2512 aFileExtension
.get(), mForceSave
);
2513 if (NS_FAILED(rv
)) {
2514 Cancel(NS_BINDING_ABORTED
);
2518 // PromptForSaveDestination should only be called by the helper app dialog which
2519 // allows the user to say launch with application or save to disk.
2520 NS_IMETHODIMP
nsExternalAppHandler::PromptForSaveDestination() {
2521 if (mCanceled
) return NS_OK
;
2523 if (!StaticPrefs::browser_download_improvements_to_download_panel() ||
2525 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
2528 if (mSuggestedFileName
.IsEmpty()) {
2529 RequestSaveDestination(mTempLeafName
, mFileExtension
);
2531 nsAutoString fileExt
;
2532 int32_t pos
= mSuggestedFileName
.RFindChar('.');
2534 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
2536 if (fileExt
.IsEmpty()) {
2537 fileExt
= mFileExtension
;
2540 RequestSaveDestination(mSuggestedFileName
, fileExt
);
2545 nsresult
nsExternalAppHandler::ContinueSave(nsIFile
* aNewFileLocation
) {
2546 if (mCanceled
) return NS_OK
;
2548 MOZ_ASSERT(aNewFileLocation
, "Must be called with a non-null file");
2550 int32_t action
= nsIMIMEInfo::saveToDisk
;
2551 mMimeInfo
->GetPreferredAction(&action
);
2553 action
== nsIMIMEInfo::handleInternally
&&
2554 StaticPrefs::browser_download_improvements_to_download_panel();
2556 nsresult rv
= NS_OK
;
2557 nsCOMPtr
<nsIFile
> fileToUse
= aNewFileLocation
;
2558 mFinalFileDestination
= fileToUse
;
2560 // Move what we have in the final directory, but append .part
2561 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2562 // saver if we are done (Finish has been called) but OnSaverComplete has
2564 if (mFinalFileDestination
&& mSaver
&& !mStopRequestIssued
) {
2565 nsCOMPtr
<nsIFile
> movedFile
;
2566 mFinalFileDestination
->Clone(getter_AddRefs(movedFile
));
2568 nsAutoCString randomChars
;
2569 rv
= GenerateRandomName(randomChars
);
2570 if (NS_SUCCEEDED(rv
)) {
2571 // Get the leaf name, strip any extensions, then
2572 // add random bytes, followed by the extensions and '.part'.
2573 nsAutoString leafName
;
2574 mFinalFileDestination
->GetLeafName(leafName
);
2575 auto nameWithoutExtensionLength
= leafName
.FindChar('.');
2576 nsAutoString
extensions(u
"");
2577 if (nameWithoutExtensionLength
== kNotFound
) {
2578 nameWithoutExtensionLength
= leafName
.Length();
2580 extensions
= Substring(leafName
, nameWithoutExtensionLength
);
2582 leafName
.Truncate(nameWithoutExtensionLength
);
2584 nsAutoString suffix
= u
"."_ns
+ NS_ConvertASCIItoUTF16(randomChars
) +
2585 extensions
+ u
".part"_ns
;
2587 // Deal with MAX_PATH on Windows. Worth noting that the original
2588 // path for mFinalFileDestination must be valid for us to get
2589 // here: either SetDownloadToLaunch or the caller of
2590 // SaveDestinationAvailable has called CreateUnique or similar
2591 // to ensure both a unique name and one that isn't too long.
2592 // The only issue is we're making it longer to get the part
2595 mFinalFileDestination
->GetPath(path
);
2596 CheckedInt
<uint16_t> fullPathLength
=
2597 CheckedInt
<uint16_t>(path
.Length()) + 1 + randomChars
.Length() +
2598 ArrayLength(".part");
2599 if (!fullPathLength
.isValid()) {
2600 leafName
.Truncate();
2601 } else if (fullPathLength
.value() > MAX_PATH
) {
2602 int32_t leafNameRemaining
=
2603 (int32_t)leafName
.Length() - (fullPathLength
.value() - MAX_PATH
);
2604 leafName
.Truncate(std::max(leafNameRemaining
, 0));
2607 leafName
.Append(suffix
);
2608 movedFile
->SetLeafName(leafName
);
2610 rv
= mSaver
->SetTarget(movedFile
, true);
2611 if (NS_FAILED(rv
)) {
2613 mTempFile
->GetPath(path
);
2614 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2619 mTempFile
= movedFile
;
2624 // The helper app dialog has told us what to do and we have a final file
2626 rv
= CreateTransfer();
2627 // If we fail to create the transfer, Cancel.
2628 if (NS_FAILED(rv
)) {
2636 // SetDownloadToLaunch should only be called by the helper app dialog which
2637 // allows the user to say launch with application or save to disk.
2638 NS_IMETHODIMP
nsExternalAppHandler::SetDownloadToLaunch(
2639 bool aHandleInternally
, nsIFile
* aNewFileLocation
) {
2640 if (mCanceled
) return NS_OK
;
2642 mHandleInternally
= aHandleInternally
;
2644 // Now that the user has elected to launch the downloaded file with a helper
2645 // app, we're justified in removing the 'salted' name. We'll rename to what
2646 // was specified in mSuggestedFileName after the download is done prior to
2647 // launching the helper app. So that any existing file of that name won't be
2648 // overwritten we call CreateUnique(). Also note that we use the same
2649 // directory as originally downloaded so the download can be renamed in place
2651 nsCOMPtr
<nsIFile
> fileToUse
;
2652 if (aNewFileLocation
&&
2653 StaticPrefs::browser_download_improvements_to_download_panel()) {
2654 fileToUse
= aNewFileLocation
;
2656 (void)GetDownloadDirectory(getter_AddRefs(fileToUse
));
2658 if (mSuggestedFileName
.IsEmpty()) {
2659 // Keep using the leafname of the temp file, since we're just starting a
2661 mSuggestedFileName
= mTempLeafName
;
2665 // Ensure we don't double-append the file extension if it matches:
2666 if (StringEndsWith(mSuggestedFileName
, mFileExtension
,
2667 nsCaseInsensitiveStringComparator
)) {
2668 fileToUse
->Append(mSuggestedFileName
);
2670 fileToUse
->Append(mSuggestedFileName
+ mFileExtension
);
2673 fileToUse
->Append(mSuggestedFileName
);
2677 nsresult rv
= fileToUse
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
2678 if (NS_SUCCEEDED(rv
)) {
2679 mFinalFileDestination
= fileToUse
;
2680 // launch the progress window now that the user has picked the desired
2682 rv
= CreateTransfer();
2683 if (NS_FAILED(rv
)) {
2687 // Cancel the download and report an error. We do not want to end up in
2688 // a state where it appears that we have a normal download that is
2689 // pointing to a file that we did not actually create.
2691 mTempFile
->GetPath(path
);
2692 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2698 nsresult
nsExternalAppHandler::LaunchLocalFile() {
2699 nsCOMPtr
<nsIFileURL
> fileUrl(do_QueryInterface(mSourceUrl
));
2703 Cancel(NS_BINDING_ABORTED
);
2704 nsCOMPtr
<nsIFile
> file
;
2705 nsresult rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2707 if (NS_SUCCEEDED(rv
)) {
2708 rv
= mMimeInfo
->LaunchWithFile(file
);
2709 if (NS_SUCCEEDED(rv
)) return NS_OK
;
2712 if (file
) file
->GetPath(path
);
2713 // If we get here, an error happened
2714 SendStatusChange(kLaunchError
, rv
, nullptr, path
);
2718 NS_IMETHODIMP
nsExternalAppHandler::Cancel(nsresult aReason
) {
2719 NS_ENSURE_ARG(NS_FAILED(aReason
));
2727 // We are still writing to the target file. Give the saver a chance to
2728 // close the target file, then notify the transfer object if necessary in
2729 // the OnSaveComplete callback.
2730 mSaver
->Finish(aReason
);
2733 if (mStopRequestIssued
&& mTempFile
) {
2734 // This branch can only happen when the user cancels the helper app dialog
2735 // when the request has completed. The temp file has to be removed here,
2736 // because mSaver has been released at that time with the temp file left.
2737 (void)mTempFile
->Remove(false);
2740 // Notify the transfer object that the download has been canceled, if the
2741 // user has already chosen an action and we didn't notify already.
2743 NotifyTransfer(aReason
);
2747 // Break our reference cycle with the helper app dialog (set up in
2750 mDialogShowing
= false;
2754 // Release the listener, to break the reference cycle with it (we are the
2755 // observer of the listener).
2756 mDialogProgressListener
= nullptr;
2761 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char* prefName
,
2762 const char* aContentType
) {
2763 // Search the obsolete pref strings.
2764 nsAutoCString prefCString
;
2765 Preferences::GetCString(prefName
, prefCString
);
2766 if (prefCString
.IsEmpty()) {
2767 // Default is true, if not found in the pref string.
2771 NS_UnescapeURL(prefCString
);
2772 nsACString::const_iterator start
, end
;
2773 prefCString
.BeginReading(start
);
2774 prefCString
.EndReading(end
);
2775 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType
), start
,
2780 nsExternalAppHandler::GetName(nsACString
& aName
) {
2781 aName
.AssignLiteral("nsExternalAppHandler");
2785 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2786 // The following section contains our nsIMIMEService implementation and related
2789 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2791 // nsIMIMEService methods
2792 NS_IMETHODIMP
nsExternalHelperAppService::GetFromTypeAndExtension(
2793 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
2794 nsIMIMEInfo
** _retval
) {
2795 MOZ_ASSERT(!aMIMEType
.IsEmpty() || !aFileExt
.IsEmpty(),
2796 "Give me something to work with");
2797 MOZ_DIAGNOSTIC_ASSERT(aFileExt
.FindChar('\0') == kNotFound
,
2798 "The extension should never contain null characters");
2799 LOG("Getting mimeinfo from type '%s' ext '%s'\n",
2800 PromiseFlatCString(aMIMEType
).get(), PromiseFlatCString(aFileExt
).get());
2804 // OK... we need a type. Get one.
2805 nsAutoCString
typeToUse(aMIMEType
);
2806 if (typeToUse
.IsEmpty()) {
2807 nsresult rv
= GetTypeFromExtension(aFileExt
, typeToUse
);
2808 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
2811 // We promise to only send lower case mime types to the OS
2812 ToLowerCase(typeToUse
);
2814 // First, ask the OS for a mime info
2816 nsresult rv
= GetMIMEInfoFromOS(typeToUse
, aFileExt
, &found
, _retval
);
2817 if (NS_WARN_IF(NS_FAILED(rv
))) {
2821 LOG("OS gave back 0x%p - found: %i\n", *_retval
, found
);
2822 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2823 if (!*_retval
) return NS_ERROR_OUT_OF_MEMORY
;
2825 // The handler service can make up for bad mime types by checking the file
2826 // extension. If the mime type is known (in extras or in the handler
2827 // service), we stop it doing so by flipping this bool to true.
2828 bool trustMIMEType
= false;
2830 // Check extras - not everything we support will be known by the OS store,
2831 // unfortunately, and it may even miss some extensions that we know should
2832 // be accepted. We only do this for non-octet-stream mimetypes, because
2833 // our information for octet-stream would lead to us trying to open all such
2834 // files as Binary file with exe, com or bin extension regardless of the
2836 if (!typeToUse
.Equals(APPLICATION_OCTET_STREAM
,
2837 nsCaseInsensitiveCStringComparator
)) {
2838 rv
= FillMIMEInfoForMimeTypeFromExtras(typeToUse
, !found
, *_retval
);
2839 LOG("Searched extras (by type), rv 0x%08" PRIX32
"\n",
2840 static_cast<uint32_t>(rv
));
2841 trustMIMEType
= NS_SUCCEEDED(rv
);
2842 found
= found
|| NS_SUCCEEDED(rv
);
2845 // Now, let's see if we can find something in our datastore.
2846 // This will not overwrite the OS information that interests us
2847 // (i.e. default application, default app. description)
2848 nsCOMPtr
<nsIHandlerService
> handlerSvc
=
2849 do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2851 bool hasHandler
= false;
2852 (void)handlerSvc
->Exists(*_retval
, &hasHandler
);
2854 rv
= handlerSvc
->FillHandlerInfo(*_retval
, ""_ns
);
2855 LOG("Data source: Via type: retval 0x%08" PRIx32
"\n",
2856 static_cast<uint32_t>(rv
));
2857 trustMIMEType
= trustMIMEType
|| NS_SUCCEEDED(rv
);
2859 rv
= NS_ERROR_NOT_AVAILABLE
;
2862 found
= found
|| NS_SUCCEEDED(rv
);
2865 // If we still haven't found anything, try finding a match for
2866 // an extension in extras first:
2867 if (!found
&& !aFileExt
.IsEmpty()) {
2868 rv
= FillMIMEInfoForExtensionFromExtras(aFileExt
, *_retval
);
2869 LOG("Searched extras (by ext), rv 0x%08" PRIX32
"\n",
2870 static_cast<uint32_t>(rv
));
2873 // Then check the handler service - but only do so if we really do not know
2874 // the mimetype. This avoids overwriting good mimetype info with bad file
2876 if ((!found
|| !trustMIMEType
) && handlerSvc
&& !aFileExt
.IsEmpty()) {
2877 nsAutoCString overrideType
;
2878 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, overrideType
);
2879 if (NS_SUCCEEDED(rv
) && !overrideType
.IsEmpty()) {
2880 // We can't check handlerSvc->Exists() here, because we have a
2881 // overideType. That's ok, it just results in some console noise.
2882 // (If there's no handler for the override type, it throws)
2883 rv
= handlerSvc
->FillHandlerInfo(*_retval
, overrideType
);
2884 LOG("Data source: Via ext: retval 0x%08" PRIx32
"\n",
2885 static_cast<uint32_t>(rv
));
2886 found
= found
|| NS_SUCCEEDED(rv
);
2890 // If we still don't have a match, at least set the file description
2891 // to `${aFileExt} File` if it's empty:
2892 if (!found
&& !aFileExt
.IsEmpty()) {
2893 // XXXzpao This should probably be localized
2894 nsAutoCString
desc(aFileExt
);
2895 desc
.AppendLiteral(" File");
2896 (*_retval
)->SetDescription(NS_ConvertUTF8toUTF16(desc
));
2897 LOG("Falling back to 'File' file description\n");
2900 // Sometimes, OSes give us bad data. We have a set of forbidden extensions
2901 // for some MIME types. If the primary extension is forbidden,
2902 // overwrite it with a known-good one. See bug 1571247 for context.
2903 nsAutoCString primaryExtension
;
2904 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2905 if (!primaryExtension
.EqualsIgnoreCase(PromiseFlatCString(aFileExt
).get())) {
2906 if (MaybeReplacePrimaryExtension(primaryExtension
, *_retval
)) {
2907 (*_retval
)->GetPrimaryExtension(primaryExtension
);
2911 // Finally, check if we got a file extension and if yes, if it is an
2912 // extension on the mimeinfo, in which case we want it to be the primary one
2913 if (!aFileExt
.IsEmpty()) {
2914 bool matches
= false;
2915 (*_retval
)->ExtensionExists(aFileExt
, &matches
);
2916 LOG("Extension '%s' matches mime info: %i\n",
2917 PromiseFlatCString(aFileExt
).get(), matches
);
2919 nsAutoCString fileExt
;
2920 ToLowerCase(aFileExt
, fileExt
);
2921 (*_retval
)->SetPrimaryExtension(fileExt
);
2922 primaryExtension
= fileExt
;
2926 // Overwrite with a generic description if the primary extension for the
2927 // type is in our list; these are file formats supported by Firefox and
2928 // we don't want other brands positioning themselves as the sole viewer
2930 if (!primaryExtension
.IsEmpty()) {
2931 for (const char* ext
: descriptionOverwriteExtensions
) {
2932 if (primaryExtension
.Equals(ext
)) {
2933 nsCOMPtr
<nsIStringBundleService
> bundleService
=
2934 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
2935 NS_ENSURE_SUCCESS(rv
, rv
);
2936 nsCOMPtr
<nsIStringBundle
> unknownContentTypeBundle
;
2937 rv
= bundleService
->CreateBundle(
2938 "chrome://mozapps/locale/downloads/unknownContentType.properties",
2939 getter_AddRefs(unknownContentTypeBundle
));
2940 if (NS_SUCCEEDED(rv
)) {
2941 nsAutoCString
stringName(ext
);
2942 stringName
.AppendLiteral("ExtHandlerDescription");
2943 nsAutoString handlerDescription
;
2944 rv
= unknownContentTypeBundle
->GetStringFromName(stringName
.get(),
2945 handlerDescription
);
2946 if (NS_SUCCEEDED(rv
)) {
2947 (*_retval
)->SetDescription(handlerDescription
);
2955 if (LOG_ENABLED()) {
2957 (*_retval
)->GetMIMEType(type
);
2959 LOG("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type
.get(),
2960 primaryExtension
.get());
2967 nsExternalHelperAppService::GetTypeFromExtension(const nsACString
& aFileExt
,
2968 nsACString
& aContentType
) {
2969 // OK. We want to try the following sources of mimetype information, in this
2971 // 1. defaultMimeEntries array
2972 // 2. OS-provided information
2973 // 3. our "extras" array
2974 // 4. Information from plugins
2975 // 5. The "ext-to-type-mapping" category
2976 // Note that, we are intentionally not looking at the handler service, because
2977 // that can be affected by websites, which leads to undesired behavior.
2979 // Early return if called with an empty extension parameter
2980 if (aFileExt
.IsEmpty()) {
2981 return NS_ERROR_NOT_AVAILABLE
;
2984 // First of all, check our default entries
2985 for (auto& entry
: defaultMimeEntries
) {
2986 if (aFileExt
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
2987 aContentType
= entry
.mMimeType
;
2993 if (GetMIMETypeFromOSForExtension(aFileExt
, aContentType
)) {
2997 // Check extras array.
2998 bool found
= GetTypeFromExtras(aFileExt
, aContentType
);
3003 // Let's see if an extension added something
3004 nsCOMPtr
<nsICategoryManager
> catMan(
3005 do_GetService("@mozilla.org/categorymanager;1"));
3007 // The extension in the category entry is always stored as lowercase
3008 nsAutoCString
lowercaseFileExt(aFileExt
);
3009 ToLowerCase(lowercaseFileExt
);
3010 // Read the MIME type from the category entry, if available
3013 catMan
->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt
, type
);
3014 if (NS_SUCCEEDED(rv
)) {
3015 aContentType
= type
;
3020 return NS_ERROR_NOT_AVAILABLE
;
3023 NS_IMETHODIMP
nsExternalHelperAppService::GetPrimaryExtension(
3024 const nsACString
& aMIMEType
, const nsACString
& aFileExt
,
3025 nsACString
& _retval
) {
3026 NS_ENSURE_ARG(!aMIMEType
.IsEmpty());
3028 nsCOMPtr
<nsIMIMEInfo
> mi
;
3030 GetFromTypeAndExtension(aMIMEType
, aFileExt
, getter_AddRefs(mi
));
3031 if (NS_FAILED(rv
)) return rv
;
3033 return mi
->GetPrimaryExtension(_retval
);
3036 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromURI(
3037 nsIURI
* aURI
, nsACString
& aContentType
) {
3038 NS_ENSURE_ARG_POINTER(aURI
);
3039 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
3040 aContentType
.Truncate();
3042 // First look for a file to use. If we have one, we just use that.
3043 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
);
3045 nsCOMPtr
<nsIFile
> file
;
3046 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
3047 if (NS_SUCCEEDED(rv
)) {
3048 rv
= GetTypeFromFile(file
, aContentType
);
3049 if (NS_SUCCEEDED(rv
)) {
3050 // we got something!
3056 // Now try to get an nsIURL so we don't have to do our own parsing
3057 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
3060 rv
= url
->GetFileExtension(ext
);
3061 if (NS_FAILED(rv
)) return rv
;
3062 if (ext
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
3064 UnescapeFragment(ext
, url
, ext
);
3066 return GetTypeFromExtension(ext
, aContentType
);
3069 // no url, let's give the raw spec a shot
3070 nsAutoCString specStr
;
3071 rv
= aURI
->GetSpec(specStr
);
3072 if (NS_FAILED(rv
)) return rv
;
3073 UnescapeFragment(specStr
, aURI
, specStr
);
3075 // find the file extension (if any)
3076 int32_t extLoc
= specStr
.RFindChar('.');
3077 int32_t specLength
= specStr
.Length();
3078 if (-1 != extLoc
&& extLoc
!= specLength
- 1 &&
3079 // nothing over 20 chars long can be sanely considered an
3080 // extension.... Dat dere would be just data.
3081 specLength
- extLoc
< 20) {
3082 return GetTypeFromExtension(Substring(specStr
, extLoc
+ 1), aContentType
);
3085 // We found no information; say so.
3086 return NS_ERROR_NOT_AVAILABLE
;
3089 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromFile(
3090 nsIFile
* aFile
, nsACString
& aContentType
) {
3091 NS_ENSURE_ARG_POINTER(aFile
);
3094 // Get the Extension
3095 nsAutoString fileName
;
3096 rv
= aFile
->GetLeafName(fileName
);
3097 if (NS_FAILED(rv
)) return rv
;
3099 nsAutoCString fileExt
;
3100 if (!fileName
.IsEmpty()) {
3101 int32_t len
= fileName
.Length();
3102 for (int32_t i
= len
; i
>= 0; i
--) {
3103 if (fileName
[i
] == char16_t('.')) {
3104 CopyUTF16toUTF8(Substring(fileName
, i
+ 1), fileExt
);
3110 if (fileExt
.IsEmpty()) return NS_ERROR_FAILURE
;
3112 return GetTypeFromExtension(fileExt
, aContentType
);
3115 nsresult
nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
3116 const nsACString
& aContentType
, bool aOverwriteDescription
,
3117 nsIMIMEInfo
* aMIMEInfo
) {
3118 NS_ENSURE_ARG(aMIMEInfo
);
3120 NS_ENSURE_ARG(!aContentType
.IsEmpty());
3122 // Look for default entry with matching mime type.
3123 nsAutoCString
MIMEType(aContentType
);
3124 ToLowerCase(MIMEType
);
3125 for (auto entry
: extraMimeEntries
) {
3126 if (MIMEType
.Equals(entry
.mMimeType
)) {
3127 // This is the one. Set attributes appropriately.
3128 nsDependentCString
extensions(entry
.mFileExtensions
);
3129 nsACString::const_iterator start
, end
;
3130 extensions
.BeginReading(start
);
3131 extensions
.EndReading(end
);
3132 while (start
!= end
) {
3133 nsACString::const_iterator cursor
= start
;
3134 mozilla::Unused
<< FindCharInReadable(',', cursor
, end
);
3135 aMIMEInfo
->AppendExtension(Substring(start
, cursor
));
3136 // If a comma was found, skip it for the next search.
3137 start
= cursor
!= end
? ++cursor
: cursor
;
3141 aMIMEInfo
->GetDescription(desc
);
3142 if (aOverwriteDescription
|| desc
.IsEmpty()) {
3143 aMIMEInfo
->SetDescription(NS_ConvertASCIItoUTF16(entry
.mDescription
));
3149 return NS_ERROR_NOT_AVAILABLE
;
3152 nsresult
nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
3153 const nsACString
& aExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3155 bool found
= GetTypeFromExtras(aExtension
, type
);
3156 if (!found
) return NS_ERROR_NOT_AVAILABLE
;
3157 return FillMIMEInfoForMimeTypeFromExtras(type
, true, aMIMEInfo
);
3160 bool nsExternalHelperAppService::MaybeReplacePrimaryExtension(
3161 const nsACString
& aPrimaryExtension
, nsIMIMEInfo
* aMIMEInfo
) {
3162 for (const auto& entry
: sForbiddenPrimaryExtensions
) {
3163 if (aPrimaryExtension
.LowerCaseEqualsASCII(entry
.mFileExtension
)) {
3164 nsDependentCString
mime(entry
.mMimeType
);
3165 for (const auto& extraEntry
: extraMimeEntries
) {
3166 if (mime
.LowerCaseEqualsASCII(extraEntry
.mMimeType
)) {
3167 nsDependentCString
goodExts(extraEntry
.mFileExtensions
);
3168 int32_t commaPos
= goodExts
.FindChar(',');
3169 commaPos
= commaPos
== kNotFound
? goodExts
.Length() : commaPos
;
3170 auto goodExt
= Substring(goodExts
, 0, commaPos
);
3171 aMIMEInfo
->SetPrimaryExtension(goodExt
);
3180 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString
& aExtension
,
3181 nsACString
& aMIMEType
) {
3182 NS_ASSERTION(!aExtension
.IsEmpty(), "Empty aExtension parameter!");
3184 // Look for default entry with matching extension.
3185 nsDependentCString::const_iterator start
, end
, iter
;
3186 int32_t numEntries
= ArrayLength(extraMimeEntries
);
3187 for (int32_t index
= 0; index
< numEntries
; index
++) {
3188 nsDependentCString
extList(extraMimeEntries
[index
].mFileExtensions
);
3189 extList
.BeginReading(start
);
3190 extList
.EndReading(end
);
3192 while (start
!= end
) {
3193 FindCharInReadable(',', iter
, end
);
3194 if (Substring(start
, iter
)
3195 .Equals(aExtension
, nsCaseInsensitiveCStringComparator
)) {
3196 aMIMEType
= extraMimeEntries
[index
].mMimeType
;
3209 bool nsExternalHelperAppService::GetMIMETypeFromOSForExtension(
3210 const nsACString
& aExtension
, nsACString
& aMIMEType
) {
3212 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
3214 GetMIMEInfoFromOS(""_ns
, aExtension
, &found
, getter_AddRefs(mimeInfo
));
3215 return NS_SUCCEEDED(rv
) && found
&& mimeInfo
&&
3216 NS_SUCCEEDED(mimeInfo
->GetMIMEType(aMIMEType
));
3219 nsresult
nsExternalHelperAppService::GetMIMEInfoFromOS(
3220 const nsACString
& aMIMEType
, const nsACString
& aFileExt
, bool* aFound
,
3221 nsIMIMEInfo
** aMIMEInfo
) {
3222 *aMIMEInfo
= nullptr;
3224 return NS_ERROR_NOT_IMPLEMENTED
;
3227 bool nsExternalHelperAppService::GetFileNameFromChannel(nsIChannel
* aChannel
,
3228 nsAString
& aFileName
,
3234 aChannel
->GetURI(aURI
);
3235 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(*aURI
);
3237 // Check if we have a POST request, in which case we don't want to use
3238 // the url's extension
3239 bool allowURLExt
= !net::ChannelIsPost(aChannel
);
3241 // Check if we had a query string - we don't want to check the URL
3242 // extension if a query is present in the URI
3243 // If we already know we don't want to check the URL extension, don't
3244 // bother checking the query
3245 if (url
&& allowURLExt
) {
3246 nsAutoCString query
;
3248 // We only care about the query for HTTP and HTTPS URLs
3249 if (url
->SchemeIs("http") || url
->SchemeIs("https")) {
3250 url
->GetQuery(query
);
3253 // Only get the extension if the query is empty; if it isn't, then the
3254 // extension likely belongs to a cgi script and isn't helpful
3255 allowURLExt
= query
.IsEmpty();
3258 aChannel
->GetContentDispositionFilename(aFileName
);
3264 nsExternalHelperAppService::GetValidFileName(nsIChannel
* aChannel
,
3265 const nsACString
& aType
,
3266 nsIURI
* aOriginalURI
,
3268 nsAString
& aOutFileName
) {
3269 nsCOMPtr
<nsIURI
> uri
;
3270 bool allowURLExtension
=
3271 GetFileNameFromChannel(aChannel
, aOutFileName
, getter_AddRefs(uri
));
3273 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
3274 aOutFileName
, aType
, uri
, aOriginalURI
, aFlags
, allowURLExtension
);
3279 nsExternalHelperAppService::ValidateFileNameForSaving(
3280 const nsAString
& aFileName
, const nsACString
& aType
, uint32_t aFlags
,
3281 nsAString
& aOutFileName
) {
3282 nsAutoString
fileName(aFileName
);
3284 // Just sanitize the filename only.
3285 if (aFlags
& VALIDATE_SANITIZE_ONLY
) {
3286 SanitizeFileName(fileName
, aFlags
);
3288 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
= ValidateFileNameForSaving(
3289 fileName
, aType
, nullptr, nullptr, aFlags
, true);
3292 aOutFileName
= fileName
;
3296 already_AddRefed
<nsIMIMEInfo
>
3297 nsExternalHelperAppService::ValidateFileNameForSaving(
3298 nsAString
& aFileName
, const nsACString
& aMimeType
, nsIURI
* aURI
,
3299 nsIURI
* aOriginalURI
, uint32_t aFlags
, bool aAllowURLExtension
) {
3300 nsAutoString
fileName(aFileName
);
3301 nsAutoCString extension
;
3302 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
3304 bool isBinaryType
= aMimeType
.EqualsLiteral(APPLICATION_OCTET_STREAM
) ||
3305 aMimeType
.EqualsLiteral(BINARY_OCTET_STREAM
) ||
3306 aMimeType
.EqualsLiteral("application/x-msdownload");
3308 // We get the mime service here even though we're the default implementation
3309 // of it, so it's possible to override only the mime service and not need to
3310 // reimplement the whole external helper app service itself.
3311 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
3313 if (fileName
.IsEmpty()) {
3314 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
3315 // Try to extract the file name from the url and use that as a first
3316 // pass as the leaf name of our temp file...
3318 nsAutoCString leafName
;
3319 url
->GetFileName(leafName
);
3320 if (!leafName
.IsEmpty()) {
3321 if (NS_SUCCEEDED(UnescapeFragment(leafName
, url
, fileName
))) {
3322 CopyUTF8toUTF16(leafName
, aFileName
); // use escaped name
3326 // Only get the extension from the URL if allowed, or if this
3327 // is a binary type in which case the type might not be valid
3329 if (aAllowURLExtension
|| isBinaryType
) {
3330 url
->GetFileExtension(extension
);
3334 // Determine the current extension for the filename.
3335 int32_t dotidx
= fileName
.RFind(u
".");
3337 CopyUTF16toUTF8(Substring(fileName
, dotidx
+ 1), extension
);
3341 if (aFlags
& VALIDATE_GUESS_FROM_EXTENSION
) {
3342 nsAutoCString mimeType
;
3343 if (!extension
.IsEmpty()) {
3344 mimeService
->GetFromTypeAndExtension(EmptyCString(), extension
,
3345 getter_AddRefs(mimeInfo
));
3347 mimeInfo
->GetMIMEType(mimeType
);
3351 if (mimeType
.IsEmpty()) {
3352 // Extension lookup gave us no useful match, so use octet-stream
3354 mimeService
->GetFromTypeAndExtension(
3355 nsLiteralCString(APPLICATION_OCTET_STREAM
), extension
,
3356 getter_AddRefs(mimeInfo
));
3358 } else if (!aMimeType
.IsEmpty()) {
3359 // If this is a binary type, include the extension as a hint to get
3360 // the mime info. For other types, the mime type itself should be
3362 // The special case for application/ogg is because that type could
3363 // actually be used for a video which can better be determined by the
3364 // extension. This is tested by browser_save_video.js.
3366 isBinaryType
|| aMimeType
.EqualsLiteral(APPLICATION_OGG
);
3367 mimeService
->GetFromTypeAndExtension(
3368 aMimeType
, useExtension
? extension
: EmptyCString(),
3369 getter_AddRefs(mimeInfo
));
3371 // But if no primary extension was returned, this mime type is probably
3372 // an unknown type. Look it up again but this time supply the extension.
3373 nsAutoCString primaryExtension
;
3374 mimeInfo
->GetPrimaryExtension(primaryExtension
);
3375 if (primaryExtension
.IsEmpty()) {
3376 mimeService
->GetFromTypeAndExtension(aMimeType
, extension
,
3377 getter_AddRefs(mimeInfo
));
3383 // Windows ignores terminating dots. So we have to as well, so
3384 // that our security checks do "the right thing"
3385 fileName
.Trim(".", false);
3387 // If an empty filename is allowed, then return early. It will be saved
3388 // using the filename of the temporary file that was created for the download.
3389 if (aFlags
& VALIDATE_ALLOW_EMPTY
&& fileName
.IsEmpty()) {
3390 aFileName
.Truncate();
3391 return mimeInfo
.forget();
3394 // This section modifies the extension on the filename if it isn't valid for
3395 // the given content type.
3397 bool isValidExtension
;
3398 if (extension
.IsEmpty() ||
3399 NS_FAILED(mimeInfo
->ExtensionExists(extension
, &isValidExtension
)) ||
3400 !isValidExtension
) {
3401 // Skip these checks for text and binary, so we don't append the unneeded
3402 // .txt or other extension.
3403 if (aMimeType
.EqualsLiteral(TEXT_PLAIN
) || isBinaryType
) {
3404 extension
.Truncate();
3406 nsAutoCString
originalExtension(extension
);
3407 // If an original url was supplied, see if it has a valid extension.
3408 bool useOldExtension
= false;
3410 nsCOMPtr
<nsIURL
> originalURL(do_QueryInterface(aOriginalURI
));
3412 nsAutoCString uriExtension
;
3413 originalURL
->GetFileExtension(uriExtension
);
3414 if (!uriExtension
.IsEmpty()) {
3415 mimeInfo
->ExtensionExists(uriExtension
, &useOldExtension
);
3416 if (useOldExtension
) {
3417 extension
= uriExtension
;
3423 if (!useOldExtension
) {
3424 // If the filename doesn't have a valid extension, or we don't know
3425 // the extension, try to use the primary extension for the type. If we
3426 // don't know the primary extension for the type, just continue with
3427 // the existing extension, or leave the filename with no extension.
3428 nsAutoCString primaryExtension
;
3429 mimeInfo
->GetPrimaryExtension(primaryExtension
);
3430 if (!primaryExtension
.IsEmpty()) {
3431 extension
= primaryExtension
;
3435 // If an suitable extension was found, we will append to or replace the
3436 // existing extension.
3437 if (!extension
.IsEmpty()) {
3438 ModifyExtensionType modify
= ShouldModifyExtension(
3439 mimeInfo
, aFlags
& VALIDATE_FORCE_APPEND_EXTENSION
,
3441 if (modify
== ModifyExtension_Replace
) {
3442 int32_t dotidx
= fileName
.RFind(u
".");
3444 // Remove the existing extension and replace it.
3445 fileName
.Truncate(dotidx
);
3449 // Otherwise, just append the proper extension to the end of the
3450 // filename, adding to the invalid extension that might already be
3452 if (modify
!= ModifyExtension_Ignore
) {
3453 fileName
.AppendLiteral(".");
3454 fileName
.Append(NS_ConvertUTF8toUTF16(extension
));
3462 nsLocalFile::CheckForReservedFileName(fileName
);
3465 // If the extension is .lnk or .local, replace it with .download, as these
3466 // types of files can have signifance on Windows. This happens for any file,
3467 // not just those with the shortcut mime type.
3468 if (StringEndsWith(fileName
, u
".lnk"_ns
) ||
3469 StringEndsWith(fileName
, u
".local"_ns
)) {
3470 fileName
.AppendLiteral(".download");
3473 // If no filename is present, use a default filename.
3474 if (!(aFlags
& VALIDATE_NO_DEFAULT_FILENAME
) &&
3475 (fileName
.Length() == 0 || fileName
.RFind(u
".") == 0)) {
3476 nsCOMPtr
<nsIStringBundleService
> stringService
=
3477 mozilla::components::StringBundle::Service();
3478 if (stringService
) {
3479 nsCOMPtr
<nsIStringBundle
> bundle
;
3480 if (NS_SUCCEEDED(stringService
->CreateBundle(
3481 "chrome://global/locale/contentAreaCommands.properties",
3482 getter_AddRefs(bundle
)))) {
3483 nsAutoString defaultFileName
;
3484 bundle
->GetStringFromName("DefaultSaveFileName", defaultFileName
);
3485 // Append any existing extension to the default filename.
3486 fileName
= defaultFileName
+ fileName
;
3490 // Use 'index' as a last resort.
3491 if (!fileName
.Length()) {
3492 fileName
.AssignLiteral("index");
3496 // Make the filename safe for the filesystem.
3497 SanitizeFileName(fileName
, aFlags
);
3499 aFileName
= fileName
;
3500 return mimeInfo
.forget();
3503 void nsExternalHelperAppService::SanitizeFileName(nsAString
& aFileName
,
3505 nsAutoString
fileName(aFileName
);
3507 // Replace known invalid characters.
3508 fileName
.ReplaceChar(u
"" KNOWN_PATH_SEPARATORS
, u
'_');
3509 fileName
.ReplaceChar(u
"" FILE_ILLEGAL_CHARACTERS
, u
' ');
3510 fileName
.StripChar(char16_t(0));
3512 const char16_t
*startStr
, *endStr
;
3513 fileName
.BeginReading(startStr
);
3514 fileName
.EndReading(endStr
);
3516 // True if multiple consecutive whitespace characters should
3517 // be replaced by single space ' '.
3518 bool collapseWhitespace
= !(aFlags
& VALIDATE_DONT_COLLAPSE_WHITESPACE
);
3520 // The maximum filename length differs based on the platform:
3521 // Windows (FAT/NTFS) stores filenames as a maximum of 255 UTF-16 code units.
3522 // Mac (APFS) stores filenames with a maximum 255 of UTF-8 code units.
3523 // Linux (ext3/ext4...) stores filenames with a maximum 255 bytes.
3524 // So here we just use the maximum of 255 bytes.
3525 // 0 means don't truncate at a maximum size.
3526 const uint32_t maxBytes
=
3527 (aFlags
& VALIDATE_DONT_TRUNCATE
) ? 0 : kDefaultMaxFileNameLength
;
3529 // True if the last character added was whitespace.
3530 bool lastWasWhitespace
= false;
3532 // Length of the filename that fits into the maximum size excluding the
3533 // extension and period.
3534 int32_t longFileNameEnd
= -1;
3536 // Index of the last character added that was not a character that can be
3537 // trimmed off of the end of the string. Trimmable characters are whitespace,
3538 // periods and the vowel separator u'\u180e'. If all the characters after this
3539 // point are trimmable characters, truncate the string to this point after
3540 // iterating over the filename.
3541 int32_t lastNonTrimmable
= -1;
3543 // The number of bytes that the string would occupy if encoded in UTF-8.
3544 uint32_t bytesLength
= 0;
3546 // The length of the extension in bytes.
3547 uint32_t extensionBytesLength
= 0;
3549 // This algorithm iterates over each character in the string and appends it
3550 // or a replacement character if needed to outFileName.
3551 nsAutoString outFileName
;
3552 while (startStr
< endStr
) {
3554 char32_t nextChar
= UTF16CharEnumerator::NextChar(&startStr
, endStr
, &err
);
3559 // nulls are already stripped out above.
3560 MOZ_ASSERT(nextChar
!= char16_t(0));
3562 auto unicodeCategory
= unicode::GetGeneralCategory(nextChar
);
3563 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_CONTROL
||
3564 unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR
||
3565 unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR
) {
3566 // Skip over any control characters and separators.
3570 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR
||
3571 nextChar
== u
'\ufeff') {
3572 // Trim out any whitespace characters at the beginning of the filename,
3573 // and only add whitespace in the middle of the filename if the last
3574 // character was not whitespace or if we are not collapsing whitespace.
3575 if (!outFileName
.IsEmpty() &&
3576 (!lastWasWhitespace
|| !collapseWhitespace
)) {
3577 // Allow the ideographic space if it is present, otherwise replace with
3579 if (nextChar
!= u
'\u3000') {
3582 lastWasWhitespace
= true;
3584 lastWasWhitespace
= true;
3588 lastWasWhitespace
= false;
3589 if (nextChar
== '.' || nextChar
== u
'\u180e') {
3590 // Don't add any periods or vowel separators at the beginning of the
3591 // string. Note also that lastNonTrimmable is not adjusted in this
3592 // case, because periods and vowel separators are included in the
3593 // set of characters to trim at the end of the filename.
3594 if (outFileName
.IsEmpty()) {
3598 if (unicodeCategory
== HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3599 // Replace formatting characters with an underscore.
3603 // Don't truncate surrogate pairs in the middle.
3605 int32_t(outFileName
.Length()) +
3606 (NS_IS_HIGH_SURROGATE(H_SURROGATE(nextChar
)) ? 2 : 1);
3611 // UTF16CharEnumerator already converts surrogate pairs, so we can use
3612 // a simple computation of byte length here.
3613 uint32_t charBytesLength
= nextChar
< 0x80 ? 1
3614 : nextChar
< 0x800 ? 2
3615 : nextChar
< 0x10000 ? 3
3617 bytesLength
+= charBytesLength
;
3618 if (bytesLength
> maxBytes
) {
3619 if (longFileNameEnd
== -1) {
3620 longFileNameEnd
= int32_t(outFileName
.Length());
3624 // If we encounter a period, it could be the start of an extension, so
3625 // start counting the number of bytes in the extension. If another period
3626 // is found, start again since we want to use the last extension found.
3627 if (nextChar
== u
'.') {
3628 extensionBytesLength
= 1; // 1 byte for the period.
3629 } else if (extensionBytesLength
) {
3630 extensionBytesLength
+= charBytesLength
;
3634 AppendUCS4ToUTF16(nextChar
, outFileName
);
3637 // If the filename is longer than the maximum allowed filename size,
3638 // truncate it, but preserve the desired extension that is currently
3640 if (bytesLength
> maxBytes
&& !outFileName
.IsEmpty()) {
3641 // Get the sanitized extension from the filename without the dot.
3642 nsAutoString extension
;
3643 int32_t dotidx
= outFileName
.RFind(u
".");
3645 extension
= Substring(outFileName
, dotidx
+ 1);
3648 // There are two ways in which the filename should be truncated:
3649 // - If the filename was too long, truncate the name at the length
3651 // This position is indicated by longFileNameEnd.
3652 // - lastNonTrimmable will indicate the last character that was not
3653 // whitespace, a period, or a vowel separator at the end of the
3654 // the string, so the string should be truncated there as well.
3655 // If both apply, use the earliest position.
3656 if (lastNonTrimmable
>= 0) {
3657 // Subtract off the amount for the extension and the period.
3658 // Note that the extension length is in bytes but longFileNameEnd is in
3659 // characters, but if they don't match, it just means we crop off
3660 // more than is necessary. This is OK since it is better than cropping
3662 longFileNameEnd
-= extensionBytesLength
;
3663 if (longFileNameEnd
<= 0) {
3664 // This is extremely unlikely, but if the extension is larger than the
3665 // maximum size, just get rid of it. In this case, the extension
3666 // wouldn't have been an ordinary one we would want to preserve (such
3667 // as .html or .png) so just truncate off the file wherever the first
3669 int32_t dotidx
= outFileName
.Find(u
".");
3670 outFileName
.Truncate(dotidx
> 0 ? dotidx
: 1);
3672 outFileName
.Truncate(std::min(longFileNameEnd
, lastNonTrimmable
));
3674 // Now that the filename has been truncated, re-append the extension
3676 if (!extension
.IsEmpty()) {
3677 if (outFileName
.Last() != '.') {
3678 outFileName
.AppendLiteral(".");
3681 outFileName
.Append(extension
);
3685 } else if (lastNonTrimmable
>= 0) {
3686 // Otherwise, the filename wasn't too long, so just trim off the
3687 // extra whitespace and periods at the end.
3688 outFileName
.Truncate(lastNonTrimmable
);
3691 aFileName
= outFileName
;
3694 nsExternalHelperAppService::ModifyExtensionType
3695 nsExternalHelperAppService::ShouldModifyExtension(nsIMIMEInfo
* aMimeInfo
,
3697 const nsCString
& aFileExt
) {
3698 nsAutoCString MIMEType
;
3699 if (!aMimeInfo
|| NS_FAILED(aMimeInfo
->GetMIMEType(MIMEType
))) {
3700 return ModifyExtension_Append
;
3703 // Determine whether the extensions should be appended or replaced depending
3704 // on the content type.
3705 bool canForce
= StringBeginsWith(MIMEType
, "image/"_ns
) ||
3706 StringBeginsWith(MIMEType
, "audio/"_ns
) ||
3707 StringBeginsWith(MIMEType
, "video/"_ns
) || aFileExt
.IsEmpty();
3710 for (const char* mime
: forcedExtensionMimetypes
) {
3711 if (MIMEType
.Equals(mime
)) {
3712 if (!StaticPrefs::browser_download_sanitize_non_media_extensions()) {
3713 return ModifyExtension_Ignore
;
3721 return aForceAppend
? ModifyExtension_Append
: ModifyExtension_Ignore
;
3725 // If we get here, we know for sure the mimetype allows us to modify the
3726 // existing extension, if it's wrong. Return whether we should replace it
3728 bool knownExtension
= false;
3729 // Note that aFileExt is either empty or consists of an extension
3730 // excluding the dot.
3731 if (aFileExt
.IsEmpty() ||
3732 (NS_SUCCEEDED(aMimeInfo
->ExtensionExists(aFileExt
, &knownExtension
)) &&
3734 return ModifyExtension_Replace
;
3737 return ModifyExtension_Append
;