Bug 1728955: part 8) Refactor `DisplayErrCode` in Windows' `nsClipboard`. r=masayuki
[gecko.git] / uriloader / exthandler / nsExternalHelperAppService.cpp
blobd2c088abc9a2387c8dbe1706cd3fc7f4bc402184
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim:expandtab:shiftwidth=2:tabstop=2:cin:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "base/basictypes.h"
9 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Base64.h"
12 #include "mozilla/ResultExtensions.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "mozilla/dom/BrowserChild.h"
16 #include "mozilla/dom/CanonicalBrowsingContext.h"
17 #include "mozilla/dom/WindowGlobalParent.h"
18 #include "mozilla/StaticPrefs_security.h"
19 #include "nsXULAppAPI.h"
21 #include "nsExternalHelperAppService.h"
22 #include "nsCExternalHandlerService.h"
23 #include "nsIURI.h"
24 #include "nsIURL.h"
25 #include "nsIFile.h"
26 #include "nsIFileURL.h"
27 #include "nsIChannel.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "nsICategoryManager.h"
30 #include "nsDependentSubstring.h"
31 #include "nsString.h"
32 #include "nsUnicharUtils.h"
33 #include "nsIStringEnumerator.h"
34 #include "nsMemory.h"
35 #include "nsIStreamListener.h"
36 #include "nsIMIMEService.h"
37 #include "nsILoadGroup.h"
38 #include "nsIWebProgressListener.h"
39 #include "nsITransfer.h"
40 #include "nsReadableUtils.h"
41 #include "nsIRequest.h"
42 #include "nsDirectoryServiceDefs.h"
43 #include "nsIInterfaceRequestor.h"
44 #include "nsThreadUtils.h"
45 #include "nsIMutableArray.h"
46 #include "nsIRedirectHistoryEntry.h"
47 #include "nsOSHelperAppService.h"
48 #include "nsOSHelperAppServiceChild.h"
49 #include "nsContentSecurityUtils.h"
51 // used to access our datastore of user-configured helper applications
52 #include "nsIHandlerService.h"
53 #include "nsIMIMEInfo.h"
54 #include "nsIHelperAppLauncherDialog.h"
55 #include "nsIContentDispatchChooser.h"
56 #include "nsNetUtil.h"
57 #include "nsIPrivateBrowsingChannel.h"
58 #include "nsIIOService.h"
59 #include "nsNetCID.h"
61 #include "nsIApplicationReputation.h"
63 #include "nsDSURIContentListener.h"
64 #include "nsMimeTypes.h"
65 // used for header disposition information.
66 #include "nsIHttpChannel.h"
67 #include "nsIHttpChannelInternal.h"
68 #include "nsIEncodedChannel.h"
69 #include "nsIMultiPartChannel.h"
70 #include "nsIFileChannel.h"
71 #include "nsIObserverService.h" // so we can be a profile change observer
72 #include "nsIPropertyBag2.h" // for the 64-bit content length
74 #ifdef XP_MACOSX
75 # include "nsILocalFileMac.h"
76 #endif
78 #include "nsPluginHost.h"
79 #include "nsEscape.h"
81 #include "nsIStringBundle.h" // XXX needed to localize error msgs
82 #include "nsIPrompt.h"
84 #include "nsITextToSubURI.h" // to unescape the filename
86 #include "nsDocShellCID.h"
88 #include "nsCRT.h"
89 #include "nsLocalHandlerApp.h"
91 #include "nsIRandomGenerator.h"
93 #include "ContentChild.h"
94 #include "nsXULAppAPI.h"
95 #include "nsPIDOMWindow.h"
96 #include "ExternalHelperAppChild.h"
98 #ifdef XP_WIN
99 # include "nsWindowsHelpers.h"
100 #endif
102 #include "mozilla/Components.h"
103 #include "mozilla/ClearOnShutdown.h"
104 #include "mozilla/Preferences.h"
105 #include "mozilla/ipc/URIUtils.h"
107 using namespace mozilla;
108 using namespace mozilla::ipc;
109 using namespace mozilla::dom;
111 // Download Folder location constants
112 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
113 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
114 enum {
115 NS_FOLDER_VALUE_DESKTOP = 0,
116 NS_FOLDER_VALUE_DOWNLOADS = 1,
117 NS_FOLDER_VALUE_CUSTOM = 2
120 LazyLogModule nsExternalHelperAppService::mLog("HelperAppService");
122 // Using level 3 here because the OSHelperAppServices use a log level
123 // of LogLevel::Debug (4), and we want less detailed output here
124 // Using 3 instead of LogLevel::Warning because we don't output warnings
125 #undef LOG
126 #define LOG(args) \
127 MOZ_LOG(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info, args)
128 #define LOG_ENABLED() \
129 MOZ_LOG_TEST(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info)
131 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
132 "browser.helperApps.neverAsk.saveToDisk";
133 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
134 "browser.helperApps.neverAsk.openFile";
136 // Helper functions for Content-Disposition headers
139 * Given a URI fragment, unescape it
140 * @param aFragment The string to unescape
141 * @param aURI The URI from which this fragment is taken. Only its character set
142 * will be used.
143 * @param aResult [out] Unescaped string.
145 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
146 nsAString& aResult) {
147 // We need the unescaper
148 nsresult rv;
149 nsCOMPtr<nsITextToSubURI> textToSubURI =
150 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
151 NS_ENSURE_SUCCESS(rv, rv);
153 return textToSubURI->UnEscapeURIForUI(aFragment, aResult);
157 * UTF-8 version of UnescapeFragment.
158 * @param aFragment The string to unescape
159 * @param aURI The URI from which this fragment is taken. Only its character set
160 * will be used.
161 * @param aResult [out] Unescaped string, UTF-8 encoded.
162 * @note It is safe to pass the same string for aFragment and aResult.
163 * @note When this function fails, aResult will not be modified.
165 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
166 nsACString& aResult) {
167 nsAutoString result;
168 nsresult rv = UnescapeFragment(aFragment, aURI, result);
169 if (NS_SUCCEEDED(rv)) CopyUTF16toUTF8(result, aResult);
170 return rv;
174 * Given a channel, returns the filename and extension the channel has.
175 * This uses the URL and other sources (nsIMultiPartChannel).
176 * Also gives back whether the channel requested external handling (i.e.
177 * whether Content-Disposition: attachment was sent)
178 * @param aChannel The channel to extract the filename/extension from
179 * @param aFileName [out] Reference to the string where the filename should be
180 * stored. Empty if it could not be retrieved.
181 * WARNING - this filename may contain characters which the OS does not
182 * allow as part of filenames!
183 * @param aExtension [out] Reference to the string where the extension should
184 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
185 * @param aAllowURLExtension (optional) Get the extension from the URL if no
186 * Content-Disposition header is present. Default is true.
187 * @retval true The server sent Content-Disposition:attachment or equivalent
188 * @retval false Content-Disposition: inline or no content-disposition header
189 * was sent.
191 static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
192 nsString& aFileName,
193 nsCString& aExtension,
194 bool aAllowURLExtension = true) {
195 aExtension.Truncate();
197 * If the channel is an http or part of a multipart channel and we
198 * have a content disposition header set, then use the file name
199 * suggested there as the preferred file name to SUGGEST to the
200 * user. we shouldn't actually use that without their
201 * permission... otherwise just use our temp file
203 bool handleExternally = false;
204 uint32_t disp;
205 nsresult rv = aChannel->GetContentDisposition(&disp);
206 bool gotFileNameFromURI = false;
207 if (NS_SUCCEEDED(rv)) {
208 aChannel->GetContentDispositionFilename(aFileName);
209 if (disp == nsIChannel::DISPOSITION_ATTACHMENT) handleExternally = true;
212 // If the disposition header didn't work, try the filename from nsIURL
213 nsCOMPtr<nsIURI> uri;
214 aChannel->GetURI(getter_AddRefs(uri));
215 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
216 if (url && aFileName.IsEmpty()) {
217 if (aAllowURLExtension) {
218 url->GetFileExtension(aExtension);
219 UnescapeFragment(aExtension, url, aExtension);
221 // Windows ignores terminating dots. So we have to as well, so
222 // that our security checks do "the right thing"
223 // In case the aExtension consisted only of the dot, the code below will
224 // extract an aExtension from the filename
225 aExtension.Trim(".", false);
228 // try to extract the file name from the url and use that as a first pass as
229 // the leaf name of our temp file...
230 nsAutoCString leafName;
231 url->GetFileName(leafName);
232 if (!leafName.IsEmpty()) {
233 gotFileNameFromURI = true;
234 rv = UnescapeFragment(leafName, url, aFileName);
235 if (NS_FAILED(rv)) {
236 CopyUTF8toUTF16(leafName, aFileName); // use escaped name
241 // If we have a filename and no extension, remove trailing dots from the
242 // filename and extract the extension if that is possible.
243 if (aExtension.IsEmpty() && !aFileName.IsEmpty()) {
244 // Windows ignores terminating dots. So we have to as well, so
245 // that our security checks do "the right thing"
246 aFileName.Trim(".", false);
247 // We can get an extension if the filename is from a header, or if getting
248 // it from the URL was allowed.
249 bool canGetExtensionFromFilename =
250 !gotFileNameFromURI || aAllowURLExtension;
251 // ... , or if the mimetype is meaningless and we have nothing to go on:
252 if (!canGetExtensionFromFilename) {
253 nsAutoCString contentType;
254 if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
255 canGetExtensionFromFilename =
256 contentType.EqualsIgnoreCase(APPLICATION_OCTET_STREAM) ||
257 contentType.EqualsIgnoreCase("binary/octet-stream") ||
258 contentType.EqualsIgnoreCase("application/x-msdownload");
262 if (canGetExtensionFromFilename) {
263 // XXX RFindCharInReadable!!
264 nsAutoString fileNameStr(aFileName);
265 int32_t idx = fileNameStr.RFindChar(char16_t('.'));
266 if (idx != kNotFound)
267 CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1),
268 aExtension);
272 return handleExternally;
276 * Obtains the directory to use. This tends to vary per platform, and
277 * needs to be consistent throughout our codepaths. For platforms where
278 * helper apps use the downloads directory, this should be kept in
279 * sync with DownloadIntegration.jsm.
281 * Optionally skip availability of the directory and storage.
283 static nsresult GetDownloadDirectory(nsIFile** _directory,
284 bool aSkipChecks = false) {
285 #if defined(ANDROID)
286 return NS_ERROR_FAILURE;
287 #endif
289 bool usePrefDir =
290 StaticPrefs::browser_download_improvements_to_download_panel();
291 #ifdef XP_MACOSX
292 usePrefDir = true;
293 #endif
295 nsCOMPtr<nsIFile> dir;
296 nsresult rv;
297 if (usePrefDir) {
298 // Try to get the users download location, if it's set.
299 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
300 case NS_FOLDER_VALUE_DESKTOP:
301 (void)NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
302 break;
303 case NS_FOLDER_VALUE_CUSTOM: {
304 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, NS_GET_IID(nsIFile),
305 getter_AddRefs(dir));
306 if (!dir) break;
308 // If we're not checking for availability we're done.
309 if (aSkipChecks) {
310 dir.forget(_directory);
311 return NS_OK;
314 // We have the directory, and now we need to make sure it exists
315 bool dirExists = false;
316 (void)dir->Exists(&dirExists);
317 if (dirExists) break;
319 nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
320 if (NS_FAILED(rv)) {
321 dir = nullptr;
322 break;
324 } break;
325 case NS_FOLDER_VALUE_DOWNLOADS:
326 // This is just the OS default location, so fall out
327 break;
329 if (!dir) {
330 rv = NS_GetSpecialDirectory(NS_OS_DEFAULT_DOWNLOAD_DIR,
331 getter_AddRefs(dir));
332 NS_ENSURE_SUCCESS(rv, rv);
334 } else {
335 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
336 NS_ENSURE_SUCCESS(rv, rv);
338 #if !defined(XP_MACOSX) && defined(XP_UNIX)
339 // Ensuring that only the current user can read the file names we end up
340 // creating. Note that creating directories with a specified permission is
341 // only supported on Unix platform right now. That's why the above check
342 // exists.
344 uint32_t permissions;
345 rv = dir->GetPermissions(&permissions);
346 NS_ENSURE_SUCCESS(rv, rv);
348 if (permissions != PR_IRWXU) {
349 const char* userName = PR_GetEnv("USERNAME");
350 if (!userName || !*userName) {
351 userName = PR_GetEnv("USER");
353 if (!userName || !*userName) {
354 userName = PR_GetEnv("LOGNAME");
356 if (!userName || !*userName) {
357 userName = "mozillaUser";
360 nsAutoString userDir;
361 userDir.AssignLiteral("mozilla_");
362 userDir.AppendASCII(userName);
363 userDir.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
365 int counter = 0;
366 bool pathExists;
367 nsCOMPtr<nsIFile> finalPath;
369 while (true) {
370 nsAutoString countedUserDir(userDir);
371 countedUserDir.AppendInt(counter, 10);
372 dir->Clone(getter_AddRefs(finalPath));
373 finalPath->Append(countedUserDir);
375 rv = finalPath->Exists(&pathExists);
376 NS_ENSURE_SUCCESS(rv, rv);
378 if (pathExists) {
379 // If this path has the right permissions, use it.
380 rv = finalPath->GetPermissions(&permissions);
381 NS_ENSURE_SUCCESS(rv, rv);
383 // Ensuring the path is writable by the current user.
384 bool isWritable;
385 rv = finalPath->IsWritable(&isWritable);
386 NS_ENSURE_SUCCESS(rv, rv);
388 if (permissions == PR_IRWXU && isWritable) {
389 dir = finalPath;
390 break;
394 rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
395 if (NS_SUCCEEDED(rv)) {
396 dir = finalPath;
397 break;
399 if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
400 // Unexpected error.
401 return rv;
403 counter++;
407 #endif
410 NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
411 dir.forget(_directory);
412 return NS_OK;
416 * Structure for storing extension->type mappings.
417 * @see defaultMimeEntries
419 struct nsDefaultMimeTypeEntry {
420 const char* mMimeType;
421 const char* mFileExtension;
425 * Default extension->mimetype mappings. These are not overridable.
426 * If you add types here, make sure they are lowercase, or you'll regret it.
428 static const nsDefaultMimeTypeEntry defaultMimeEntries[] = {
429 // The following are those extensions that we're asked about during startup,
430 // sorted by order used
431 {IMAGE_GIF, "gif"},
432 {TEXT_XML, "xml"},
433 {APPLICATION_RDF, "rdf"},
434 {IMAGE_PNG, "png"},
435 // -- end extensions used during startup
436 {TEXT_CSS, "css"},
437 {IMAGE_JPEG, "jpeg"},
438 {IMAGE_JPEG, "jpg"},
439 {IMAGE_SVG_XML, "svg"},
440 {TEXT_HTML, "html"},
441 {TEXT_HTML, "htm"},
442 {APPLICATION_XPINSTALL, "xpi"},
443 {"application/xhtml+xml", "xhtml"},
444 {"application/xhtml+xml", "xht"},
445 {TEXT_PLAIN, "txt"},
446 {APPLICATION_JSON, "json"},
447 {APPLICATION_XJAVASCRIPT, "js"},
448 {APPLICATION_XJAVASCRIPT, "jsm"},
449 {VIDEO_OGG, "ogv"},
450 {VIDEO_OGG, "ogg"},
451 {APPLICATION_OGG, "ogg"},
452 {AUDIO_OGG, "oga"},
453 {AUDIO_OGG, "opus"},
454 {APPLICATION_PDF, "pdf"},
455 {VIDEO_WEBM, "webm"},
456 {AUDIO_WEBM, "webm"},
457 {IMAGE_ICO, "ico"},
458 {TEXT_PLAIN, "properties"},
459 {TEXT_PLAIN, "locale"},
460 {TEXT_PLAIN, "ftl"},
461 #if defined(MOZ_WMF)
462 {VIDEO_MP4, "mp4"},
463 {AUDIO_MP4, "m4a"},
464 {AUDIO_MP3, "mp3"},
465 #endif
466 #ifdef MOZ_RAW
467 {VIDEO_RAW, "yuv"}
468 #endif
472 * This is a small private struct used to help us initialize some
473 * default mime types.
475 struct nsExtraMimeTypeEntry {
476 const char* mMimeType;
477 const char* mFileExtensions;
478 const char* mDescription;
482 * This table lists all of the 'extra' content types that we can deduce from
483 * particular file extensions. These entries also ensure that we provide a good
484 * descriptive name when we encounter files with these content types and/or
485 * extensions. These can be overridden by user helper app prefs. If you add
486 * types here, make sure they are lowercase, or you'll regret it.
488 static const nsExtraMimeTypeEntry extraMimeEntries[] = {
489 #if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to
490 // look that up...
491 {APPLICATION_OCTET_STREAM, "exe,com", "Binary File"},
492 #else
493 {APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File"},
494 #endif
495 {APPLICATION_GZIP2, "gz", "gzip"},
496 {"application/x-arj", "arj", "ARJ file"},
497 {"application/rtf", "rtf", "Rich Text Format File"},
498 {APPLICATION_ZIP, "zip", "ZIP Archive"},
499 {APPLICATION_XPINSTALL, "xpi", "XPInstall Install"},
500 {APPLICATION_PDF, "pdf", "Portable Document Format"},
501 {APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File"},
502 {APPLICATION_XJAVASCRIPT, "js", "Javascript Source File"},
503 {APPLICATION_XJAVASCRIPT, "jsm,mjs", "Javascript Module Source File"},
504 #ifdef MOZ_WIDGET_ANDROID
505 {"application/vnd.android.package-archive", "apk", "Android Package"},
506 #endif
508 // OpenDocument formats
509 {"application/vnd.oasis.opendocument.text", "odt", "OpenDocument Text"},
510 {"application/vnd.oasis.opendocument.presentation", "odp",
511 "OpenDocument Presentation"},
512 {"application/vnd.oasis.opendocument.spreadsheet", "ods",
513 "OpenDocument Spreadsheet"},
514 {"application/vnd.oasis.opendocument.graphics", "odg",
515 "OpenDocument Graphics"},
517 // Legacy Microsoft Office
518 {"application/msword", "doc", "Microsoft Word"},
519 {"application/vnd.ms-powerpoint", "ppt", "Microsoft PowerPoint"},
520 {"application/vnd.ms-excel", "xls", "Microsoft Excel"},
522 // Office Open XML
523 {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
524 "docx", "Microsoft Word (Open XML)"},
525 {"application/"
526 "vnd.openxmlformats-officedocument.presentationml.presentation",
527 "pptx", "Microsoft PowerPoint (Open XML)"},
528 {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
529 "xlsx", "Microsoft Excel (Open XML)"},
531 // Note: if you add new image types, please also update the list in
532 // contentAreaUtils.js to match.
533 {IMAGE_ART, "art", "ART Image"},
534 {IMAGE_BMP, "bmp", "BMP Image"},
535 {IMAGE_GIF, "gif", "GIF Image"},
536 {IMAGE_ICO, "ico,cur", "ICO Image"},
537 {IMAGE_JPEG, "jpg,jpeg,jfif,pjpeg,pjp", "JPEG Image"},
538 {IMAGE_PNG, "png", "PNG Image"},
539 {IMAGE_APNG, "apng", "APNG Image"},
540 {IMAGE_TIFF, "tiff,tif", "TIFF Image"},
541 {IMAGE_XBM, "xbm", "XBM Image"},
542 {IMAGE_SVG_XML, "svg", "Scalable Vector Graphics"},
543 {IMAGE_WEBP, "webp", "WebP Image"},
544 {IMAGE_AVIF, "avif", "AV1 Image File"},
545 {IMAGE_JXL, "jxl", "JPEG XL Image File"},
547 {MESSAGE_RFC822, "eml", "RFC-822 data"},
548 {TEXT_PLAIN, "txt,text", "Text File"},
549 {APPLICATION_JSON, "json", "JavaScript Object Notation"},
550 {TEXT_VTT, "vtt", "Web Video Text Tracks"},
551 {TEXT_CACHE_MANIFEST, "appcache", "Application Cache Manifest"},
552 {TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language"},
553 {"application/xhtml+xml", "xhtml,xht",
554 "Extensible HyperText Markup Language"},
555 {APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language"},
556 {APPLICATION_RDF, "rdf", "Resource Description Framework"},
557 {"text/csv", "csv", "CSV File"},
558 {TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language"},
559 {TEXT_CSS, "css", "Style Sheet"},
560 {TEXT_VCARD, "vcf,vcard", "Contact Information"},
561 {TEXT_CALENDAR, "ics,ical,ifb,icalendar", "iCalendar"},
562 {VIDEO_OGG, "ogv", "Ogg Video"},
563 {VIDEO_OGG, "ogg", "Ogg Video"},
564 {APPLICATION_OGG, "ogg", "Ogg Video"},
565 {AUDIO_OGG, "oga", "Ogg Audio"},
566 {AUDIO_OGG, "opus", "Opus Audio"},
567 {VIDEO_WEBM, "webm", "Web Media Video"},
568 {AUDIO_WEBM, "webm", "Web Media Audio"},
569 {AUDIO_MP3, "mp3", "MPEG Audio"},
570 {VIDEO_MP4, "mp4", "MPEG-4 Video"},
571 {AUDIO_MP4, "m4a", "MPEG-4 Audio"},
572 {VIDEO_RAW, "yuv", "Raw YUV Video"},
573 {AUDIO_WAV, "wav", "Waveform Audio"},
574 {VIDEO_3GPP, "3gpp,3gp", "3GPP Video"},
575 {VIDEO_3GPP2, "3g2", "3GPP2 Video"},
576 {AUDIO_AAC, "aac", "AAC Audio"},
577 {AUDIO_FLAC, "flac", "FLAC Audio"},
578 {AUDIO_MIDI, "mid", "Standard MIDI Audio"},
579 {APPLICATION_WASM, "wasm", "WebAssembly Module"}};
581 static const nsDefaultMimeTypeEntry sForbiddenPrimaryExtensions[] = {
582 {IMAGE_JPEG, "jfif"}};
585 * File extensions for which decoding should be disabled.
586 * NOTE: These MUST be lower-case and ASCII.
588 static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
589 {APPLICATION_GZIP, "gz"},
590 {APPLICATION_GZIP, "tgz"},
591 {APPLICATION_ZIP, "zip"},
592 {APPLICATION_COMPRESS, "z"},
593 {APPLICATION_GZIP, "svgz"}};
596 * Mimetypes for which we enforce using a known extension.
598 * In addition to this list, we do this for all audio/, video/ and
599 * image/ mimetypes.
601 static const char* forcedExtensionMimetypes[] = {
602 // Note: zip and json mimetypes are commonly used with a variety of
603 // extensions; don't add them here. It's a similar story for text/xml,
604 // but slightly worse because we can use it when sniffing for a mimetype
605 // if one hasn't been provided, so don't re-add that here either.
606 APPLICATION_PDF, APPLICATION_OGG, APPLICATION_WASM,
607 TEXT_CALENDAR, TEXT_CSS, TEXT_VCARD};
610 * Primary extensions of types whose descriptions should be overwritten.
611 * This extension is concatenated with "ExtHandlerDescription" to look up the
612 * description in unknownContentType.properties.
613 * NOTE: These MUST be lower-case and ASCII.
615 static const char* descriptionOverwriteExtensions[] = {
616 "avif", "jxl", "pdf", "svg", "webp", "xml",
619 static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
622 * On Mac child processes, return an nsOSHelperAppServiceChild for remoting
623 * OS calls to the parent process. On all other platforms use
624 * nsOSHelperAppService.
626 /* static */
627 already_AddRefed<nsExternalHelperAppService>
628 nsExternalHelperAppService::GetSingleton() {
629 if (!sExtHelperAppSvcSingleton) {
630 #if defined(XP_MACOSX) || defined(XP_WIN)
631 if (XRE_IsParentProcess()) {
632 sExtHelperAppSvcSingleton = new nsOSHelperAppService();
633 } else {
634 sExtHelperAppSvcSingleton = new nsOSHelperAppServiceChild();
636 #else
637 sExtHelperAppSvcSingleton = new nsOSHelperAppService();
638 #endif // defined(XP_MACOSX) || defined(XP_WIN)
639 ClearOnShutdown(&sExtHelperAppSvcSingleton);
642 return do_AddRef(sExtHelperAppSvcSingleton);
645 NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
646 nsPIExternalAppLauncher, nsIExternalProtocolService,
647 nsIMIMEService, nsIObserver, nsISupportsWeakReference)
649 nsExternalHelperAppService::nsExternalHelperAppService() {}
650 nsresult nsExternalHelperAppService::Init() {
651 // Add an observer for profile change
652 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
653 if (!obs) return NS_ERROR_FAILURE;
655 nsresult rv = obs->AddObserver(this, "profile-before-change", true);
656 NS_ENSURE_SUCCESS(rv, rv);
657 return obs->AddObserver(this, "last-pb-context-exited", true);
660 nsExternalHelperAppService::~nsExternalHelperAppService() {}
662 nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
663 const nsACString& aMimeContentType, nsIRequest* aRequest,
664 BrowsingContext* aContentContext, bool aForceSave,
665 nsIInterfaceRequestor* aWindowContext,
666 nsIStreamListener** aStreamListener) {
667 // We need to get a hold of a ContentChild so that we can begin forwarding
668 // this data to the parent. In the HTTP case, this is unfortunate, since
669 // we're actually passing data from parent->child->parent wastefully, but
670 // the Right Fix will eventually be to short-circuit those channels on the
671 // parent side based on some sort of subscription concept.
672 using mozilla::dom::ContentChild;
673 using mozilla::dom::ExternalHelperAppChild;
674 ContentChild* child = ContentChild::GetSingleton();
675 if (!child) {
676 return NS_ERROR_FAILURE;
679 nsCString disp;
680 nsCOMPtr<nsIURI> uri;
681 int64_t contentLength = -1;
682 bool wasFileChannel = false;
683 uint32_t contentDisposition = -1;
684 nsAutoString fileName;
685 nsCOMPtr<nsILoadInfo> loadInfo;
687 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
688 if (channel) {
689 channel->GetURI(getter_AddRefs(uri));
690 channel->GetContentLength(&contentLength);
691 channel->GetContentDisposition(&contentDisposition);
692 channel->GetContentDispositionFilename(fileName);
693 channel->GetContentDispositionHeader(disp);
694 loadInfo = channel->LoadInfo();
696 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aRequest));
697 wasFileChannel = fileChan != nullptr;
700 nsCOMPtr<nsIURI> referrer;
701 NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
703 Maybe<mozilla::net::LoadInfoArgs> loadInfoArgs;
704 MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
706 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aRequest));
707 // Determine whether a new window was opened specifically for this request
708 bool shouldCloseWindow = false;
709 if (props) {
710 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns,
711 &shouldCloseWindow);
714 // Now we build a protocol for forwarding our data to the parent. The
715 // protocol will act as a listener on the child-side and create a "real"
716 // helperAppService listener on the parent-side, via another call to
717 // DoContent.
718 RefPtr<ExternalHelperAppChild> childListener = new ExternalHelperAppChild();
719 MOZ_ALWAYS_TRUE(child->SendPExternalHelperAppConstructor(
720 childListener, uri, loadInfoArgs, nsCString(aMimeContentType), disp,
721 contentDisposition, fileName, aForceSave, contentLength, wasFileChannel,
722 referrer, aContentContext, shouldCloseWindow));
724 NS_ADDREF(*aStreamListener = childListener);
726 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
728 RefPtr<nsExternalAppHandler> handler =
729 new nsExternalAppHandler(nullptr, ""_ns, aContentContext, aWindowContext,
730 this, fileName, reason, aForceSave);
731 if (!handler) {
732 return NS_ERROR_OUT_OF_MEMORY;
735 childListener->SetHandler(handler);
736 return NS_OK;
739 NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
740 const nsACString& aMimeContentType, nsIRequest* aRequest,
741 BrowsingContext* aContentContext, bool aForceSave,
742 nsIInterfaceRequestor* aWindowContext,
743 nsIStreamListener** aStreamListener) {
744 MOZ_ASSERT(!XRE_IsContentProcess());
746 nsAutoString fileName;
747 nsAutoCString fileExtension;
748 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
749 uint32_t contentDisposition = -1;
751 // Get the file extension and name that we will need later
752 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
753 nsCOMPtr<nsIURI> uri;
754 int64_t contentLength = -1;
755 if (channel) {
756 channel->GetURI(getter_AddRefs(uri));
757 channel->GetContentLength(&contentLength);
758 channel->GetContentDisposition(&contentDisposition);
759 channel->GetContentDispositionFilename(fileName);
761 // Check if we have a POST request, in which case we don't want to use
762 // the url's extension
763 bool allowURLExt = !net::ChannelIsPost(channel);
765 // Check if we had a query string - we don't want to check the URL
766 // extension if a query is present in the URI
767 // If we already know we don't want to check the URL extension, don't
768 // bother checking the query
769 if (uri && allowURLExt) {
770 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
772 if (url) {
773 nsAutoCString query;
775 // We only care about the query for HTTP and HTTPS URLs
776 if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
777 url->GetQuery(query);
780 // Only get the extension if the query is empty; if it isn't, then the
781 // extension likely belongs to a cgi script and isn't helpful
782 allowURLExt = query.IsEmpty();
785 // Extract name & extension
786 bool isAttachment = GetFilenameAndExtensionFromChannel(
787 channel, fileName, fileExtension, allowURLExt);
788 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
789 fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
790 isAttachment));
791 if (isAttachment) {
792 reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
796 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
797 PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
799 // We get the mime service here even though we're the default implementation
800 // of it, so it's possible to override only the mime service and not need to
801 // reimplement the whole external helper app service itself.
802 nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
803 NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
805 // Try to find a mime object by looking at the mime type/extension
806 nsCOMPtr<nsIMIMEInfo> mimeInfo;
807 if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
808 nsCaseInsensitiveCStringComparator)) {
809 nsAutoCString mimeType;
810 if (!fileExtension.IsEmpty()) {
811 mimeSvc->GetFromTypeAndExtension(""_ns, fileExtension,
812 getter_AddRefs(mimeInfo));
813 if (mimeInfo) {
814 mimeInfo->GetMIMEType(mimeType);
816 LOG(("OS-Provided mime type '%s' for extension '%s'\n", mimeType.get(),
817 fileExtension.get()));
821 if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
822 // Extension lookup gave us no useful match
823 mimeSvc->GetFromTypeAndExtension(
824 nsLiteralCString(APPLICATION_OCTET_STREAM), fileExtension,
825 getter_AddRefs(mimeInfo));
826 mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
829 if (channel) {
830 channel->SetContentType(mimeType);
833 // Don't overwrite SERVERREQUEST
834 if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
835 reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
837 } else {
838 mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
839 getter_AddRefs(mimeInfo));
841 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
843 // No mimeinfo -> we can't continue. probably OOM.
844 if (!mimeInfo) {
845 return NS_ERROR_OUT_OF_MEMORY;
848 *aStreamListener = nullptr;
849 // We want the mimeInfo's primary extension to pass it to
850 // nsExternalAppHandler
851 nsAutoCString buf;
852 mimeInfo->GetPrimaryExtension(buf);
854 // NB: ExternalHelperAppParent depends on this listener always being an
855 // nsExternalAppHandler. If this changes, make sure to update that code.
856 nsExternalAppHandler* handler =
857 new nsExternalAppHandler(mimeInfo, buf, aContentContext, aWindowContext,
858 this, fileName, reason, aForceSave);
859 if (!handler) {
860 return NS_ERROR_OUT_OF_MEMORY;
863 NS_ADDREF(*aStreamListener = handler);
864 return NS_OK;
867 NS_IMETHODIMP nsExternalHelperAppService::DoContent(
868 const nsACString& aMimeContentType, nsIRequest* aRequest,
869 nsIInterfaceRequestor* aContentContext, bool aForceSave,
870 nsIInterfaceRequestor* aWindowContext,
871 nsIStreamListener** aStreamListener) {
872 // Scripted interface requestors cannot return an instance of the
873 // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
874 // get to the window via `nsIDOMWindow`. Unfortunately, at that point we
875 // don't know whether the thing we got is an inner or outer window, so have to
876 // work with either one.
877 RefPtr<BrowsingContext> bc;
878 nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(aContentContext);
879 if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_QueryInterface(domWindow)) {
880 bc = outerWindow->GetBrowsingContext();
881 } else if (nsCOMPtr<nsPIDOMWindowInner> innerWindow =
882 do_QueryInterface(domWindow)) {
883 bc = innerWindow->GetBrowsingContext();
886 if (XRE_IsContentProcess()) {
887 return DoContentContentProcessHelper(aMimeContentType, aRequest, bc,
888 aForceSave, aWindowContext,
889 aStreamListener);
892 nsresult rv = CreateListener(aMimeContentType, aRequest, bc, aForceSave,
893 aWindowContext, aStreamListener);
894 return rv;
897 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
898 const nsACString& aExtension, const nsACString& aEncodingType,
899 bool* aApplyDecoding) {
900 *aApplyDecoding = true;
901 uint32_t i;
902 for (i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
903 if (aExtension.LowerCaseEqualsASCII(
904 nonDecodableExtensions[i].mFileExtension) &&
905 aEncodingType.LowerCaseEqualsASCII(
906 nonDecodableExtensions[i].mMimeType)) {
907 *aApplyDecoding = false;
908 break;
911 return NS_OK;
914 nsresult nsExternalHelperAppService::GetFileTokenForPath(
915 const char16_t* aPlatformAppPath, nsIFile** aFile) {
916 nsDependentString platformAppPath(aPlatformAppPath);
917 // First, check if we have an absolute path
918 nsIFile* localFile = nullptr;
919 nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
920 if (NS_SUCCEEDED(rv)) {
921 *aFile = localFile;
922 bool exists;
923 if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
924 NS_RELEASE(*aFile);
925 return NS_ERROR_FILE_NOT_FOUND;
927 return NS_OK;
930 // Second, check if file exists in mozilla program directory
931 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
932 if (NS_SUCCEEDED(rv)) {
933 rv = (*aFile)->Append(platformAppPath);
934 if (NS_SUCCEEDED(rv)) {
935 bool exists = false;
936 rv = (*aFile)->Exists(&exists);
937 if (NS_SUCCEEDED(rv) && exists) return NS_OK;
939 NS_RELEASE(*aFile);
942 return NS_ERROR_NOT_AVAILABLE;
945 //////////////////////////////////////////////////////////////////////////////////////////////////////
946 // begin external protocol service default implementation...
947 //////////////////////////////////////////////////////////////////////////////////////////////////////
948 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(
949 const char* aProtocolScheme, bool* aHandlerExists) {
950 nsCOMPtr<nsIHandlerInfo> handlerInfo;
951 nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
952 getter_AddRefs(handlerInfo));
953 if (NS_SUCCEEDED(rv)) {
954 // See if we have any known possible handler apps for this
955 nsCOMPtr<nsIMutableArray> possibleHandlers;
956 handlerInfo->GetPossibleApplicationHandlers(
957 getter_AddRefs(possibleHandlers));
959 uint32_t length;
960 possibleHandlers->GetLength(&length);
961 if (length) {
962 *aHandlerExists = true;
963 return NS_OK;
967 // if not, fall back on an os-based handler
968 return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
971 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(
972 const char* aProtocolScheme, bool* aResult) {
973 // check the per protocol setting first. it always takes precedence.
974 // if not set, then use the global setting.
976 nsAutoCString prefName("network.protocol-handler.expose.");
977 prefName += aProtocolScheme;
978 bool val;
979 if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
980 *aResult = val;
981 return NS_OK;
984 // by default, no protocol is exposed. i.e., by default all link clicks must
985 // go through the external protocol service. most applications override this
986 // default behavior.
987 *aResult = Preferences::GetBool("network.protocol-handler.expose-all", false);
989 return NS_OK;
992 static const char kExternalProtocolPrefPrefix[] =
993 "network.protocol-handler.external.";
994 static const char kExternalProtocolDefaultPref[] =
995 "network.protocol-handler.external-default";
997 NS_IMETHODIMP
998 nsExternalHelperAppService::LoadURI(nsIURI* aURI,
999 nsIPrincipal* aTriggeringPrincipal,
1000 BrowsingContext* aBrowsingContext,
1001 bool aTriggeredExternally) {
1002 NS_ENSURE_ARG_POINTER(aURI);
1004 if (XRE_IsContentProcess()) {
1005 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
1006 aURI, aTriggeringPrincipal, aBrowsingContext, aTriggeredExternally);
1007 return NS_OK;
1010 nsAutoCString spec;
1011 aURI->GetSpec(spec);
1013 if (spec.Find("%00") != -1) return NS_ERROR_MALFORMED_URI;
1015 spec.ReplaceSubstring("\"", "%22");
1016 spec.ReplaceSubstring("`", "%60");
1018 nsCOMPtr<nsIIOService> ios(do_GetIOService());
1019 nsCOMPtr<nsIURI> uri;
1020 nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
1021 NS_ENSURE_SUCCESS(rv, rv);
1023 nsAutoCString scheme;
1024 uri->GetScheme(scheme);
1025 if (scheme.IsEmpty()) return NS_OK; // must have a scheme
1027 // Deny load if the prefs say to do so
1028 nsAutoCString externalPref(kExternalProtocolPrefPrefix);
1029 externalPref += scheme;
1030 bool allowLoad = false;
1031 if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
1032 // no scheme-specific value, check the default
1033 if (NS_FAILED(
1034 Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) {
1035 return NS_OK; // missing default pref
1039 if (!allowLoad) {
1040 return NS_OK; // explicitly denied
1043 // Now check if the principal is allowed to access the navigated context.
1044 // We allow navigating subframes, even if not same-origin - non-external
1045 // links can always navigate everywhere, so this is a minor additional
1046 // restriction, only aiming to prevent some types of spoofing attacks
1047 // from otherwise disjoint browsingcontext trees.
1048 if (aBrowsingContext && aTriggeringPrincipal &&
1049 !StaticPrefs::security_allow_disjointed_external_uri_loads() &&
1050 // Add-on principals are always allowed:
1051 !BasePrincipal::Cast(aTriggeringPrincipal)->AddonPolicy() &&
1052 // As is chrome code:
1053 !aTriggeringPrincipal->IsSystemPrincipal()) {
1054 RefPtr<BrowsingContext> bc = aBrowsingContext;
1055 WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal();
1056 bool foundAccessibleFrame = false;
1058 // Also allow this load if the target is a toplevel BC and contains a
1059 // non-web-controlled about:blank document
1060 if (bc->IsTop() && !bc->HadOriginalOpener() && wgp) {
1061 RefPtr<nsIURI> uri = wgp->GetDocumentURI();
1062 foundAccessibleFrame =
1063 uri && uri->GetSpecOrDefault().EqualsLiteral("about:blank");
1066 while (!foundAccessibleFrame) {
1067 if (wgp) {
1068 foundAccessibleFrame =
1069 aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
1071 // We have to get the parent via the bc, because there may not
1072 // be a window global for the innermost bc; see bug 1650162.
1073 BrowsingContext* parent = bc->GetParent();
1074 if (!parent) {
1075 break;
1077 bc = parent;
1078 wgp = parent->Canonical()->GetCurrentWindowGlobal();
1081 if (!foundAccessibleFrame) {
1082 // See if this navigation could have come from a subframe.
1083 nsTArray<RefPtr<BrowsingContext>> contexts;
1084 aBrowsingContext->GetAllBrowsingContextsInSubtree(contexts);
1085 for (const auto& kid : contexts) {
1086 wgp = kid->Canonical()->GetCurrentWindowGlobal();
1087 if (wgp && aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal())) {
1088 foundAccessibleFrame = true;
1089 break;
1094 if (!foundAccessibleFrame) {
1095 return NS_OK; // deny the load.
1099 nsCOMPtr<nsIHandlerInfo> handler;
1100 rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
1101 NS_ENSURE_SUCCESS(rv, rv);
1103 nsCOMPtr<nsIContentDispatchChooser> chooser =
1104 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
1105 NS_ENSURE_SUCCESS(rv, rv);
1107 return chooser->HandleURI(handler, uri, aTriggeringPrincipal,
1108 aBrowsingContext, aTriggeredExternally);
1111 //////////////////////////////////////////////////////////////////////////////////////////////////////
1112 // Methods related to deleting temporary files on exit
1113 //////////////////////////////////////////////////////////////////////////////////////////////////////
1115 /* static */
1116 nsresult nsExternalHelperAppService::DeleteTemporaryFileHelper(
1117 nsIFile* aTemporaryFile, nsCOMArray<nsIFile>& aFileList) {
1118 bool isFile = false;
1120 // as a safety measure, make sure the nsIFile is really a file and not a
1121 // directory object.
1122 aTemporaryFile->IsFile(&isFile);
1123 if (!isFile) return NS_OK;
1125 aFileList.AppendObject(aTemporaryFile);
1127 return NS_OK;
1130 NS_IMETHODIMP
1131 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) {
1132 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
1135 NS_IMETHODIMP
1136 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1137 nsIFile* aTemporaryFile) {
1138 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
1141 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1142 nsCOMArray<nsIFile>& fileList) {
1143 int32_t numEntries = fileList.Count();
1144 nsIFile* localFile;
1145 for (int32_t index = 0; index < numEntries; index++) {
1146 localFile = fileList[index];
1147 if (localFile) {
1148 // First make the file writable, since the temp file is probably readonly.
1149 localFile->SetPermissions(0600);
1150 localFile->Remove(false);
1154 fileList.Clear();
1157 void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1158 ExpungeTemporaryFilesHelper(mTemporaryFilesList);
1161 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1162 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
1165 static const char kExternalWarningPrefPrefix[] =
1166 "network.protocol-handler.warn-external.";
1167 static const char kExternalWarningDefaultPref[] =
1168 "network.protocol-handler.warn-external-default";
1170 NS_IMETHODIMP
1171 nsExternalHelperAppService::GetProtocolHandlerInfo(
1172 const nsACString& aScheme, nsIHandlerInfo** aHandlerInfo) {
1173 // XXX enterprise customers should be able to turn this support off with a
1174 // single master pref (maybe use one of the "exposed" prefs here?)
1176 bool exists;
1177 nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
1178 if (NS_FAILED(rv)) {
1179 // Either it knows nothing, or we ran out of memory
1180 return NS_ERROR_FAILURE;
1183 nsCOMPtr<nsIHandlerService> handlerSvc =
1184 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1185 if (handlerSvc) {
1186 bool hasHandler = false;
1187 (void)handlerSvc->Exists(*aHandlerInfo, &hasHandler);
1188 if (hasHandler) {
1189 rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, ""_ns);
1190 if (NS_SUCCEEDED(rv)) return NS_OK;
1194 return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
1197 NS_IMETHODIMP
1198 nsExternalHelperAppService::SetProtocolHandlerDefaults(
1199 nsIHandlerInfo* aHandlerInfo, bool aOSHandlerExists) {
1200 // this type isn't in our database, so we've only got an OS default handler,
1201 // if one exists
1203 if (aOSHandlerExists) {
1204 // we've got a default, so use it
1205 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
1207 // whether or not to ask the user depends on the warning preference
1208 nsAutoCString scheme;
1209 aHandlerInfo->GetType(scheme);
1211 nsAutoCString warningPref(kExternalWarningPrefPrefix);
1212 warningPref += scheme;
1213 bool warn;
1214 if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
1215 // no scheme-specific value, check the default
1216 warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
1218 aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
1219 } else {
1220 // If no OS default existed, we set the preferred action to alwaysAsk.
1221 // This really means not initialized (i.e. there's no available handler)
1222 // to all the code...
1223 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
1226 return NS_OK;
1229 // XPCOM profile change observer
1230 NS_IMETHODIMP
1231 nsExternalHelperAppService::Observe(nsISupports* aSubject, const char* aTopic,
1232 const char16_t* someData) {
1233 if (!strcmp(aTopic, "profile-before-change")) {
1234 ExpungeTemporaryFiles();
1235 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1236 ExpungeTemporaryPrivateFiles();
1238 return NS_OK;
1241 //////////////////////////////////////////////////////////////////////////////////////////////////////
1242 // begin external app handler implementation
1243 //////////////////////////////////////////////////////////////////////////////////////////////////////
1245 NS_IMPL_ADDREF(nsExternalAppHandler)
1246 NS_IMPL_RELEASE(nsExternalAppHandler)
1248 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
1249 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
1250 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
1251 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
1252 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
1253 NS_INTERFACE_MAP_ENTRY(nsICancelable)
1254 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
1255 NS_INTERFACE_MAP_ENTRY(nsINamed)
1256 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler)
1257 NS_INTERFACE_MAP_END
1259 nsExternalAppHandler::nsExternalAppHandler(
1260 nsIMIMEInfo* aMIMEInfo, const nsACString& aTempFileExtension,
1261 BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext,
1262 nsExternalHelperAppService* aExtProtSvc,
1263 const nsAString& aSuggestedFilename, uint32_t aReason, bool aForceSave)
1264 : mMimeInfo(aMIMEInfo),
1265 mBrowsingContext(aBrowsingContext),
1266 mWindowContext(aWindowContext),
1267 mSuggestedFileName(aSuggestedFilename),
1268 mForceSave(aForceSave),
1269 mCanceled(false),
1270 mStopRequestIssued(false),
1271 mIsFileChannel(false),
1272 mShouldCloseWindow(false),
1273 mHandleInternally(false),
1274 mReason(aReason),
1275 mTempFileIsExecutable(false),
1276 mTimeDownloadStarted(0),
1277 mContentLength(-1),
1278 mProgress(0),
1279 mSaver(nullptr),
1280 mDialogProgressListener(nullptr),
1281 mTransfer(nullptr),
1282 mRequest(nullptr),
1283 mExtProtSvc(aExtProtSvc) {
1284 // make sure the extention includes the '.'
1285 if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
1286 mTempFileExtension = char16_t('.');
1287 AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
1289 // Get mSuggestedFileName's current file extension.
1290 nsAutoString originalFileExt;
1291 int32_t pos = mSuggestedFileName.RFindChar('.');
1292 if (pos != kNotFound) {
1293 mSuggestedFileName.Right(originalFileExt,
1294 mSuggestedFileName.Length() - pos);
1297 // replace platform specific path separator and illegal characters to avoid
1298 // any confusion.
1299 // Try to keep the use of spaces or underscores in sync with the Downloads
1300 // code sanitization in DownloadPaths.jsm
1301 mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
1302 mSuggestedFileName.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
1303 mSuggestedFileName.ReplaceChar(char16_t(0), '_');
1304 mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
1305 mTempFileExtension.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
1307 // Remove unsafe bidi characters which might have spoofing implications (bug
1308 // 511521).
1309 const char16_t unsafeBidiCharacters[] = {
1310 char16_t(0x061c), // Arabic Letter Mark
1311 char16_t(0x200e), // Left-to-Right Mark
1312 char16_t(0x200f), // Right-to-Left Mark
1313 char16_t(0x202a), // Left-to-Right Embedding
1314 char16_t(0x202b), // Right-to-Left Embedding
1315 char16_t(0x202c), // Pop Directional Formatting
1316 char16_t(0x202d), // Left-to-Right Override
1317 char16_t(0x202e), // Right-to-Left Override
1318 char16_t(0x2066), // Left-to-Right Isolate
1319 char16_t(0x2067), // Right-to-Left Isolate
1320 char16_t(0x2068), // First Strong Isolate
1321 char16_t(0x2069), // Pop Directional Isolate
1322 char16_t(0)};
1323 mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
1324 mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
1326 // Remove trailing or leading spaces that we may have generated while
1327 // sanitizing.
1328 mSuggestedFileName.CompressWhitespace();
1329 mTempFileExtension.CompressWhitespace();
1331 EnsureCorrectExtension(originalFileExt);
1333 mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
1336 nsExternalAppHandler::~nsExternalAppHandler() {
1337 MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
1340 bool nsExternalAppHandler::ShouldForceExtension(const nsString& aFileExt) {
1341 nsAutoCString MIMEType;
1342 if (!mMimeInfo || NS_FAILED(mMimeInfo->GetMIMEType(MIMEType))) {
1343 return false;
1346 bool canForce = StringBeginsWith(MIMEType, "image/"_ns) ||
1347 StringBeginsWith(MIMEType, "audio/"_ns) ||
1348 StringBeginsWith(MIMEType, "video/"_ns);
1350 if (!canForce &&
1351 StaticPrefs::browser_download_sanitize_non_media_extensions()) {
1352 for (const char* mime : forcedExtensionMimetypes) {
1353 if (MIMEType.Equals(mime)) {
1354 canForce = true;
1355 break;
1359 if (!canForce) {
1360 return false;
1363 // If we get here, we know for sure the mimetype allows us to overwrite the
1364 // existing extension, if it's wrong. Return whether the extension is wrong:
1366 bool knownExtension = false;
1367 // Note that aFileExt is either empty or consists of an extension
1368 // *including the dot* which we remove for ExtensionExists().
1369 return (
1370 aFileExt.IsEmpty() || aFileExt.EqualsLiteral(".") ||
1371 (NS_SUCCEEDED(mMimeInfo->ExtensionExists(
1372 Substring(NS_ConvertUTF16toUTF8(aFileExt), 1), &knownExtension)) &&
1373 !knownExtension));
1376 void nsExternalAppHandler::EnsureCorrectExtension(const nsString& aFileExt) {
1377 // If we don't have an extension (which will include the .),
1378 // just short-circuit.
1379 if (mTempFileExtension.Length() <= 1) {
1380 return;
1383 // After removing trailing whitespaces from the name, if we have a
1384 // temp file extension, there are broadly 2 cases where we want to
1385 // replace the extension.
1386 // First, if the file extension contains invalid characters.
1387 // Second, for document type mimetypes, if the extension is either
1388 // missing or not valid for this mimetype.
1389 bool replaceExtension =
1390 (aFileExt.FindCharInSet(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS) !=
1391 kNotFound) ||
1392 ShouldForceExtension(aFileExt);
1394 if (replaceExtension) {
1395 int32_t pos = mSuggestedFileName.RFindChar('.');
1396 if (pos != kNotFound) {
1397 mSuggestedFileName =
1398 Substring(mSuggestedFileName, 0, pos) + mTempFileExtension;
1399 } else {
1400 mSuggestedFileName.Append(mTempFileExtension);
1405 * Ensure we don't double-append the file extension if it matches:
1407 if (replaceExtension ||
1408 aFileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator)) {
1409 // Matches -> mTempFileExtension can be empty
1410 mTempFileExtension.Truncate();
1414 void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
1415 MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1416 // Remove our request from the child loadGroup
1417 RetargetLoadNotifications(request);
1420 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(
1421 nsIWebProgressListener2* aWebProgressListener) {
1422 // This is always called by nsHelperDlg.js. Go ahead and register the
1423 // progress listener. At this point, we don't have mTransfer.
1424 mDialogProgressListener = aWebProgressListener;
1425 return NS_OK;
1428 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) {
1429 if (mFinalFileDestination)
1430 *aTarget = mFinalFileDestination;
1431 else
1432 *aTarget = mTempFile;
1434 NS_IF_ADDREF(*aTarget);
1435 return NS_OK;
1438 NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec) {
1439 // Use the real target if it's been set
1440 if (mFinalFileDestination) return mFinalFileDestination->IsExecutable(aExec);
1442 // Otherwise, use the stored executable-ness of the temporary
1443 *aExec = mTempFileIsExecutable;
1444 return NS_OK;
1447 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) {
1448 *aTime = mTimeDownloadStarted;
1449 return NS_OK;
1452 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t* aContentLength) {
1453 *aContentLength = mContentLength;
1454 return NS_OK;
1457 NS_IMETHODIMP nsExternalAppHandler::GetBrowsingContextId(
1458 uint64_t* aBrowsingContextId) {
1459 *aBrowsingContextId = mBrowsingContext ? mBrowsingContext->Id() : 0;
1460 return NS_OK;
1463 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest* request) {
1464 // we are going to run the downloading of the helper app in our own little
1465 // docloader / load group context. so go ahead and force the creation of a
1466 // load group and doc loader for us to use...
1467 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1468 if (!aChannel) return;
1470 bool isPrivate = NS_UsePrivateBrowsing(aChannel);
1472 nsCOMPtr<nsILoadGroup> oldLoadGroup;
1473 aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
1475 if (oldLoadGroup) {
1476 oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
1479 aChannel->SetLoadGroup(nullptr);
1480 aChannel->SetNotificationCallbacks(nullptr);
1482 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
1483 if (pbChannel) {
1484 pbChannel->SetPrivate(isPrivate);
1488 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
1489 // First we need to try to get the destination directory for the temporary
1490 // file.
1491 nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
1492 NS_ENSURE_SUCCESS(rv, rv);
1494 // At this point, we do not have a filename for the temp file. For security
1495 // purposes, this cannot be predictable, so we must use a cryptographic
1496 // quality PRNG to generate one.
1497 // We will request raw random bytes, and transform that to a base64 string,
1498 // as all characters from the base64 set are acceptable for filenames. For
1499 // each three bytes of random data, we will get four bytes of ASCII. Request
1500 // a bit more, to be safe, and truncate to the length we want in the end.
1502 const uint32_t wantedFileNameLength = 8;
1503 const uint32_t requiredBytesLength =
1504 static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
1506 nsCOMPtr<nsIRandomGenerator> rg =
1507 do_GetService("@mozilla.org/security/random-generator;1", &rv);
1508 NS_ENSURE_SUCCESS(rv, rv);
1510 uint8_t* buffer;
1511 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
1512 NS_ENSURE_SUCCESS(rv, rv);
1514 nsAutoCString tempLeafName;
1515 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
1516 requiredBytesLength);
1517 rv = Base64Encode(randomData, tempLeafName);
1518 free(buffer);
1519 buffer = nullptr;
1520 NS_ENSURE_SUCCESS(rv, rv);
1522 tempLeafName.Truncate(wantedFileNameLength);
1524 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1525 // to replace illegal characters -- notably '/'
1526 tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1528 // now append our extension.
1529 nsAutoCString ext;
1530 mMimeInfo->GetPrimaryExtension(ext);
1531 if (!ext.IsEmpty()) {
1532 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1533 if (ext.First() != '.') tempLeafName.Append('.');
1534 tempLeafName.Append(ext);
1537 // We need to temporarily create a dummy file with the correct
1538 // file extension to determine the executable-ness, so do this before adding
1539 // the extra .part extension.
1540 nsCOMPtr<nsIFile> dummyFile;
1541 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
1542 NS_ENSURE_SUCCESS(rv, rv);
1544 // Set the file name without .part
1545 rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1546 NS_ENSURE_SUCCESS(rv, rv);
1547 rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1548 NS_ENSURE_SUCCESS(rv, rv);
1550 // Store executable-ness then delete
1551 dummyFile->IsExecutable(&mTempFileIsExecutable);
1552 dummyFile->Remove(false);
1554 // Add an additional .part to prevent the OS from running this file in the
1555 // default application.
1556 tempLeafName.AppendLiteral(".part");
1558 rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1559 // make this file unique!!!
1560 NS_ENSURE_SUCCESS(rv, rv);
1561 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1562 NS_ENSURE_SUCCESS(rv, rv);
1564 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1565 // This is a bit broken in the case when createUnique actually had to append
1566 // some numbers, because then we now have a filename like foo.bar-1.part and
1567 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1568 // this. But the other options are all bad too.... Ideally we'd have a way
1569 // to tell createUnique to put its unique marker before the extension that
1570 // comes before ".part" or something.
1571 rv = mTempFile->GetLeafName(mTempLeafName);
1572 NS_ENSURE_SUCCESS(rv, rv);
1574 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, u".part"_ns),
1575 NS_ERROR_UNEXPECTED);
1577 // Strip off the ".part" from mTempLeafName
1578 mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
1580 MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
1581 mSaver =
1582 do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
1583 NS_ENSURE_SUCCESS(rv, rv);
1585 rv = mSaver->SetObserver(this);
1586 if (NS_FAILED(rv)) {
1587 mSaver = nullptr;
1588 return rv;
1591 rv = mSaver->EnableSha256();
1592 NS_ENSURE_SUCCESS(rv, rv);
1594 rv = mSaver->EnableSignatureInfo();
1595 NS_ENSURE_SUCCESS(rv, rv);
1596 LOG(("Enabled hashing and signature verification"));
1598 rv = mSaver->SetTarget(mTempFile, false);
1599 NS_ENSURE_SUCCESS(rv, rv);
1601 return rv;
1604 void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1605 nsIRequest* aRequest) {
1606 MOZ_ASSERT(aRequest);
1608 nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
1609 if (!encChannel) {
1610 return;
1613 // Turn off content encoding conversions if needed
1614 bool applyConversion = true;
1616 // First, check to see if conversion is already disabled. If so, we
1617 // have nothing to do here.
1618 encChannel->GetApplyConversion(&applyConversion);
1619 if (!applyConversion) {
1620 return;
1623 nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
1624 if (sourceURL) {
1625 nsAutoCString extension;
1626 sourceURL->GetFileExtension(extension);
1627 if (!extension.IsEmpty()) {
1628 nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
1629 encChannel->GetContentEncodings(getter_AddRefs(encEnum));
1630 if (encEnum) {
1631 bool hasMore;
1632 nsresult rv = encEnum->HasMore(&hasMore);
1633 if (NS_SUCCEEDED(rv) && hasMore) {
1634 nsAutoCString encType;
1635 rv = encEnum->GetNext(encType);
1636 if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) {
1637 MOZ_ASSERT(mExtProtSvc);
1638 mExtProtSvc->ApplyDecodingForExtension(extension, encType,
1639 &applyConversion);
1646 encChannel->SetApplyConversion(applyConversion);
1649 already_AddRefed<nsIInterfaceRequestor>
1650 nsExternalAppHandler::GetDialogParent() {
1651 nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext;
1653 if (!dialogParent && mBrowsingContext) {
1654 dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
1656 if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
1657 RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement();
1658 if (element) {
1659 dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
1662 return dialogParent.forget();
1665 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
1666 MOZ_ASSERT(request, "OnStartRequest without request?");
1668 // Set mTimeDownloadStarted here as the download has already started and
1669 // we want to record the start time before showing the filepicker.
1670 mTimeDownloadStarted = PR_Now();
1672 mRequest = request;
1674 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1676 nsresult rv;
1677 nsAutoCString MIMEType;
1678 if (mMimeInfo) {
1679 mMimeInfo->GetMIMEType(MIMEType);
1681 // Now get the URI
1682 if (aChannel) {
1683 aChannel->GetURI(getter_AddRefs(mSourceUrl));
1686 if (StaticPrefs::browser_download_improvements_to_download_panel() &&
1687 IsDownloadSpam(aChannel)) {
1688 return NS_OK;
1691 mDownloadClassification =
1692 nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType);
1694 if (mDownloadClassification == nsITransfer::DOWNLOAD_FORBIDDEN) {
1695 // If the download is rated as forbidden,
1696 // cancel the request so no ui knows about this.
1697 mCanceled = true;
1698 request->Cancel(NS_ERROR_ABORT);
1699 return NS_OK;
1702 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
1703 mIsFileChannel = fileChan != nullptr;
1704 if (!mIsFileChannel) {
1705 // It's possible that this request came from the child process and the
1706 // file channel actually lives there. If this returns true, then our
1707 // mSourceUrl will be an nsIFileURL anyway.
1708 nsCOMPtr<dom::nsIExternalHelperAppParent> parent(
1709 do_QueryInterface(request));
1710 mIsFileChannel = parent && parent->WasFileChannel();
1713 // Get content length
1714 if (aChannel) {
1715 aChannel->GetContentLength(&mContentLength);
1718 if (mBrowsingContext) {
1719 mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mBrowsingContext);
1720 mMaybeCloseWindowHelper->SetShouldCloseWindow(mShouldCloseWindow);
1721 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
1722 // Determine whether a new window was opened specifically for this request
1723 if (props) {
1724 bool tmp = false;
1725 if (NS_SUCCEEDED(
1726 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp))) {
1727 mMaybeCloseWindowHelper->SetShouldCloseWindow(tmp);
1732 // retarget all load notifications to our docloader instead of the original
1733 // window's docloader...
1734 RetargetLoadNotifications(request);
1736 // Close the underlying DOMWindow if it was opened specifically for the
1737 // download. We don't run this in the content process, since we have
1738 // an instance running in the parent as well, which will handle this
1739 // if needed.
1740 if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper) {
1741 mBrowsingContext = mMaybeCloseWindowHelper->MaybeCloseWindow();
1744 // In an IPC setting, we're allowing the child process, here, to make
1745 // decisions about decoding the channel (e.g. decompression). It will
1746 // still forward the decoded (uncompressed) data back to the parent.
1747 // Con: Uncompressed data means more IPC overhead.
1748 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1749 // Parent process doesn't need to expect CPU time on decompression.
1750 MaybeApplyDecodingForExtension(aChannel);
1752 // At this point, the child process has done everything it can usefully do
1753 // for OnStartRequest.
1754 if (XRE_IsContentProcess()) {
1755 return NS_OK;
1758 rv = SetUpTempFile(aChannel);
1759 if (NS_FAILED(rv)) {
1760 nsresult transferError = rv;
1762 rv = CreateFailedTransfer();
1763 if (NS_FAILED(rv)) {
1764 LOG(
1765 ("Failed to create transfer to report failure."
1766 "Will fallback to prompter!"));
1769 mCanceled = true;
1770 request->Cancel(transferError);
1772 nsAutoString path;
1773 if (mTempFile) mTempFile->GetPath(path);
1775 SendStatusChange(kWriteError, transferError, request, path);
1777 return NS_OK;
1780 // Inform channel it is open on behalf of a download to throttle it during
1781 // page loads and prevent its caching.
1782 nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
1783 if (httpInternal) {
1784 rv = httpInternal->SetChannelIsForDownload(true);
1785 MOZ_ASSERT(NS_SUCCEEDED(rv));
1788 if (mSourceUrl->SchemeIs("data")) {
1789 // In case we're downloading a data:// uri
1790 // we don't want to apply AllowTopLevelNavigationToDataURI.
1791 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
1792 loadInfo->SetForceAllowDataURI(true);
1795 // now that the temp file is set up, find out if we need to invoke a dialog
1796 // asking the user what they want us to do with this content...
1798 // We can get here for three reasons: "can't handle", "sniffed type", or
1799 // "server sent content-disposition:attachment". In the first case we want
1800 // to honor the user's "always ask" pref; in the other two cases we want to
1801 // honor it only if the default action is "save". Opening attachments in
1802 // helper apps by default breaks some websites (especially if the attachment
1803 // is one part of a multipart document). Opening sniffed content in helper
1804 // apps by default introduces security holes that we'd rather not have.
1806 // So let's find out whether the user wants to be prompted. If he does not,
1807 // check mReason and the preferred action to see what we should do.
1809 bool alwaysAsk = true;
1811 // Skip showing UnknownContentType dialog by default if the pref is set.
1812 bool skipShowingDialog =
1813 StaticPrefs::browser_download_improvements_to_download_panel();
1815 if (skipShowingDialog) {
1816 alwaysAsk = false;
1817 } else {
1818 mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
1821 if (alwaysAsk) {
1822 // But we *don't* ask if this mimeInfo didn't come from
1823 // our user configuration datastore and the user has said
1824 // at some point in the distant past that they don't
1825 // want to be asked. The latter fact would have been
1826 // stored in pref strings back in the old days.
1828 bool mimeTypeIsInDatastore = false;
1829 nsCOMPtr<nsIHandlerService> handlerSvc =
1830 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1831 if (handlerSvc) {
1832 handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
1834 if (!handlerSvc || !mimeTypeIsInDatastore) {
1835 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF,
1836 MIMEType.get())) {
1837 // Don't need to ask after all.
1838 alwaysAsk = false;
1839 // Make sure action matches pref (save to disk).
1840 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1841 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF,
1842 MIMEType.get())) {
1843 // Don't need to ask after all.
1844 alwaysAsk = false;
1847 } else if (MIMEType.EqualsLiteral("text/plain")) {
1848 nsAutoCString ext;
1849 mMimeInfo->GetPrimaryExtension(ext);
1850 // If people are sending us ApplicationReputation-eligible files with
1851 // text/plain mimetypes, enforce asking the user what to do.
1852 if (!ext.IsEmpty()) {
1853 nsAutoCString dummyFileName("f");
1854 if (ext.First() != '.') {
1855 dummyFileName.Append(".");
1857 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1858 nsCOMPtr<nsIApplicationReputationService> appRep =
1859 components::ApplicationReputation::Service();
1860 appRep->IsBinary(dummyFileName + ext, &alwaysAsk);
1864 int32_t action = nsIMIMEInfo::saveToDisk;
1865 mMimeInfo->GetPreferredAction(&action);
1867 bool forcePrompt =
1868 mReason == nsIHelperAppLauncherDialog::REASON_TYPESNIFFED ||
1869 (mReason == nsIHelperAppLauncherDialog::REASON_SERVERREQUEST &&
1870 !skipShowingDialog);
1872 // OK, now check why we're here
1873 if (!alwaysAsk && forcePrompt) {
1874 // Force asking if we're not saving. See comment back when we fetched the
1875 // alwaysAsk boolean for details.
1876 alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
1879 bool shouldAutomaticallyHandleInternally =
1880 action == nsIMIMEInfo::handleInternally &&
1881 StaticPrefs::browser_download_improvements_to_download_panel();
1883 // If we're not asking, check we actually know what to do:
1884 if (!alwaysAsk) {
1885 alwaysAsk = action != nsIMIMEInfo::saveToDisk &&
1886 action != nsIMIMEInfo::useHelperApp &&
1887 action != nsIMIMEInfo::useSystemDefault &&
1888 !shouldAutomaticallyHandleInternally;
1891 // if we were told that we _must_ save to disk without asking, all the stuff
1892 // before this is irrelevant; override it
1893 if (mForceSave) {
1894 alwaysAsk = false;
1895 action = nsIMIMEInfo::saveToDisk;
1897 // Additionally, if we are asked by the OS to open a local file,
1898 // automatically downloading it to create a second copy of that file doesn't
1899 // really make sense. We should ask the user what they want to do.
1900 if (mSourceUrl->SchemeIs("file") && !alwaysAsk &&
1901 action == nsIMIMEInfo::saveToDisk) {
1902 alwaysAsk = true;
1904 if (alwaysAsk) {
1905 // Display the dialog
1906 mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
1907 NS_ENSURE_SUCCESS(rv, rv);
1909 // this will create a reference cycle (the dialog holds a reference to us as
1910 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1911 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
1912 rv = mDialog->Show(this, dialogParent, mReason);
1914 // what do we do if the dialog failed? I guess we should call Cancel and
1915 // abort the load....
1916 } else {
1917 // We need to do the save/open immediately, then.
1918 #ifdef XP_WIN
1919 /* We need to see whether the file we've got here could be
1920 * executable. If it could, we had better not try to open it!
1921 * We can skip this check, though, if we have a setting to open in a
1922 * helper app.
1923 * This code mirrors the code in
1924 * nsExternalAppHandler::LaunchWithApplication so that what we
1925 * test here is as close as possible to what will really be
1926 * happening if we decide to execute
1928 nsCOMPtr<nsIHandlerApp> prefApp;
1929 mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
1930 if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
1931 nsCOMPtr<nsIFile> fileToTest;
1932 GetTargetFile(getter_AddRefs(fileToTest));
1933 if (fileToTest) {
1934 bool isExecutable;
1935 rv = fileToTest->IsExecutable(&isExecutable);
1936 if (NS_FAILED(rv) ||
1937 isExecutable) { // checking NS_FAILED, because paranoia is good
1938 action = nsIMIMEInfo::saveToDisk;
1940 } else { // Paranoia is good here too, though this really should not
1941 // happen
1942 NS_WARNING(
1943 "GetDownloadInfo returned a null file after the temp file has been "
1944 "set up! ");
1945 action = nsIMIMEInfo::saveToDisk;
1949 #endif
1950 bool alwaysAskWhereToSave =
1951 !Preferences::GetBool("browser.download.useDownloadDir") &&
1952 StaticPrefs::browser_download_improvements_to_download_panel();
1954 if ((action == nsIMIMEInfo::useHelperApp ||
1955 action == nsIMIMEInfo::useSystemDefault ||
1956 shouldAutomaticallyHandleInternally) &&
1957 !alwaysAskWhereToSave) {
1958 rv = LaunchWithApplication(shouldAutomaticallyHandleInternally, nullptr);
1959 } else {
1960 rv = PromptForSaveDestination();
1963 return NS_OK;
1966 bool nsExternalAppHandler::IsDownloadSpam(nsIChannel* aChannel) {
1967 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
1969 if (loadInfo->GetHasValidUserGestureActivation()) {
1970 return false;
1973 nsCOMPtr<nsIPermissionManager> permissionManager =
1974 mozilla::services::GetPermissionManager();
1975 nsCOMPtr<nsIPrincipal> principal = loadInfo->TriggeringPrincipal();
1976 bool exactHostMatch = false;
1977 constexpr auto type = "automatic-download"_ns;
1978 nsCOMPtr<nsIPermission> permission;
1980 permissionManager->GetPermissionObject(principal, type, exactHostMatch,
1981 getter_AddRefs(permission));
1983 if (permission) {
1984 uint32_t capability;
1985 permission->GetCapability(&capability);
1986 if (capability == nsIPermissionManager::DENY_ACTION) {
1987 mCanceled = true;
1988 aChannel->Cancel(NS_ERROR_ABORT);
1989 return true;
1991 if (capability == nsIPermissionManager::ALLOW_ACTION) {
1992 return false;
1994 // If no action is set (i.e: null), we set PROMPT_ACTION by default,
1995 // which will notify the Downloads UI to open the panel on the next request.
1996 if (capability == nsIPermissionManager::PROMPT_ACTION) {
1997 nsCOMPtr<nsIObserverService> observerService =
1998 mozilla::services::GetObserverService();
2000 nsAutoCString cStringURI;
2001 loadInfo->TriggeringPrincipal()->GetPrePath(cStringURI);
2002 observerService->NotifyObservers(
2003 nullptr, "blocked-automatic-download",
2004 NS_ConvertASCIItoUTF16(cStringURI.get()).get());
2005 // FIXME: In order to escape memory leaks, currently we cancel blocked
2006 // downloads. This is temporary solution, because download data should be
2007 // kept in order to restart the blocked download.
2008 mCanceled = true;
2009 aChannel->Cancel(NS_ERROR_ABORT);
2010 // End cancel
2011 return true;
2013 } else {
2014 permissionManager->AddFromPrincipal(
2015 principal, type, nsIPermissionManager::PROMPT_ACTION,
2016 nsIPermissionManager::EXPIRE_NEVER, 0 /* expire time */);
2019 return false;
2022 // Convert error info into proper message text and send OnStatusChange
2023 // notification to the dialog progress listener or nsITransfer implementation.
2024 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
2025 nsIRequest* aRequest,
2026 const nsString& path) {
2027 const char* msgId = nullptr;
2028 switch (rv) {
2029 case NS_ERROR_OUT_OF_MEMORY:
2030 // No memory
2031 msgId = "noMemory";
2032 break;
2034 case NS_ERROR_FILE_NO_DEVICE_SPACE:
2035 // Out of space on target volume.
2036 msgId = "diskFull";
2037 break;
2039 case NS_ERROR_FILE_READ_ONLY:
2040 // Attempt to write to read/only file.
2041 msgId = "readOnly";
2042 break;
2044 case NS_ERROR_FILE_ACCESS_DENIED:
2045 if (type == kWriteError) {
2046 // Attempt to write without sufficient permissions.
2047 #if defined(ANDROID)
2048 // On Android this means the SD card is present but
2049 // unavailable (read-only).
2050 msgId = "SDAccessErrorCardReadOnly";
2051 #else
2052 msgId = "accessError";
2053 #endif
2054 } else {
2055 msgId = "launchError";
2057 break;
2059 case NS_ERROR_FILE_NOT_FOUND:
2060 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
2061 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
2062 // Helper app not found, let's verify this happened on launch
2063 if (type == kLaunchError) {
2064 msgId = "helperAppNotFound";
2065 break;
2067 #if defined(ANDROID)
2068 else if (type == kWriteError) {
2069 // On Android this means the SD card is missing (not in
2070 // SD slot).
2071 msgId = "SDAccessErrorCardMissing";
2072 break;
2074 #endif
2075 [[fallthrough]];
2077 default:
2078 // Generic read/write/launch error message.
2079 switch (type) {
2080 case kReadError:
2081 msgId = "readError";
2082 break;
2083 case kWriteError:
2084 msgId = "writeError";
2085 break;
2086 case kLaunchError:
2087 msgId = "launchError";
2088 break;
2090 break;
2093 MOZ_LOG(
2094 nsExternalHelperAppService::mLog, LogLevel::Error,
2095 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32 "\n",
2096 msgId, type, mDialogProgressListener.get(), mTransfer.get(),
2097 static_cast<uint32_t>(rv)));
2099 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
2100 (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
2102 // Get properties file bundle and extract status string.
2103 nsCOMPtr<nsIStringBundleService> stringService =
2104 mozilla::components::StringBundle::Service();
2105 if (stringService) {
2106 nsCOMPtr<nsIStringBundle> bundle;
2107 if (NS_SUCCEEDED(stringService->CreateBundle(
2108 "chrome://global/locale/nsWebBrowserPersist.properties",
2109 getter_AddRefs(bundle)))) {
2110 nsAutoString msgText;
2111 AutoTArray<nsString, 1> strings = {path};
2112 if (NS_SUCCEEDED(bundle->FormatStringFromName(msgId, strings, msgText))) {
2113 if (mDialogProgressListener) {
2114 // We have a listener, let it handle the error.
2115 mDialogProgressListener->OnStatusChange(
2116 nullptr, (type == kReadError) ? aRequest : nullptr, rv,
2117 msgText.get());
2118 } else if (mTransfer) {
2119 mTransfer->OnStatusChange(nullptr,
2120 (type == kReadError) ? aRequest : nullptr,
2121 rv, msgText.get());
2122 } else if (XRE_IsParentProcess()) {
2123 // We don't have a listener. Simply show the alert ourselves.
2124 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
2125 nsresult qiRv;
2126 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(dialogParent, &qiRv));
2127 nsAutoString title;
2128 bundle->FormatStringFromName("title", strings, title);
2130 MOZ_LOG(
2131 nsExternalHelperAppService::mLog, LogLevel::Debug,
2132 ("mBrowsingContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
2133 ", title='%s', msg='%s'",
2134 mBrowsingContext.get(), prompter.get(),
2135 static_cast<uint32_t>(qiRv), NS_ConvertUTF16toUTF8(title).get(),
2136 NS_ConvertUTF16toUTF8(msgText).get()));
2138 // If we didn't have a prompter we will try and get a window
2139 // instead, get it's docshell and use it to alert the user.
2140 if (!prompter) {
2141 nsCOMPtr<nsPIDOMWindowOuter> window(do_GetInterface(dialogParent));
2142 if (!window || !window->GetDocShell()) {
2143 return;
2146 prompter = do_GetInterface(window->GetDocShell(), &qiRv);
2148 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
2149 ("No prompter from mBrowsingContext, using DocShell, "
2150 "window=0x%p, docShell=0x%p, "
2151 "prompter=0x%p, qi rv=0x%08" PRIX32,
2152 window.get(), window->GetDocShell(), prompter.get(),
2153 static_cast<uint32_t>(qiRv)));
2155 // If we still don't have a prompter, there's nothing else we
2156 // can do so just return.
2157 if (!prompter) {
2158 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
2159 ("No prompter from DocShell, no way to alert user"));
2160 return;
2164 // We should always have a prompter at this point.
2165 prompter->Alert(title.get(), msgText.get());
2172 NS_IMETHODIMP
2173 nsExternalAppHandler::OnDataAvailable(nsIRequest* request,
2174 nsIInputStream* inStr,
2175 uint64_t sourceOffset, uint32_t count) {
2176 nsresult rv = NS_OK;
2177 // first, check to see if we've been canceled....
2178 if (mCanceled || !mSaver) {
2179 // then go cancel our underlying channel too
2180 return request->Cancel(NS_BINDING_ABORTED);
2183 // read the data out of the stream and write it to the temp file.
2184 if (count > 0) {
2185 mProgress += count;
2187 nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
2188 rv = saver->OnDataAvailable(request, inStr, sourceOffset, count);
2189 if (NS_SUCCEEDED(rv)) {
2190 // Send progress notification.
2191 if (mTransfer) {
2192 mTransfer->OnProgressChange64(nullptr, request, mProgress,
2193 mContentLength, mProgress,
2194 mContentLength);
2196 } else {
2197 // An error occurred, notify listener.
2198 nsAutoString tempFilePath;
2199 if (mTempFile) {
2200 mTempFile->GetPath(tempFilePath);
2202 SendStatusChange(kReadError, rv, request, tempFilePath);
2204 // Cancel the download.
2205 Cancel(rv);
2208 return rv;
2211 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest* request,
2212 nsresult aStatus) {
2213 LOG(
2214 ("nsExternalAppHandler::OnStopRequest\n"
2215 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08" PRIX32 "\n",
2216 mCanceled, mTransfer.get(), static_cast<uint32_t>(aStatus)));
2218 mStopRequestIssued = true;
2220 // Cancel if the request did not complete successfully.
2221 if (!mCanceled && NS_FAILED(aStatus)) {
2222 // Send error notification.
2223 nsAutoString tempFilePath;
2224 if (mTempFile) mTempFile->GetPath(tempFilePath);
2225 SendStatusChange(kReadError, aStatus, request, tempFilePath);
2227 Cancel(aStatus);
2230 // first, check to see if we've been canceled....
2231 if (mCanceled || !mSaver) {
2232 return NS_OK;
2235 return mSaver->Finish(NS_OK);
2238 NS_IMETHODIMP
2239 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver* aSaver,
2240 nsIFile* aTarget) {
2241 return NS_OK;
2244 NS_IMETHODIMP
2245 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
2246 nsresult aStatus) {
2247 LOG(
2248 ("nsExternalAppHandler::OnSaveComplete\n"
2249 " aSaver=0x%p, aStatus=0x%08" PRIX32 ", mCanceled=%d, mTransfer=0x%p\n",
2250 aSaver, static_cast<uint32_t>(aStatus), mCanceled, mTransfer.get()));
2252 if (!mCanceled) {
2253 // Save the hash and signature information
2254 (void)mSaver->GetSha256Hash(mHash);
2255 (void)mSaver->GetSignatureInfo(mSignatureInfo);
2257 // Free the reference that the saver keeps on us, even if we couldn't get
2258 // the hash.
2259 mSaver = nullptr;
2261 // Save the redirect information.
2262 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2263 if (channel) {
2264 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2265 nsresult rv = NS_OK;
2266 nsCOMPtr<nsIMutableArray> redirectChain =
2267 do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
2268 NS_ENSURE_SUCCESS(rv, rv);
2269 LOG(("nsExternalAppHandler: Got %zu redirects\n",
2270 loadInfo->RedirectChain().Length()));
2271 for (nsIRedirectHistoryEntry* entry : loadInfo->RedirectChain()) {
2272 redirectChain->AppendElement(entry);
2274 mRedirects = redirectChain;
2277 if (NS_FAILED(aStatus)) {
2278 nsAutoString path;
2279 mTempFile->GetPath(path);
2281 // It may happen when e10s is enabled that there will be no transfer
2282 // object available to communicate status as expected by the system.
2283 // Let's try and create a temporary transfer object to take care of this
2284 // for us, we'll fall back to using the prompt service if we absolutely
2285 // have to.
2286 if (!mTransfer) {
2287 // We don't care if this fails.
2288 CreateFailedTransfer();
2291 SendStatusChange(kWriteError, aStatus, nullptr, path);
2292 if (!mCanceled) Cancel(aStatus);
2293 return NS_OK;
2297 // Notify the transfer object that we are done if the user has chosen an
2298 // action. If the user hasn't chosen an action, the progress listener
2299 // (nsITransfer) will be notified in CreateTransfer.
2300 if (mTransfer) {
2301 NotifyTransfer(aStatus);
2304 return NS_OK;
2307 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus) {
2308 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2309 MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
2311 LOG(("Notifying progress listener"));
2313 if (NS_SUCCEEDED(aStatus)) {
2314 (void)mTransfer->SetSha256Hash(mHash);
2315 (void)mTransfer->SetSignatureInfo(mSignatureInfo);
2316 (void)mTransfer->SetRedirects(mRedirects);
2317 (void)mTransfer->OnProgressChange64(
2318 nullptr, nullptr, mProgress, mContentLength, mProgress, mContentLength);
2321 (void)mTransfer->OnStateChange(nullptr, nullptr,
2322 nsIWebProgressListener::STATE_STOP |
2323 nsIWebProgressListener::STATE_IS_REQUEST |
2324 nsIWebProgressListener::STATE_IS_NETWORK,
2325 aStatus);
2327 // This nsITransfer object holds a reference to us (we are its observer), so
2328 // we need to release the reference to break a reference cycle (and therefore
2329 // to prevent leaking). We do this even if the previous calls failed.
2330 mTransfer = nullptr;
2333 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo** aMIMEInfo) {
2334 *aMIMEInfo = mMimeInfo;
2335 NS_ADDREF(*aMIMEInfo);
2336 return NS_OK;
2339 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI** aSourceURI) {
2340 NS_ENSURE_ARG(aSourceURI);
2341 *aSourceURI = mSourceUrl;
2342 NS_IF_ADDREF(*aSourceURI);
2343 return NS_OK;
2346 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(
2347 nsAString& aSuggestedFileName) {
2348 aSuggestedFileName = mSuggestedFileName;
2349 return NS_OK;
2352 nsresult nsExternalAppHandler::CreateTransfer() {
2353 LOG(("nsExternalAppHandler::CreateTransfer"));
2355 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2356 // We are back from the helper app dialog (where the user chooses to save or
2357 // open), but we aren't done processing the load. in this case, throw up a
2358 // progress dialog so the user can see what's going on.
2359 // Also, release our reference to mDialog. We don't need it anymore, and we
2360 // need to break the reference cycle.
2361 mDialog = nullptr;
2362 if (!mDialogProgressListener) {
2363 NS_WARNING("The dialog should nullify the dialog progress listener");
2365 // In case of a non acceptable download, we need to cancel the request and
2366 // pass a FailedTransfer for the Download UI.
2367 if (mDownloadClassification != nsITransfer::DOWNLOAD_ACCEPTABLE) {
2368 mCanceled = true;
2369 mRequest->Cancel(NS_ERROR_ABORT);
2370 if (mSaver) {
2371 mSaver->Finish(NS_ERROR_ABORT);
2372 mSaver = nullptr;
2374 return CreateFailedTransfer();
2376 nsresult rv;
2378 // We must be able to create an nsITransfer object. If not, it doesn't matter
2379 // much that we can't launch the helper application or save to disk. Work on
2380 // a local copy rather than mTransfer until we know we succeeded, to make it
2381 // clearer that this function is re-entrant.
2382 nsCOMPtr<nsITransfer> transfer =
2383 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
2384 NS_ENSURE_SUCCESS(rv, rv);
2386 // Initialize the download
2387 nsCOMPtr<nsIURI> target;
2388 rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
2389 NS_ENSURE_SUCCESS(rv, rv);
2391 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2392 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mRequest);
2393 nsCOMPtr<nsIReferrerInfo> referrerInfo = nullptr;
2394 if (httpChannel) {
2395 referrerInfo = httpChannel->GetReferrerInfo();
2398 if (mBrowsingContext) {
2399 rv = transfer->InitWithBrowsingContext(
2400 mSourceUrl, target, u""_ns, mMimeInfo, mTimeDownloadStarted, mTempFile,
2401 this, channel && NS_UsePrivateBrowsing(channel),
2402 mDownloadClassification, referrerInfo, mBrowsingContext,
2403 mHandleInternally);
2404 } else {
2405 rv = transfer->Init(mSourceUrl, target, u""_ns, mMimeInfo,
2406 mTimeDownloadStarted, mTempFile, this,
2407 channel && NS_UsePrivateBrowsing(channel),
2408 mDownloadClassification, referrerInfo);
2411 NS_ENSURE_SUCCESS(rv, rv);
2413 // If we were cancelled since creating the transfer, just return. It is
2414 // always ok to return NS_OK if we are cancelled. Callers of this function
2415 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2416 // twice.
2417 if (mCanceled) {
2418 return NS_OK;
2420 rv = transfer->OnStateChange(nullptr, mRequest,
2421 nsIWebProgressListener::STATE_START |
2422 nsIWebProgressListener::STATE_IS_REQUEST |
2423 nsIWebProgressListener::STATE_IS_NETWORK,
2424 NS_OK);
2425 NS_ENSURE_SUCCESS(rv, rv);
2427 if (mCanceled) {
2428 return NS_OK;
2431 mRequest = nullptr;
2432 // Finally, save the transfer to mTransfer.
2433 mTransfer = transfer;
2434 transfer = nullptr;
2436 // While we were bringing up the progress dialog, we actually finished
2437 // processing the url. If that's the case then mStopRequestIssued will be
2438 // true and OnSaveComplete has been called.
2439 if (mStopRequestIssued && !mSaver && mTransfer) {
2440 NotifyTransfer(NS_OK);
2443 return rv;
2446 nsresult nsExternalAppHandler::CreateFailedTransfer() {
2447 nsresult rv;
2448 nsCOMPtr<nsITransfer> transfer =
2449 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
2450 NS_ENSURE_SUCCESS(rv, rv);
2452 // We won't pass the temp file to the transfer, so if we have one it needs to
2453 // get deleted now.
2454 if (mTempFile) {
2455 if (mSaver) {
2456 mSaver->Finish(NS_BINDING_ABORTED);
2457 mSaver = nullptr;
2459 mTempFile->Remove(false);
2462 nsCOMPtr<nsIURI> pseudoTarget;
2463 if (!mFinalFileDestination) {
2464 // If we don't have a download directory we're kinda screwed but it's OK
2465 // we'll still report the error via the prompter.
2466 nsCOMPtr<nsIFile> pseudoFile;
2467 rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
2468 NS_ENSURE_SUCCESS(rv, rv);
2470 // Append the default suggested filename. If the user restarts the transfer
2471 // we will re-trigger a filename check anyway to ensure that it is unique.
2472 rv = pseudoFile->Append(mSuggestedFileName);
2473 NS_ENSURE_SUCCESS(rv, rv);
2475 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
2476 NS_ENSURE_SUCCESS(rv, rv);
2477 } else {
2478 // Initialize the target, if present
2479 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), mFinalFileDestination);
2480 NS_ENSURE_SUCCESS(rv, rv);
2483 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2484 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mRequest);
2485 nsCOMPtr<nsIReferrerInfo> referrerInfo = nullptr;
2486 if (httpChannel) {
2487 referrerInfo = httpChannel->GetReferrerInfo();
2490 if (mBrowsingContext) {
2491 rv = transfer->InitWithBrowsingContext(
2492 mSourceUrl, pseudoTarget, u""_ns, mMimeInfo, mTimeDownloadStarted,
2493 mTempFile, this, channel && NS_UsePrivateBrowsing(channel),
2494 mDownloadClassification, referrerInfo, mBrowsingContext,
2495 mHandleInternally);
2496 } else {
2497 rv = transfer->Init(mSourceUrl, pseudoTarget, u""_ns, mMimeInfo,
2498 mTimeDownloadStarted, mTempFile, this,
2499 channel && NS_UsePrivateBrowsing(channel),
2500 mDownloadClassification, referrerInfo);
2502 NS_ENSURE_SUCCESS(rv, rv);
2504 // Our failed transfer is ready.
2505 mTransfer = std::move(transfer);
2507 return NS_OK;
2510 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile* aFile) {
2511 if (aFile)
2512 ContinueSave(aFile);
2513 else
2514 Cancel(NS_BINDING_ABORTED);
2516 return NS_OK;
2519 void nsExternalAppHandler::RequestSaveDestination(
2520 const nsString& aDefaultFile, const nsString& aFileExtension) {
2521 // Display the dialog
2522 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2523 // "AutoDownload" w/o showing a prompt
2524 nsresult rv = NS_OK;
2525 if (!mDialog) {
2526 // Get helper app launcher dialog.
2527 mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
2528 if (rv != NS_OK) {
2529 Cancel(NS_BINDING_ABORTED);
2530 return;
2534 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we
2535 // can't unescape it because the dialog is implemented by a JS component which
2536 // doesn't have a window so no unescape routine is defined...
2538 // Now, be sure to keep |this| alive, and the dialog
2539 // If we don't do this, users that close the helper app dialog while the file
2540 // picker is up would cause Cancel() to be called, and the dialog would be
2541 // released, which would release this object too, which would crash.
2542 // See Bug 249143
2543 RefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
2544 nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
2545 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
2547 rv = dlg->PromptForSaveToFileAsync(this, dialogParent, aDefaultFile.get(),
2548 aFileExtension.get(), mForceSave);
2549 if (NS_FAILED(rv)) {
2550 Cancel(NS_BINDING_ABORTED);
2554 // PromptForSaveDestination should only be called by the helper app dialog which
2555 // allows the user to say launch with application or save to disk.
2556 NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
2557 if (mCanceled) return NS_OK;
2559 if (!StaticPrefs::browser_download_improvements_to_download_panel()) {
2560 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
2563 if (mSuggestedFileName.IsEmpty()) {
2564 RequestSaveDestination(mTempLeafName, mTempFileExtension);
2565 } else {
2566 nsAutoString fileExt;
2567 int32_t pos = mSuggestedFileName.RFindChar('.');
2568 if (pos >= 0) {
2569 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
2571 if (fileExt.IsEmpty()) {
2572 fileExt = mTempFileExtension;
2575 RequestSaveDestination(mSuggestedFileName, fileExt);
2578 return NS_OK;
2580 nsresult nsExternalAppHandler::ContinueSave(nsIFile* aNewFileLocation) {
2581 if (mCanceled) return NS_OK;
2583 MOZ_ASSERT(aNewFileLocation, "Must be called with a non-null file");
2585 int32_t action = nsIMIMEInfo::saveToDisk;
2586 mMimeInfo->GetPreferredAction(&action);
2587 bool shouldAutomaticallyHandleInternally =
2588 action == nsIMIMEInfo::handleInternally &&
2589 StaticPrefs::browser_download_improvements_to_download_panel();
2591 if (StaticPrefs::browser_download_improvements_to_download_panel() &&
2592 (action == nsIMIMEInfo::useHelperApp ||
2593 action == nsIMIMEInfo::useSystemDefault ||
2594 shouldAutomaticallyHandleInternally)) {
2595 return LaunchWithApplication(shouldAutomaticallyHandleInternally,
2596 aNewFileLocation);
2599 nsresult rv = NS_OK;
2600 nsCOMPtr<nsIFile> fileToUse = aNewFileLocation;
2601 mFinalFileDestination = fileToUse;
2603 // Move what we have in the final directory, but append .part
2604 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2605 // saver if we are done (Finish has been called) but OnSaverComplete has
2606 // not been called.
2607 if (mFinalFileDestination && mSaver && !mStopRequestIssued) {
2608 nsCOMPtr<nsIFile> movedFile;
2609 mFinalFileDestination->Clone(getter_AddRefs(movedFile));
2610 if (movedFile) {
2611 // Get the old leaf name and append .part to it
2612 nsAutoString name;
2613 mFinalFileDestination->GetLeafName(name);
2614 name.AppendLiteral(".part");
2615 movedFile->SetLeafName(name);
2617 rv = mSaver->SetTarget(movedFile, true);
2618 if (NS_FAILED(rv)) {
2619 nsAutoString path;
2620 mTempFile->GetPath(path);
2621 SendStatusChange(kWriteError, rv, nullptr, path);
2622 Cancel(rv);
2623 return NS_OK;
2626 mTempFile = movedFile;
2630 // The helper app dialog has told us what to do and we have a final file
2631 // destination.
2632 rv = CreateTransfer();
2633 // If we fail to create the transfer, Cancel.
2634 if (NS_FAILED(rv)) {
2635 Cancel(rv);
2636 return rv;
2639 return NS_OK;
2642 // LaunchWithApplication should only be called by the helper app dialog which
2643 // allows the user to say launch with application or save to disk.
2644 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(
2645 bool aHandleInternally, nsIFile* aNewFileLocation) {
2646 if (mCanceled) return NS_OK;
2648 mHandleInternally = aHandleInternally;
2650 // Now check if the file is local, in which case we won't bother with saving
2651 // it to a temporary directory and just launch it from where it is
2652 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
2653 if (fileUrl && mIsFileChannel) {
2654 Cancel(NS_BINDING_ABORTED);
2655 nsCOMPtr<nsIFile> file;
2656 nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
2658 if (NS_SUCCEEDED(rv)) {
2659 rv = mMimeInfo->LaunchWithFile(file);
2660 if (NS_SUCCEEDED(rv)) return NS_OK;
2662 nsAutoString path;
2663 if (file) file->GetPath(path);
2664 // If we get here, an error happened
2665 SendStatusChange(kLaunchError, rv, nullptr, path);
2666 return rv;
2669 // Now that the user has elected to launch the downloaded file with a helper
2670 // app, we're justified in removing the 'salted' name. We'll rename to what
2671 // was specified in mSuggestedFileName after the download is done prior to
2672 // launching the helper app. So that any existing file of that name won't be
2673 // overwritten we call CreateUnique(). Also note that we use the same
2674 // directory as originally downloaded so the download can be renamed in place
2675 // later.
2676 nsCOMPtr<nsIFile> fileToUse;
2677 if (aNewFileLocation &&
2678 StaticPrefs::browser_download_improvements_to_download_panel()) {
2679 fileToUse = aNewFileLocation;
2680 } else {
2681 (void)GetDownloadDirectory(getter_AddRefs(fileToUse));
2683 if (mSuggestedFileName.IsEmpty()) {
2684 // Keep using the leafname of the temp file, since we're just starting a
2685 // helper
2686 mSuggestedFileName = mTempLeafName;
2689 #ifdef XP_WIN
2690 fileToUse->Append(mSuggestedFileName + mTempFileExtension);
2691 #else
2692 fileToUse->Append(mSuggestedFileName);
2693 #endif
2696 nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
2697 if (NS_SUCCEEDED(rv)) {
2698 mFinalFileDestination = fileToUse;
2699 // launch the progress window now that the user has picked the desired
2700 // action.
2701 rv = CreateTransfer();
2702 if (NS_FAILED(rv)) {
2703 Cancel(rv);
2705 } else {
2706 // Cancel the download and report an error. We do not want to end up in
2707 // a state where it appears that we have a normal download that is
2708 // pointing to a file that we did not actually create.
2709 nsAutoString path;
2710 mTempFile->GetPath(path);
2711 SendStatusChange(kWriteError, rv, nullptr, path);
2712 Cancel(rv);
2714 return rv;
2717 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
2718 NS_ENSURE_ARG(NS_FAILED(aReason));
2720 if (mCanceled) {
2721 return NS_OK;
2723 mCanceled = true;
2725 if (mSaver) {
2726 // We are still writing to the target file. Give the saver a chance to
2727 // close the target file, then notify the transfer object if necessary in
2728 // the OnSaveComplete callback.
2729 mSaver->Finish(aReason);
2730 mSaver = nullptr;
2731 } else {
2732 if (mStopRequestIssued && mTempFile) {
2733 // This branch can only happen when the user cancels the helper app dialog
2734 // when the request has completed. The temp file has to be removed here,
2735 // because mSaver has been released at that time with the temp file left.
2736 (void)mTempFile->Remove(false);
2739 // Notify the transfer object that the download has been canceled, if the
2740 // user has already chosen an action and we didn't notify already.
2741 if (mTransfer) {
2742 NotifyTransfer(aReason);
2746 // Break our reference cycle with the helper app dialog (set up in
2747 // OnStartRequest)
2748 mDialog = nullptr;
2750 mRequest = nullptr;
2752 // Release the listener, to break the reference cycle with it (we are the
2753 // observer of the listener).
2754 mDialogProgressListener = nullptr;
2756 return NS_OK;
2759 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char* prefName,
2760 const char* aContentType) {
2761 // Search the obsolete pref strings.
2762 nsAutoCString prefCString;
2763 Preferences::GetCString(prefName, prefCString);
2764 if (prefCString.IsEmpty()) {
2765 // Default is true, if not found in the pref string.
2766 return true;
2769 NS_UnescapeURL(prefCString);
2770 nsACString::const_iterator start, end;
2771 prefCString.BeginReading(start);
2772 prefCString.EndReading(end);
2773 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType), start,
2774 end);
2777 NS_IMETHODIMP
2778 nsExternalAppHandler::GetName(nsACString& aName) {
2779 aName.AssignLiteral("nsExternalAppHandler");
2780 return NS_OK;
2783 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2784 // The following section contains our nsIMIMEService implementation and related
2785 // methods.
2787 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2789 // nsIMIMEService methods
2790 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(
2791 const nsACString& aMIMEType, const nsACString& aFileExt,
2792 nsIMIMEInfo** _retval) {
2793 MOZ_ASSERT(!aMIMEType.IsEmpty() || !aFileExt.IsEmpty(),
2794 "Give me something to work with");
2795 MOZ_DIAGNOSTIC_ASSERT(aFileExt.FindChar('\0') == kNotFound,
2796 "The extension should never contain null characters");
2797 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2798 PromiseFlatCString(aMIMEType).get(),
2799 PromiseFlatCString(aFileExt).get()));
2801 *_retval = nullptr;
2803 // OK... we need a type. Get one.
2804 nsAutoCString typeToUse(aMIMEType);
2805 if (typeToUse.IsEmpty()) {
2806 nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
2807 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
2810 // We promise to only send lower case mime types to the OS
2811 ToLowerCase(typeToUse);
2813 // First, ask the OS for a mime info
2814 bool found;
2815 nsresult rv = GetMIMEInfoFromOS(typeToUse, aFileExt, &found, _retval);
2816 if (NS_WARN_IF(NS_FAILED(rv))) {
2817 return rv;
2820 LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
2821 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2822 if (!*_retval) return NS_ERROR_OUT_OF_MEMORY;
2824 // The handler service can make up for bad mime types by checking the file
2825 // extension. If the mime type is known (in extras or in the handler
2826 // service), we stop it doing so by flipping this bool to true.
2827 bool trustMIMEType = false;
2829 // Check extras - not everything we support will be known by the OS store,
2830 // unfortunately, and it may even miss some extensions that we know should
2831 // be accepted. We only do this for non-octet-stream mimetypes, because
2832 // our information for octet-stream would lead to us trying to open all such
2833 // files as Binary file with exe, com or bin extension regardless of the
2834 // real extension.
2835 if (!typeToUse.Equals(APPLICATION_OCTET_STREAM,
2836 nsCaseInsensitiveCStringComparator)) {
2837 rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, !found, *_retval);
2838 LOG(("Searched extras (by type), rv 0x%08" PRIX32 "\n",
2839 static_cast<uint32_t>(rv)));
2840 trustMIMEType = NS_SUCCEEDED(rv);
2841 found = found || NS_SUCCEEDED(rv);
2844 // Now, let's see if we can find something in our datastore.
2845 // This will not overwrite the OS information that interests us
2846 // (i.e. default application, default app. description)
2847 nsCOMPtr<nsIHandlerService> handlerSvc =
2848 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2849 if (handlerSvc) {
2850 bool hasHandler = false;
2851 (void)handlerSvc->Exists(*_retval, &hasHandler);
2852 if (hasHandler) {
2853 rv = handlerSvc->FillHandlerInfo(*_retval, ""_ns);
2854 LOG(("Data source: Via type: retval 0x%08" PRIx32 "\n",
2855 static_cast<uint32_t>(rv)));
2856 trustMIMEType = trustMIMEType || NS_SUCCEEDED(rv);
2857 } else {
2858 rv = NS_ERROR_NOT_AVAILABLE;
2861 found = found || NS_SUCCEEDED(rv);
2864 // If we still haven't found anything, try finding a match for
2865 // an extension in extras first:
2866 if (!found && !aFileExt.IsEmpty()) {
2867 rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
2868 LOG(("Searched extras (by ext), rv 0x%08" PRIX32 "\n",
2869 static_cast<uint32_t>(rv)));
2872 // Then check the handler service - but only do so if we really do not know
2873 // the mimetype. This avoids overwriting good mimetype info with bad file
2874 // extension info.
2875 if ((!found || !trustMIMEType) && handlerSvc && !aFileExt.IsEmpty()) {
2876 nsAutoCString overrideType;
2877 rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
2878 if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
2879 // We can't check handlerSvc->Exists() here, because we have a
2880 // overideType. That's ok, it just results in some console noise.
2881 // (If there's no handler for the override type, it throws)
2882 rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
2883 LOG(("Data source: Via ext: retval 0x%08" PRIx32 "\n",
2884 static_cast<uint32_t>(rv)));
2885 found = found || NS_SUCCEEDED(rv);
2889 // If we still don't have a match, at least set the file description
2890 // to `${aFileExt} File` if it's empty:
2891 if (!found && !aFileExt.IsEmpty()) {
2892 // XXXzpao This should probably be localized
2893 nsAutoCString desc(aFileExt);
2894 desc.AppendLiteral(" File");
2895 (*_retval)->SetDescription(NS_ConvertUTF8toUTF16(desc));
2896 LOG(("Falling back to 'File' file description\n"));
2899 // Sometimes, OSes give us bad data. We have a set of forbidden extensions
2900 // for some MIME types. If the primary extension is forbidden,
2901 // overwrite it with a known-good one. See bug 1571247 for context.
2902 nsAutoCString primaryExtension;
2903 (*_retval)->GetPrimaryExtension(primaryExtension);
2904 if (!primaryExtension.EqualsIgnoreCase(PromiseFlatCString(aFileExt).get())) {
2905 if (MaybeReplacePrimaryExtension(primaryExtension, *_retval)) {
2906 (*_retval)->GetPrimaryExtension(primaryExtension);
2910 // Finally, check if we got a file extension and if yes, if it is an
2911 // extension on the mimeinfo, in which case we want it to be the primary one
2912 if (!aFileExt.IsEmpty()) {
2913 bool matches = false;
2914 (*_retval)->ExtensionExists(aFileExt, &matches);
2915 LOG(("Extension '%s' matches mime info: %i\n",
2916 PromiseFlatCString(aFileExt).get(), matches));
2917 if (matches) {
2918 nsAutoCString fileExt;
2919 ToLowerCase(aFileExt, fileExt);
2920 (*_retval)->SetPrimaryExtension(fileExt);
2921 primaryExtension = fileExt;
2925 // Overwrite with a generic description if the primary extension for the
2926 // type is in our list; these are file formats supported by Firefox and
2927 // we don't want other brands positioning themselves as the sole viewer
2928 // for a system.
2929 if (!primaryExtension.IsEmpty()) {
2930 for (const char* ext : descriptionOverwriteExtensions) {
2931 if (primaryExtension.Equals(ext)) {
2932 nsCOMPtr<nsIStringBundleService> bundleService =
2933 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
2934 NS_ENSURE_SUCCESS(rv, rv);
2935 nsCOMPtr<nsIStringBundle> unknownContentTypeBundle;
2936 rv = bundleService->CreateBundle(
2937 "chrome://mozapps/locale/downloads/unknownContentType.properties",
2938 getter_AddRefs(unknownContentTypeBundle));
2939 if (NS_SUCCEEDED(rv)) {
2940 nsAutoCString stringName(ext);
2941 stringName.AppendLiteral("ExtHandlerDescription");
2942 nsAutoString handlerDescription;
2943 rv = unknownContentTypeBundle->GetStringFromName(stringName.get(),
2944 handlerDescription);
2945 if (NS_SUCCEEDED(rv)) {
2946 (*_retval)->SetDescription(handlerDescription);
2949 break;
2954 if (LOG_ENABLED()) {
2955 nsAutoCString type;
2956 (*_retval)->GetMIMEType(type);
2958 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(),
2959 primaryExtension.get()));
2962 return NS_OK;
2965 NS_IMETHODIMP
2966 nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt,
2967 nsACString& aContentType) {
2968 // OK. We want to try the following sources of mimetype information, in this
2969 // order:
2970 // 1. defaultMimeEntries array
2971 // 2. OS-provided information
2972 // 3. our "extras" array
2973 // 4. Information from plugins
2974 // 5. The "ext-to-type-mapping" category
2975 // Note that, we are intentionally not looking at the handler service, because
2976 // that can be affected by websites, which leads to undesired behavior.
2978 // Early return if called with an empty extension parameter
2979 if (aFileExt.IsEmpty()) {
2980 return NS_ERROR_NOT_AVAILABLE;
2983 // First of all, check our default entries
2984 for (auto& entry : defaultMimeEntries) {
2985 if (aFileExt.LowerCaseEqualsASCII(entry.mFileExtension)) {
2986 aContentType = entry.mMimeType;
2987 return NS_OK;
2991 // Ask OS.
2992 if (GetMIMETypeFromOSForExtension(aFileExt, aContentType)) {
2993 return NS_OK;
2996 // Check extras array.
2997 bool found = GetTypeFromExtras(aFileExt, aContentType);
2998 if (found) {
2999 return NS_OK;
3002 // Let's see if an extension added something
3003 nsCOMPtr<nsICategoryManager> catMan(
3004 do_GetService("@mozilla.org/categorymanager;1"));
3005 if (catMan) {
3006 // The extension in the category entry is always stored as lowercase
3007 nsAutoCString lowercaseFileExt(aFileExt);
3008 ToLowerCase(lowercaseFileExt);
3009 // Read the MIME type from the category entry, if available
3010 nsCString type;
3011 nsresult rv =
3012 catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt, type);
3013 if (NS_SUCCEEDED(rv)) {
3014 aContentType = type;
3015 return NS_OK;
3019 return NS_ERROR_NOT_AVAILABLE;
3022 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(
3023 const nsACString& aMIMEType, const nsACString& aFileExt,
3024 nsACString& _retval) {
3025 NS_ENSURE_ARG(!aMIMEType.IsEmpty());
3027 nsCOMPtr<nsIMIMEInfo> mi;
3028 nsresult rv =
3029 GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
3030 if (NS_FAILED(rv)) return rv;
3032 return mi->GetPrimaryExtension(_retval);
3035 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(
3036 nsIURI* aURI, nsACString& aContentType) {
3037 NS_ENSURE_ARG_POINTER(aURI);
3038 nsresult rv = NS_ERROR_NOT_AVAILABLE;
3039 aContentType.Truncate();
3041 // First look for a file to use. If we have one, we just use that.
3042 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
3043 if (fileUrl) {
3044 nsCOMPtr<nsIFile> file;
3045 rv = fileUrl->GetFile(getter_AddRefs(file));
3046 if (NS_SUCCEEDED(rv)) {
3047 rv = GetTypeFromFile(file, aContentType);
3048 if (NS_SUCCEEDED(rv)) {
3049 // we got something!
3050 return rv;
3055 // Now try to get an nsIURL so we don't have to do our own parsing
3056 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
3057 if (url) {
3058 nsAutoCString ext;
3059 rv = url->GetFileExtension(ext);
3060 if (NS_FAILED(rv)) return rv;
3061 if (ext.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
3063 UnescapeFragment(ext, url, ext);
3065 return GetTypeFromExtension(ext, aContentType);
3068 // no url, let's give the raw spec a shot
3069 nsAutoCString specStr;
3070 rv = aURI->GetSpec(specStr);
3071 if (NS_FAILED(rv)) return rv;
3072 UnescapeFragment(specStr, aURI, specStr);
3074 // find the file extension (if any)
3075 int32_t extLoc = specStr.RFindChar('.');
3076 int32_t specLength = specStr.Length();
3077 if (-1 != extLoc && extLoc != specLength - 1 &&
3078 // nothing over 20 chars long can be sanely considered an
3079 // extension.... Dat dere would be just data.
3080 specLength - extLoc < 20) {
3081 return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
3084 // We found no information; say so.
3085 return NS_ERROR_NOT_AVAILABLE;
3088 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(
3089 nsIFile* aFile, nsACString& aContentType) {
3090 NS_ENSURE_ARG_POINTER(aFile);
3091 nsresult rv;
3093 // Get the Extension
3094 nsAutoString fileName;
3095 rv = aFile->GetLeafName(fileName);
3096 if (NS_FAILED(rv)) return rv;
3098 nsAutoCString fileExt;
3099 if (!fileName.IsEmpty()) {
3100 int32_t len = fileName.Length();
3101 for (int32_t i = len; i >= 0; i--) {
3102 if (fileName[i] == char16_t('.')) {
3103 CopyUTF16toUTF8(Substring(fileName, i + 1), fileExt);
3104 break;
3109 if (fileExt.IsEmpty()) return NS_ERROR_FAILURE;
3111 return GetTypeFromExtension(fileExt, aContentType);
3114 nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
3115 const nsACString& aContentType, bool aOverwriteDescription,
3116 nsIMIMEInfo* aMIMEInfo) {
3117 NS_ENSURE_ARG(aMIMEInfo);
3119 NS_ENSURE_ARG(!aContentType.IsEmpty());
3121 // Look for default entry with matching mime type.
3122 nsAutoCString MIMEType(aContentType);
3123 ToLowerCase(MIMEType);
3124 for (auto entry : extraMimeEntries) {
3125 if (MIMEType.Equals(entry.mMimeType)) {
3126 // This is the one. Set attributes appropriately.
3127 nsDependentCString extensions(entry.mFileExtensions);
3128 nsACString::const_iterator start, end;
3129 extensions.BeginReading(start);
3130 extensions.EndReading(end);
3131 while (start != end) {
3132 nsACString::const_iterator cursor = start;
3133 mozilla::Unused << FindCharInReadable(',', cursor, end);
3134 aMIMEInfo->AppendExtension(Substring(start, cursor));
3135 // If a comma was found, skip it for the next search.
3136 start = cursor != end ? ++cursor : cursor;
3139 nsAutoString desc;
3140 aMIMEInfo->GetDescription(desc);
3141 if (aOverwriteDescription || desc.IsEmpty()) {
3142 aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(entry.mDescription));
3144 return NS_OK;
3148 return NS_ERROR_NOT_AVAILABLE;
3151 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
3152 const nsACString& aExtension, nsIMIMEInfo* aMIMEInfo) {
3153 nsAutoCString type;
3154 bool found = GetTypeFromExtras(aExtension, type);
3155 if (!found) return NS_ERROR_NOT_AVAILABLE;
3156 return FillMIMEInfoForMimeTypeFromExtras(type, true, aMIMEInfo);
3159 bool nsExternalHelperAppService::MaybeReplacePrimaryExtension(
3160 const nsACString& aPrimaryExtension, nsIMIMEInfo* aMIMEInfo) {
3161 for (const auto& entry : sForbiddenPrimaryExtensions) {
3162 if (aPrimaryExtension.LowerCaseEqualsASCII(entry.mFileExtension)) {
3163 nsDependentCString mime(entry.mMimeType);
3164 for (const auto& extraEntry : extraMimeEntries) {
3165 if (mime.LowerCaseEqualsASCII(extraEntry.mMimeType)) {
3166 nsDependentCString goodExts(extraEntry.mFileExtensions);
3167 int32_t commaPos = goodExts.FindChar(',');
3168 commaPos = commaPos == kNotFound ? goodExts.Length() : commaPos;
3169 auto goodExt = Substring(goodExts, 0, commaPos);
3170 aMIMEInfo->SetPrimaryExtension(goodExt);
3171 return true;
3176 return false;
3179 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension,
3180 nsACString& aMIMEType) {
3181 NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
3183 // Look for default entry with matching extension.
3184 nsDependentCString::const_iterator start, end, iter;
3185 int32_t numEntries = ArrayLength(extraMimeEntries);
3186 for (int32_t index = 0; index < numEntries; index++) {
3187 nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
3188 extList.BeginReading(start);
3189 extList.EndReading(end);
3190 iter = start;
3191 while (start != end) {
3192 FindCharInReadable(',', iter, end);
3193 if (Substring(start, iter)
3194 .Equals(aExtension, nsCaseInsensitiveCStringComparator)) {
3195 aMIMEType = extraMimeEntries[index].mMimeType;
3196 return true;
3198 if (iter != end) {
3199 ++iter;
3201 start = iter;
3205 return false;
3208 bool nsExternalHelperAppService::GetMIMETypeFromOSForExtension(
3209 const nsACString& aExtension, nsACString& aMIMEType) {
3210 bool found = false;
3211 nsCOMPtr<nsIMIMEInfo> mimeInfo;
3212 nsresult rv =
3213 GetMIMEInfoFromOS(""_ns, aExtension, &found, getter_AddRefs(mimeInfo));
3214 return NS_SUCCEEDED(rv) && found && mimeInfo &&
3215 NS_SUCCEEDED(mimeInfo->GetMIMEType(aMIMEType));
3218 nsresult nsExternalHelperAppService::GetMIMEInfoFromOS(
3219 const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound,
3220 nsIMIMEInfo** aMIMEInfo) {
3221 *aMIMEInfo = nullptr;
3222 *aFound = false;
3223 return NS_ERROR_NOT_IMPLEMENTED;