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/. */
6
7 #include "base/basictypes.h"
8
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"
13
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"
20
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"
50
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"
60
61 #include "nsIApplicationReputation.h"
62
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
73
74 #ifdef XP_MACOSX
75 # include "nsILocalFileMac.h"
76 #endif
77
78 #include "nsPluginHost.h"
79 #include "nsEscape.h"
80
81 #include "nsIStringBundle.h" // XXX needed to localize error msgs
82 #include "nsIPrompt.h"
83
84 #include "nsITextToSubURI.h" // to unescape the filename
85
86 #include "nsDocShellCID.h"
87
88 #include "nsCRT.h"
89 #include "nsLocalHandlerApp.h"
90
91 #include "nsIRandomGenerator.h"
92
93 #include "ContentChild.h"
94 #include "nsXULAppAPI.h"
95 #include "nsPIDOMWindow.h"
96 #include "ExternalHelperAppChild.h"
97
98 #ifdef XP_WIN
99 # include "nsWindowsHelpers.h"
100 #endif
101
102 #include "mozilla/Components.h"
103 #include "mozilla/ClearOnShutdown.h"
104 #include "mozilla/Preferences.h"
105 #include "mozilla/ipc/URIUtils.h"
106
107 using namespace mozilla;
108 using namespace mozilla::ipc;
109 using namespace mozilla::dom;
110
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
118 };
119
120 LazyLogModule nsExternalHelperAppService::mLog("HelperAppService");
121
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)
130
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";
135
136 // Helper functions for Content-Disposition headers
137
138 /**
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.
144 */
UnescapeFragment(const nsACString & aFragment,nsIURI * aURI,nsAString & aResult)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);
152
153 return textToSubURI->UnEscapeURIForUI(aFragment, aResult);
154 }
155
156 /**
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.
164 */
UnescapeFragment(const nsACString & aFragment,nsIURI * aURI,nsACString & aResult)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;
171 }
172
173 /**
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.
190 */
GetFilenameAndExtensionFromChannel(nsIChannel * aChannel,nsString & aFileName,nsCString & aExtension,bool aAllowURLExtension=true)191 static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
192 nsString& aFileName,
193 nsCString& aExtension,
194 bool aAllowURLExtension = true) {
195 aExtension.Truncate();
196 /*
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
202 */
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;
210 }
211
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);
220
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);
226 }
227
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
237 }
238 }
239 }
240
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");
259 }
260 }
261
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);
269 }
270 }
271
272 return handleExternally;
273 }
274
275 /**
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.
280 *
281 * Optionally skip availability of the directory and storage.
282 */
GetDownloadDirectory(nsIFile ** _directory,bool aSkipChecks=false)283 static nsresult GetDownloadDirectory(nsIFile** _directory,
284 bool aSkipChecks = false) {
285 nsCOMPtr<nsIFile> dir;
286 #ifdef XP_MACOSX
287 // On OS X, we first try to get the users download location, if it's set.
288 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
289 case NS_FOLDER_VALUE_DESKTOP:
290 (void)NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
291 break;
292 case NS_FOLDER_VALUE_CUSTOM: {
293 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, NS_GET_IID(nsIFile),
294 getter_AddRefs(dir));
295 if (!dir) break;
296
297 // If we're not checking for availability we're done.
298 if (aSkipChecks) {
299 dir.forget(_directory);
300 return NS_OK;
301 }
302
303 // We have the directory, and now we need to make sure it exists
304 bool dirExists = false;
305 (void)dir->Exists(&dirExists);
306 if (dirExists) break;
307
308 nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
309 if (NS_FAILED(rv)) {
310 dir = nullptr;
311 break;
312 }
313 } break;
314 case NS_FOLDER_VALUE_DOWNLOADS:
315 // This is just the OS default location, so fall out
316 break;
317 }
318
319 if (!dir) {
320 // If not, we default to the OS X default download location.
321 nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
322 getter_AddRefs(dir));
323 NS_ENSURE_SUCCESS(rv, rv);
324 }
325 #elif defined(ANDROID)
326 return NS_ERROR_FAILURE;
327 #else
328 // On all other platforms, we default to the systems temporary directory.
329 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
330 NS_ENSURE_SUCCESS(rv, rv);
331
332 # if defined(XP_UNIX)
333 // Ensuring that only the current user can read the file names we end up
334 // creating. Note that Creating directories with specified permission only
335 // supported on Unix platform right now. That's why above if exists.
336
337 uint32_t permissions;
338 rv = dir->GetPermissions(&permissions);
339 NS_ENSURE_SUCCESS(rv, rv);
340
341 if (permissions != PR_IRWXU) {
342 const char* userName = PR_GetEnv("USERNAME");
343 if (!userName || !*userName) {
344 userName = PR_GetEnv("USER");
345 }
346 if (!userName || !*userName) {
347 userName = PR_GetEnv("LOGNAME");
348 }
349 if (!userName || !*userName) {
350 userName = "mozillaUser";
351 }
352
353 nsAutoString userDir;
354 userDir.AssignLiteral("mozilla_");
355 userDir.AppendASCII(userName);
356 userDir.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
357
358 int counter = 0;
359 bool pathExists;
360 nsCOMPtr<nsIFile> finalPath;
361
362 while (true) {
363 nsAutoString countedUserDir(userDir);
364 countedUserDir.AppendInt(counter, 10);
365 dir->Clone(getter_AddRefs(finalPath));
366 finalPath->Append(countedUserDir);
367
368 rv = finalPath->Exists(&pathExists);
369 NS_ENSURE_SUCCESS(rv, rv);
370
371 if (pathExists) {
372 // If this path has the right permissions, use it.
373 rv = finalPath->GetPermissions(&permissions);
374 NS_ENSURE_SUCCESS(rv, rv);
375
376 // Ensuring the path is writable by the current user.
377 bool isWritable;
378 rv = finalPath->IsWritable(&isWritable);
379 NS_ENSURE_SUCCESS(rv, rv);
380
381 if (permissions == PR_IRWXU && isWritable) {
382 dir = finalPath;
383 break;
384 }
385 }
386
387 rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
388 if (NS_SUCCEEDED(rv)) {
389 dir = finalPath;
390 break;
391 } else if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
392 // Unexpected error.
393 return rv;
394 }
395
396 counter++;
397 }
398 }
399
400 # endif
401 #endif
402
403 NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
404 dir.forget(_directory);
405 return NS_OK;
406 }
407
408 /**
409 * Structure for storing extension->type mappings.
410 * @see defaultMimeEntries
411 */
412 struct nsDefaultMimeTypeEntry {
413 const char* mMimeType;
414 const char* mFileExtension;
415 };
416
417 /**
418 * Default extension->mimetype mappings. These are not overridable.
419 * If you add types here, make sure they are lowercase, or you'll regret it.
420 */
421 static const nsDefaultMimeTypeEntry defaultMimeEntries[] = {
422 // The following are those extensions that we're asked about during startup,
423 // sorted by order used
424 {IMAGE_GIF, "gif"},
425 {TEXT_XML, "xml"},
426 {APPLICATION_RDF, "rdf"},
427 {IMAGE_PNG, "png"},
428 // -- end extensions used during startup
429 {TEXT_CSS, "css"},
430 {IMAGE_JPEG, "jpeg"},
431 {IMAGE_JPEG, "jpg"},
432 {IMAGE_SVG_XML, "svg"},
433 {TEXT_HTML, "html"},
434 {TEXT_HTML, "htm"},
435 {APPLICATION_XPINSTALL, "xpi"},
436 {"application/xhtml+xml", "xhtml"},
437 {"application/xhtml+xml", "xht"},
438 {TEXT_PLAIN, "txt"},
439 {APPLICATION_JSON, "json"},
440 {APPLICATION_XJAVASCRIPT, "js"},
441 {APPLICATION_XJAVASCRIPT, "jsm"},
442 {VIDEO_OGG, "ogv"},
443 {VIDEO_OGG, "ogg"},
444 {APPLICATION_OGG, "ogg"},
445 {AUDIO_OGG, "oga"},
446 {AUDIO_OGG, "opus"},
447 {APPLICATION_PDF, "pdf"},
448 {VIDEO_WEBM, "webm"},
449 {AUDIO_WEBM, "webm"},
450 {IMAGE_ICO, "ico"},
451 {TEXT_PLAIN, "properties"},
452 {TEXT_PLAIN, "locale"},
453 {TEXT_PLAIN, "ftl"},
454 #if defined(MOZ_WMF)
455 {VIDEO_MP4, "mp4"},
456 {AUDIO_MP4, "m4a"},
457 {AUDIO_MP3, "mp3"},
458 #endif
459 #ifdef MOZ_RAW
460 {VIDEO_RAW, "yuv"}
461 #endif
462 };
463
464 /**
465 * This is a small private struct used to help us initialize some
466 * default mime types.
467 */
468 struct nsExtraMimeTypeEntry {
469 const char* mMimeType;
470 const char* mFileExtensions;
471 const char* mDescription;
472 };
473
474 /**
475 * This table lists all of the 'extra' content types that we can deduce from
476 * particular file extensions. These entries also ensure that we provide a good
477 * descriptive name when we encounter files with these content types and/or
478 * extensions. These can be overridden by user helper app prefs. If you add
479 * types here, make sure they are lowercase, or you'll regret it.
480 */
481 static const nsExtraMimeTypeEntry extraMimeEntries[] = {
482 #if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to
483 // look that up...
484 {APPLICATION_OCTET_STREAM, "exe,com", "Binary File"},
485 #else
486 {APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File"},
487 #endif
488 {APPLICATION_GZIP2, "gz", "gzip"},
489 {"application/x-arj", "arj", "ARJ file"},
490 {"application/rtf", "rtf", "Rich Text Format File"},
491 {APPLICATION_ZIP, "zip", "ZIP Archive"},
492 {APPLICATION_XPINSTALL, "xpi", "XPInstall Install"},
493 {APPLICATION_PDF, "pdf", "Portable Document Format"},
494 {APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File"},
495 {APPLICATION_XJAVASCRIPT, "js", "Javascript Source File"},
496 {APPLICATION_XJAVASCRIPT, "jsm,mjs", "Javascript Module Source File"},
497 #ifdef MOZ_WIDGET_ANDROID
498 {"application/vnd.android.package-archive", "apk", "Android Package"},
499 #endif
500
501 // OpenDocument formats
502 {"application/vnd.oasis.opendocument.text", "odt", "OpenDocument Text"},
503 {"application/vnd.oasis.opendocument.presentation", "odp",
504 "OpenDocument Presentation"},
505 {"application/vnd.oasis.opendocument.spreadsheet", "ods",
506 "OpenDocument Spreadsheet"},
507 {"application/vnd.oasis.opendocument.graphics", "odg",
508 "OpenDocument Graphics"},
509
510 // Legacy Microsoft Office
511 {"application/msword", "doc", "Microsoft Word"},
512 {"application/vnd.ms-powerpoint", "ppt", "Microsoft PowerPoint"},
513 {"application/vnd.ms-excel", "xls", "Microsoft Excel"},
514
515 // Office Open XML
516 {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
517 "docx", "Microsoft Word (Open XML)"},
518 {"application/"
519 "vnd.openxmlformats-officedocument.presentationml.presentation",
520 "pptx", "Microsoft PowerPoint (Open XML)"},
521 {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
522 "xlsx", "Microsoft Excel (Open XML)"},
523
524 // Note: if you add new image types, please also update the list in
525 // contentAreaUtils.js to match.
526 {IMAGE_ART, "art", "ART Image"},
527 {IMAGE_BMP, "bmp", "BMP Image"},
528 {IMAGE_GIF, "gif", "GIF Image"},
529 {IMAGE_ICO, "ico,cur", "ICO Image"},
530 {IMAGE_JPEG, "jpg,jpeg,jfif,pjpeg,pjp", "JPEG Image"},
531 {IMAGE_PNG, "png", "PNG Image"},
532 {IMAGE_APNG, "apng", "APNG Image"},
533 {IMAGE_TIFF, "tiff,tif", "TIFF Image"},
534 {IMAGE_XBM, "xbm", "XBM Image"},
535 {IMAGE_SVG_XML, "svg", "Scalable Vector Graphics"},
536 {IMAGE_WEBP, "webp", "WebP Image"},
537 {IMAGE_AVIF, "avif", "AV1 Image File"},
538 {IMAGE_JXL, "jxl", "JPEG XL Image File"},
539
540 {MESSAGE_RFC822, "eml", "RFC-822 data"},
541 {TEXT_PLAIN, "txt,text", "Text File"},
542 {APPLICATION_JSON, "json", "JavaScript Object Notation"},
543 {TEXT_VTT, "vtt", "Web Video Text Tracks"},
544 {TEXT_CACHE_MANIFEST, "appcache", "Application Cache Manifest"},
545 {TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language"},
546 {"application/xhtml+xml", "xhtml,xht",
547 "Extensible HyperText Markup Language"},
548 {APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language"},
549 {APPLICATION_RDF, "rdf", "Resource Description Framework"},
550 {"text/csv", "csv", "CSV File"},
551 {TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language"},
552 {TEXT_CSS, "css", "Style Sheet"},
553 {TEXT_VCARD, "vcf,vcard", "Contact Information"},
554 {TEXT_CALENDAR, "ics,ical,ifb,icalendar", "iCalendar"},
555 {VIDEO_OGG, "ogv", "Ogg Video"},
556 {VIDEO_OGG, "ogg", "Ogg Video"},
557 {APPLICATION_OGG, "ogg", "Ogg Video"},
558 {AUDIO_OGG, "oga", "Ogg Audio"},
559 {AUDIO_OGG, "opus", "Opus Audio"},
560 {VIDEO_WEBM, "webm", "Web Media Video"},
561 {AUDIO_WEBM, "webm", "Web Media Audio"},
562 {AUDIO_MP3, "mp3", "MPEG Audio"},
563 {VIDEO_MP4, "mp4", "MPEG-4 Video"},
564 {AUDIO_MP4, "m4a", "MPEG-4 Audio"},
565 {VIDEO_RAW, "yuv", "Raw YUV Video"},
566 {AUDIO_WAV, "wav", "Waveform Audio"},
567 {VIDEO_3GPP, "3gpp,3gp", "3GPP Video"},
568 {VIDEO_3GPP2, "3g2", "3GPP2 Video"},
569 {AUDIO_AAC, "aac", "AAC Audio"},
570 {AUDIO_FLAC, "flac", "FLAC Audio"},
571 {AUDIO_MIDI, "mid", "Standard MIDI Audio"},
572 {APPLICATION_WASM, "wasm", "WebAssembly Module"}};
573
574 static const nsDefaultMimeTypeEntry sForbiddenPrimaryExtensions[] = {
575 {IMAGE_JPEG, "jfif"}};
576
577 /**
578 * File extensions for which decoding should be disabled.
579 * NOTE: These MUST be lower-case and ASCII.
580 */
581 static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
582 {APPLICATION_GZIP, "gz"},
583 {APPLICATION_GZIP, "tgz"},
584 {APPLICATION_ZIP, "zip"},
585 {APPLICATION_COMPRESS, "z"},
586 {APPLICATION_GZIP, "svgz"}};
587
588 /**
589 * Mimetypes for which we enforce using a known extension.
590 *
591 * In addition to this list, we do this for all audio/, video/ and
592 * image/ mimetypes.
593 */
594 static const char* forcedExtensionMimetypes[] = {
595 // Note: zip and json mimetypes are commonly used with a variety of
596 // extensions; don't add them here. It's a similar story for text/xml,
597 // but slightly worse because we can use it when sniffing for a mimetype
598 // if one hasn't been provided, so don't re-add that here either.
599 APPLICATION_PDF, APPLICATION_OGG, APPLICATION_WASM,
600 TEXT_CALENDAR, TEXT_CSS, TEXT_VCARD};
601
602 /**
603 * Primary extensions of types whose descriptions should be overwritten.
604 * This extension is concatenated with "ExtHandlerDescription" to look up the
605 * description in unknownContentType.properties.
606 * NOTE: These MUST be lower-case and ASCII.
607 */
608 static const char* descriptionOverwriteExtensions[] = {
609 "avif", "jxl", "pdf", "svg", "webp", "xml",
610 };
611
612 static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
613
614 /**
615 * On Mac child processes, return an nsOSHelperAppServiceChild for remoting
616 * OS calls to the parent process. On all other platforms use
617 * nsOSHelperAppService.
618 */
619 /* static */
620 already_AddRefed<nsExternalHelperAppService>
GetSingleton()621 nsExternalHelperAppService::GetSingleton() {
622 if (!sExtHelperAppSvcSingleton) {
623 #ifdef XP_MACOSX
624 if (XRE_IsParentProcess()) {
625 sExtHelperAppSvcSingleton = new nsOSHelperAppService();
626 } else {
627 sExtHelperAppSvcSingleton = new nsOSHelperAppServiceChild();
628 }
629 #else
630 sExtHelperAppSvcSingleton = new nsOSHelperAppService();
631 #endif /* XP_MACOSX */
632 ClearOnShutdown(&sExtHelperAppSvcSingleton);
633 }
634
635 return do_AddRef(sExtHelperAppSvcSingleton);
636 }
637
NS_IMPL_ISUPPORTS(nsExternalHelperAppService,nsIExternalHelperAppService,nsPIExternalAppLauncher,nsIExternalProtocolService,nsIMIMEService,nsIObserver,nsISupportsWeakReference)638 NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
639 nsPIExternalAppLauncher, nsIExternalProtocolService,
640 nsIMIMEService, nsIObserver, nsISupportsWeakReference)
641
642 nsExternalHelperAppService::nsExternalHelperAppService() {}
Init()643 nsresult nsExternalHelperAppService::Init() {
644 // Add an observer for profile change
645 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
646 if (!obs) return NS_ERROR_FAILURE;
647
648 nsresult rv = obs->AddObserver(this, "profile-before-change", true);
649 NS_ENSURE_SUCCESS(rv, rv);
650 return obs->AddObserver(this, "last-pb-context-exited", true);
651 }
652
~nsExternalHelperAppService()653 nsExternalHelperAppService::~nsExternalHelperAppService() {}
654
DoContentContentProcessHelper(const nsACString & aMimeContentType,nsIRequest * aRequest,BrowsingContext * aContentContext,bool aForceSave,nsIInterfaceRequestor * aWindowContext,nsIStreamListener ** aStreamListener)655 nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
656 const nsACString& aMimeContentType, nsIRequest* aRequest,
657 BrowsingContext* aContentContext, bool aForceSave,
658 nsIInterfaceRequestor* aWindowContext,
659 nsIStreamListener** aStreamListener) {
660 // We need to get a hold of a ContentChild so that we can begin forwarding
661 // this data to the parent. In the HTTP case, this is unfortunate, since
662 // we're actually passing data from parent->child->parent wastefully, but
663 // the Right Fix will eventually be to short-circuit those channels on the
664 // parent side based on some sort of subscription concept.
665 using mozilla::dom::ContentChild;
666 using mozilla::dom::ExternalHelperAppChild;
667 ContentChild* child = ContentChild::GetSingleton();
668 if (!child) {
669 return NS_ERROR_FAILURE;
670 }
671
672 nsCString disp;
673 nsCOMPtr<nsIURI> uri;
674 int64_t contentLength = -1;
675 bool wasFileChannel = false;
676 uint32_t contentDisposition = -1;
677 nsAutoString fileName;
678 nsCOMPtr<nsILoadInfo> loadInfo;
679
680 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
681 if (channel) {
682 channel->GetURI(getter_AddRefs(uri));
683 channel->GetContentLength(&contentLength);
684 channel->GetContentDisposition(&contentDisposition);
685 channel->GetContentDispositionFilename(fileName);
686 channel->GetContentDispositionHeader(disp);
687 loadInfo = channel->LoadInfo();
688
689 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aRequest));
690 wasFileChannel = fileChan != nullptr;
691 }
692
693 nsCOMPtr<nsIURI> referrer;
694 NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
695
696 Maybe<mozilla::net::LoadInfoArgs> loadInfoArgs;
697 MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
698
699 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aRequest));
700 // Determine whether a new window was opened specifically for this request
701 bool shouldCloseWindow = false;
702 if (props) {
703 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns,
704 &shouldCloseWindow);
705 }
706
707 // Now we build a protocol for forwarding our data to the parent. The
708 // protocol will act as a listener on the child-side and create a "real"
709 // helperAppService listener on the parent-side, via another call to
710 // DoContent.
711 RefPtr<ExternalHelperAppChild> childListener = new ExternalHelperAppChild();
712 MOZ_ALWAYS_TRUE(child->SendPExternalHelperAppConstructor(
713 childListener, uri, loadInfoArgs, nsCString(aMimeContentType), disp,
714 contentDisposition, fileName, aForceSave, contentLength, wasFileChannel,
715 referrer, aContentContext, shouldCloseWindow));
716
717 NS_ADDREF(*aStreamListener = childListener);
718
719 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
720
721 RefPtr<nsExternalAppHandler> handler =
722 new nsExternalAppHandler(nullptr, ""_ns, aContentContext, aWindowContext,
723 this, fileName, reason, aForceSave);
724 if (!handler) {
725 return NS_ERROR_OUT_OF_MEMORY;
726 }
727
728 childListener->SetHandler(handler);
729 return NS_OK;
730 }
731
CreateListener(const nsACString & aMimeContentType,nsIRequest * aRequest,BrowsingContext * aContentContext,bool aForceSave,nsIInterfaceRequestor * aWindowContext,nsIStreamListener ** aStreamListener)732 NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
733 const nsACString& aMimeContentType, nsIRequest* aRequest,
734 BrowsingContext* aContentContext, bool aForceSave,
735 nsIInterfaceRequestor* aWindowContext,
736 nsIStreamListener** aStreamListener) {
737 MOZ_ASSERT(!XRE_IsContentProcess());
738
739 nsAutoString fileName;
740 nsAutoCString fileExtension;
741 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
742 uint32_t contentDisposition = -1;
743
744 // Get the file extension and name that we will need later
745 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
746 nsCOMPtr<nsIURI> uri;
747 int64_t contentLength = -1;
748 if (channel) {
749 channel->GetURI(getter_AddRefs(uri));
750 channel->GetContentLength(&contentLength);
751 channel->GetContentDisposition(&contentDisposition);
752 channel->GetContentDispositionFilename(fileName);
753
754 // Check if we have a POST request, in which case we don't want to use
755 // the url's extension
756 bool allowURLExt = !net::ChannelIsPost(channel);
757
758 // Check if we had a query string - we don't want to check the URL
759 // extension if a query is present in the URI
760 // If we already know we don't want to check the URL extension, don't
761 // bother checking the query
762 if (uri && allowURLExt) {
763 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
764
765 if (url) {
766 nsAutoCString query;
767
768 // We only care about the query for HTTP and HTTPS URLs
769 if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
770 url->GetQuery(query);
771 }
772
773 // Only get the extension if the query is empty; if it isn't, then the
774 // extension likely belongs to a cgi script and isn't helpful
775 allowURLExt = query.IsEmpty();
776 }
777 }
778 // Extract name & extension
779 bool isAttachment = GetFilenameAndExtensionFromChannel(
780 channel, fileName, fileExtension, allowURLExt);
781 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
782 fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
783 isAttachment));
784 if (isAttachment) {
785 reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
786 }
787 }
788
789 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
790 PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
791
792 // We get the mime service here even though we're the default implementation
793 // of it, so it's possible to override only the mime service and not need to
794 // reimplement the whole external helper app service itself.
795 nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
796 NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
797
798 // Try to find a mime object by looking at the mime type/extension
799 nsCOMPtr<nsIMIMEInfo> mimeInfo;
800 if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
801 nsCaseInsensitiveCStringComparator)) {
802 nsAutoCString mimeType;
803 if (!fileExtension.IsEmpty()) {
804 mimeSvc->GetFromTypeAndExtension(""_ns, fileExtension,
805 getter_AddRefs(mimeInfo));
806 if (mimeInfo) {
807 mimeInfo->GetMIMEType(mimeType);
808
809 LOG(("OS-Provided mime type '%s' for extension '%s'\n", mimeType.get(),
810 fileExtension.get()));
811 }
812 }
813
814 if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
815 // Extension lookup gave us no useful match
816 mimeSvc->GetFromTypeAndExtension(
817 nsLiteralCString(APPLICATION_OCTET_STREAM), fileExtension,
818 getter_AddRefs(mimeInfo));
819 mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
820 }
821
822 if (channel) {
823 channel->SetContentType(mimeType);
824 }
825
826 // Don't overwrite SERVERREQUEST
827 if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
828 reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
829 }
830 } else {
831 mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
832 getter_AddRefs(mimeInfo));
833 }
834 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
835
836 // No mimeinfo -> we can't continue. probably OOM.
837 if (!mimeInfo) {
838 return NS_ERROR_OUT_OF_MEMORY;
839 }
840
841 *aStreamListener = nullptr;
842 // We want the mimeInfo's primary extension to pass it to
843 // nsExternalAppHandler
844 nsAutoCString buf;
845 mimeInfo->GetPrimaryExtension(buf);
846
847 // NB: ExternalHelperAppParent depends on this listener always being an
848 // nsExternalAppHandler. If this changes, make sure to update that code.
849 nsExternalAppHandler* handler =
850 new nsExternalAppHandler(mimeInfo, buf, aContentContext, aWindowContext,
851 this, fileName, reason, aForceSave);
852 if (!handler) {
853 return NS_ERROR_OUT_OF_MEMORY;
854 }
855
856 NS_ADDREF(*aStreamListener = handler);
857 return NS_OK;
858 }
859
DoContent(const nsACString & aMimeContentType,nsIRequest * aRequest,nsIInterfaceRequestor * aContentContext,bool aForceSave,nsIInterfaceRequestor * aWindowContext,nsIStreamListener ** aStreamListener)860 NS_IMETHODIMP nsExternalHelperAppService::DoContent(
861 const nsACString& aMimeContentType, nsIRequest* aRequest,
862 nsIInterfaceRequestor* aContentContext, bool aForceSave,
863 nsIInterfaceRequestor* aWindowContext,
864 nsIStreamListener** aStreamListener) {
865 // Scripted interface requestors cannot return an instance of the
866 // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so
867 // get to the window via `nsIDOMWindow`. Unfortunately, at that point we
868 // don't know whether the thing we got is an inner or outer window, so have to
869 // work with either one.
870 RefPtr<BrowsingContext> bc;
871 nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(aContentContext);
872 if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_QueryInterface(domWindow)) {
873 bc = outerWindow->GetBrowsingContext();
874 } else if (nsCOMPtr<nsPIDOMWindowInner> innerWindow =
875 do_QueryInterface(domWindow)) {
876 bc = innerWindow->GetBrowsingContext();
877 }
878
879 if (XRE_IsContentProcess()) {
880 return DoContentContentProcessHelper(aMimeContentType, aRequest, bc,
881 aForceSave, aWindowContext,
882 aStreamListener);
883 }
884
885 nsresult rv = CreateListener(aMimeContentType, aRequest, bc, aForceSave,
886 aWindowContext, aStreamListener);
887 return rv;
888 }
889
ApplyDecodingForExtension(const nsACString & aExtension,const nsACString & aEncodingType,bool * aApplyDecoding)890 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
891 const nsACString& aExtension, const nsACString& aEncodingType,
892 bool* aApplyDecoding) {
893 *aApplyDecoding = true;
894 uint32_t i;
895 for (i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
896 if (aExtension.LowerCaseEqualsASCII(
897 nonDecodableExtensions[i].mFileExtension) &&
898 aEncodingType.LowerCaseEqualsASCII(
899 nonDecodableExtensions[i].mMimeType)) {
900 *aApplyDecoding = false;
901 break;
902 }
903 }
904 return NS_OK;
905 }
906
GetFileTokenForPath(const char16_t * aPlatformAppPath,nsIFile ** aFile)907 nsresult nsExternalHelperAppService::GetFileTokenForPath(
908 const char16_t* aPlatformAppPath, nsIFile** aFile) {
909 nsDependentString platformAppPath(aPlatformAppPath);
910 // First, check if we have an absolute path
911 nsIFile* localFile = nullptr;
912 nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
913 if (NS_SUCCEEDED(rv)) {
914 *aFile = localFile;
915 bool exists;
916 if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
917 NS_RELEASE(*aFile);
918 return NS_ERROR_FILE_NOT_FOUND;
919 }
920 return NS_OK;
921 }
922
923 // Second, check if file exists in mozilla program directory
924 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
925 if (NS_SUCCEEDED(rv)) {
926 rv = (*aFile)->Append(platformAppPath);
927 if (NS_SUCCEEDED(rv)) {
928 bool exists = false;
929 rv = (*aFile)->Exists(&exists);
930 if (NS_SUCCEEDED(rv) && exists) return NS_OK;
931 }
932 NS_RELEASE(*aFile);
933 }
934
935 return NS_ERROR_NOT_AVAILABLE;
936 }
937
938 //////////////////////////////////////////////////////////////////////////////////////////////////////
939 // begin external protocol service default implementation...
940 //////////////////////////////////////////////////////////////////////////////////////////////////////
ExternalProtocolHandlerExists(const char * aProtocolScheme,bool * aHandlerExists)941 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(
942 const char* aProtocolScheme, bool* aHandlerExists) {
943 nsCOMPtr<nsIHandlerInfo> handlerInfo;
944 nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
945 getter_AddRefs(handlerInfo));
946 if (NS_SUCCEEDED(rv)) {
947 // See if we have any known possible handler apps for this
948 nsCOMPtr<nsIMutableArray> possibleHandlers;
949 handlerInfo->GetPossibleApplicationHandlers(
950 getter_AddRefs(possibleHandlers));
951
952 uint32_t length;
953 possibleHandlers->GetLength(&length);
954 if (length) {
955 *aHandlerExists = true;
956 return NS_OK;
957 }
958 }
959
960 // if not, fall back on an os-based handler
961 return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
962 }
963
IsExposedProtocol(const char * aProtocolScheme,bool * aResult)964 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(
965 const char* aProtocolScheme, bool* aResult) {
966 // check the per protocol setting first. it always takes precedence.
967 // if not set, then use the global setting.
968
969 nsAutoCString prefName("network.protocol-handler.expose.");
970 prefName += aProtocolScheme;
971 bool val;
972 if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
973 *aResult = val;
974 return NS_OK;
975 }
976
977 // by default, no protocol is exposed. i.e., by default all link clicks must
978 // go through the external protocol service. most applications override this
979 // default behavior.
980 *aResult = Preferences::GetBool("network.protocol-handler.expose-all", false);
981
982 return NS_OK;
983 }
984
985 static const char kExternalProtocolPrefPrefix[] =
986 "network.protocol-handler.external.";
987 static const char kExternalProtocolDefaultPref[] =
988 "network.protocol-handler.external-default";
989
990 // static
EscapeURI(nsIURI * aURI,nsIURI ** aResult)991 nsresult nsExternalHelperAppService::EscapeURI(nsIURI* aURI, nsIURI** aResult) {
992 MOZ_ASSERT(aURI);
993 MOZ_ASSERT(aResult);
994
995 nsAutoCString spec;
996 aURI->GetSpec(spec);
997
998 if (spec.Find("%00") != -1) return NS_ERROR_MALFORMED_URI;
999
1000 nsAutoCString escapedSpec;
1001 nsresult rv = NS_EscapeURL(spec, esc_AlwaysCopy | esc_ExtHandler, escapedSpec,
1002 fallible);
1003 NS_ENSURE_SUCCESS(rv, rv);
1004
1005 nsCOMPtr<nsIIOService> ios(do_GetIOService());
1006 return ios->NewURI(escapedSpec, nullptr, nullptr, aResult);
1007 }
1008
1009 NS_IMETHODIMP
LoadURI(nsIURI * aURI,nsIPrincipal * aTriggeringPrincipal,nsIPrincipal * aRedirectPrincipal,BrowsingContext * aBrowsingContext,bool aTriggeredExternally)1010 nsExternalHelperAppService::LoadURI(nsIURI* aURI,
1011 nsIPrincipal* aTriggeringPrincipal,
1012 nsIPrincipal* aRedirectPrincipal,
1013 BrowsingContext* aBrowsingContext,
1014 bool aTriggeredExternally) {
1015 NS_ENSURE_ARG_POINTER(aURI);
1016
1017 if (XRE_IsContentProcess()) {
1018 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
1019 aURI, aTriggeringPrincipal, aRedirectPrincipal, aBrowsingContext,
1020 aTriggeredExternally);
1021 return NS_OK;
1022 }
1023
1024 nsCOMPtr<nsIURI> escapedURI;
1025 nsresult rv = EscapeURI(aURI, getter_AddRefs(escapedURI));
1026 NS_ENSURE_SUCCESS(rv, rv);
1027
1028 nsAutoCString scheme;
1029 escapedURI->GetScheme(scheme);
1030 if (scheme.IsEmpty()) return NS_OK; // must have a scheme
1031
1032 // Deny load if the prefs say to do so
1033 nsAutoCString externalPref(kExternalProtocolPrefPrefix);
1034 externalPref += scheme;
1035 bool allowLoad = false;
1036 if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
1037 // no scheme-specific value, check the default
1038 if (NS_FAILED(
1039 Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) {
1040 return NS_OK; // missing default pref
1041 }
1042 }
1043
1044 if (!allowLoad) {
1045 return NS_OK; // explicitly denied
1046 }
1047
1048 // Now check if the principal is allowed to access the navigated context.
1049 // We allow navigating subframes, even if not same-origin - non-external
1050 // links can always navigate everywhere, so this is a minor additional
1051 // restriction, only aiming to prevent some types of spoofing attacks
1052 // from otherwise disjoint browsingcontext trees.
1053 if (aBrowsingContext && aTriggeringPrincipal &&
1054 !StaticPrefs::security_allow_disjointed_external_uri_loads() &&
1055 // Add-on principals are always allowed:
1056 !BasePrincipal::Cast(aTriggeringPrincipal)->AddonPolicy() &&
1057 // As is chrome code:
1058 !aTriggeringPrincipal->IsSystemPrincipal()) {
1059 RefPtr<BrowsingContext> bc = aBrowsingContext;
1060 WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal();
1061 bool foundAccessibleFrame = false;
1062
1063 // Also allow this load if the target is a toplevel BC and contains a
1064 // non-web-controlled about:blank document
1065 if (bc->IsTop() && !bc->HadOriginalOpener() && wgp) {
1066 RefPtr<nsIURI> uri = wgp->GetDocumentURI();
1067 foundAccessibleFrame =
1068 uri && uri->GetSpecOrDefault().EqualsLiteral("about:blank");
1069 }
1070
1071 while (!foundAccessibleFrame) {
1072 if (wgp) {
1073 foundAccessibleFrame =
1074 aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
1075 }
1076 // We have to get the parent via the bc, because there may not
1077 // be a window global for the innermost bc; see bug 1650162.
1078 BrowsingContext* parent = bc->GetParent();
1079 if (!parent) {
1080 break;
1081 }
1082 bc = parent;
1083 wgp = parent->Canonical()->GetCurrentWindowGlobal();
1084 }
1085
1086 if (!foundAccessibleFrame) {
1087 // See if this navigation could have come from a subframe.
1088 nsTArray<RefPtr<BrowsingContext>> contexts;
1089 aBrowsingContext->GetAllBrowsingContextsInSubtree(contexts);
1090 for (const auto& kid : contexts) {
1091 wgp = kid->Canonical()->GetCurrentWindowGlobal();
1092 if (wgp && aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal())) {
1093 foundAccessibleFrame = true;
1094 break;
1095 }
1096 }
1097 }
1098
1099 if (!foundAccessibleFrame) {
1100 return NS_OK; // deny the load.
1101 }
1102 }
1103
1104 nsCOMPtr<nsIHandlerInfo> handler;
1105 rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
1106 NS_ENSURE_SUCCESS(rv, rv);
1107
1108 nsCOMPtr<nsIContentDispatchChooser> chooser =
1109 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
1110 NS_ENSURE_SUCCESS(rv, rv);
1111
1112 return chooser->HandleURI(
1113 handler, escapedURI,
1114 aRedirectPrincipal ? aRedirectPrincipal : aTriggeringPrincipal,
1115 aBrowsingContext, aTriggeredExternally);
1116 }
1117
1118 //////////////////////////////////////////////////////////////////////////////////////////////////////
1119 // Methods related to deleting temporary files on exit
1120 //////////////////////////////////////////////////////////////////////////////////////////////////////
1121
1122 /* static */
DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,nsCOMArray<nsIFile> & aFileList)1123 nsresult nsExternalHelperAppService::DeleteTemporaryFileHelper(
1124 nsIFile* aTemporaryFile, nsCOMArray<nsIFile>& aFileList) {
1125 bool isFile = false;
1126
1127 // as a safety measure, make sure the nsIFile is really a file and not a
1128 // directory object.
1129 aTemporaryFile->IsFile(&isFile);
1130 if (!isFile) return NS_OK;
1131
1132 aFileList.AppendObject(aTemporaryFile);
1133
1134 return NS_OK;
1135 }
1136
1137 NS_IMETHODIMP
DeleteTemporaryFileOnExit(nsIFile * aTemporaryFile)1138 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) {
1139 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
1140 }
1141
1142 NS_IMETHODIMP
DeleteTemporaryPrivateFileWhenPossible(nsIFile * aTemporaryFile)1143 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1144 nsIFile* aTemporaryFile) {
1145 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
1146 }
1147
ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> & fileList)1148 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1149 nsCOMArray<nsIFile>& fileList) {
1150 int32_t numEntries = fileList.Count();
1151 nsIFile* localFile;
1152 for (int32_t index = 0; index < numEntries; index++) {
1153 localFile = fileList[index];
1154 if (localFile) {
1155 // First make the file writable, since the temp file is probably readonly.
1156 localFile->SetPermissions(0600);
1157 localFile->Remove(false);
1158 }
1159 }
1160
1161 fileList.Clear();
1162 }
1163
ExpungeTemporaryFiles()1164 void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1165 ExpungeTemporaryFilesHelper(mTemporaryFilesList);
1166 }
1167
ExpungeTemporaryPrivateFiles()1168 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1169 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
1170 }
1171
1172 static const char kExternalWarningPrefPrefix[] =
1173 "network.protocol-handler.warn-external.";
1174 static const char kExternalWarningDefaultPref[] =
1175 "network.protocol-handler.warn-external-default";
1176
1177 NS_IMETHODIMP
GetProtocolHandlerInfo(const nsACString & aScheme,nsIHandlerInfo ** aHandlerInfo)1178 nsExternalHelperAppService::GetProtocolHandlerInfo(
1179 const nsACString& aScheme, nsIHandlerInfo** aHandlerInfo) {
1180 // XXX enterprise customers should be able to turn this support off with a
1181 // single master pref (maybe use one of the "exposed" prefs here?)
1182
1183 bool exists;
1184 nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
1185 if (NS_FAILED(rv)) {
1186 // Either it knows nothing, or we ran out of memory
1187 return NS_ERROR_FAILURE;
1188 }
1189
1190 nsCOMPtr<nsIHandlerService> handlerSvc =
1191 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1192 if (handlerSvc) {
1193 bool hasHandler = false;
1194 (void)handlerSvc->Exists(*aHandlerInfo, &hasHandler);
1195 if (hasHandler) {
1196 rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, ""_ns);
1197 if (NS_SUCCEEDED(rv)) return NS_OK;
1198 }
1199 }
1200
1201 return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
1202 }
1203
1204 NS_IMETHODIMP
SetProtocolHandlerDefaults(nsIHandlerInfo * aHandlerInfo,bool aOSHandlerExists)1205 nsExternalHelperAppService::SetProtocolHandlerDefaults(
1206 nsIHandlerInfo* aHandlerInfo, bool aOSHandlerExists) {
1207 // this type isn't in our database, so we've only got an OS default handler,
1208 // if one exists
1209
1210 if (aOSHandlerExists) {
1211 // we've got a default, so use it
1212 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
1213
1214 // whether or not to ask the user depends on the warning preference
1215 nsAutoCString scheme;
1216 aHandlerInfo->GetType(scheme);
1217
1218 nsAutoCString warningPref(kExternalWarningPrefPrefix);
1219 warningPref += scheme;
1220 bool warn;
1221 if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
1222 // no scheme-specific value, check the default
1223 warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
1224 }
1225 aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
1226 } else {
1227 // If no OS default existed, we set the preferred action to alwaysAsk.
1228 // This really means not initialized (i.e. there's no available handler)
1229 // to all the code...
1230 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
1231 }
1232
1233 return NS_OK;
1234 }
1235
1236 // XPCOM profile change observer
1237 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * someData)1238 nsExternalHelperAppService::Observe(nsISupports* aSubject, const char* aTopic,
1239 const char16_t* someData) {
1240 if (!strcmp(aTopic, "profile-before-change")) {
1241 ExpungeTemporaryFiles();
1242 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1243 ExpungeTemporaryPrivateFiles();
1244 }
1245 return NS_OK;
1246 }
1247
1248 //////////////////////////////////////////////////////////////////////////////////////////////////////
1249 // begin external app handler implementation
1250 //////////////////////////////////////////////////////////////////////////////////////////////////////
1251
1252 NS_IMPL_ADDREF(nsExternalAppHandler)
NS_IMPL_RELEASE(nsExternalAppHandler)1253 NS_IMPL_RELEASE(nsExternalAppHandler)
1254
1255 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
1256 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
1257 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
1258 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
1259 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
1260 NS_INTERFACE_MAP_ENTRY(nsICancelable)
1261 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
1262 NS_INTERFACE_MAP_ENTRY(nsINamed)
1263 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsExternalAppHandler)
1264 NS_INTERFACE_MAP_END
1265
1266 nsExternalAppHandler::nsExternalAppHandler(
1267 nsIMIMEInfo* aMIMEInfo, const nsACString& aTempFileExtension,
1268 BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext,
1269 nsExternalHelperAppService* aExtProtSvc,
1270 const nsAString& aSuggestedFilename, uint32_t aReason, bool aForceSave)
1271 : mMimeInfo(aMIMEInfo),
1272 mBrowsingContext(aBrowsingContext),
1273 mWindowContext(aWindowContext),
1274 mSuggestedFileName(aSuggestedFilename),
1275 mForceSave(aForceSave),
1276 mCanceled(false),
1277 mStopRequestIssued(false),
1278 mIsFileChannel(false),
1279 mShouldCloseWindow(false),
1280 mHandleInternally(false),
1281 mReason(aReason),
1282 mTempFileIsExecutable(false),
1283 mTimeDownloadStarted(0),
1284 mContentLength(-1),
1285 mProgress(0),
1286 mSaver(nullptr),
1287 mDialogProgressListener(nullptr),
1288 mTransfer(nullptr),
1289 mRequest(nullptr),
1290 mExtProtSvc(aExtProtSvc) {
1291 // make sure the extention includes the '.'
1292 if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
1293 mTempFileExtension = char16_t('.');
1294 AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
1295
1296 // Get mSuggestedFileName's current file extension.
1297 nsAutoString originalFileExt;
1298 int32_t pos = mSuggestedFileName.RFindChar('.');
1299 if (pos != kNotFound) {
1300 mSuggestedFileName.Right(originalFileExt,
1301 mSuggestedFileName.Length() - pos);
1302 }
1303
1304 // replace platform specific path separator and illegal characters to avoid
1305 // any confusion.
1306 // Try to keep the use of spaces or underscores in sync with the Downloads
1307 // code sanitization in DownloadPaths.jsm
1308 mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
1309 mSuggestedFileName.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
1310 mSuggestedFileName.ReplaceChar(char16_t(0), '_');
1311 mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
1312 mTempFileExtension.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
1313
1314 // Remove unsafe bidi characters which might have spoofing implications (bug
1315 // 511521).
1316 const char16_t unsafeBidiCharacters[] = {
1317 char16_t(0x061c), // Arabic Letter Mark
1318 char16_t(0x200e), // Left-to-Right Mark
1319 char16_t(0x200f), // Right-to-Left Mark
1320 char16_t(0x202a), // Left-to-Right Embedding
1321 char16_t(0x202b), // Right-to-Left Embedding
1322 char16_t(0x202c), // Pop Directional Formatting
1323 char16_t(0x202d), // Left-to-Right Override
1324 char16_t(0x202e), // Right-to-Left Override
1325 char16_t(0x2066), // Left-to-Right Isolate
1326 char16_t(0x2067), // Right-to-Left Isolate
1327 char16_t(0x2068), // First Strong Isolate
1328 char16_t(0x2069), // Pop Directional Isolate
1329 char16_t(0)};
1330 mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
1331 mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
1332
1333 // Remove trailing or leading spaces that we may have generated while
1334 // sanitizing.
1335 mSuggestedFileName.CompressWhitespace();
1336 mTempFileExtension.CompressWhitespace();
1337
1338 EnsureCorrectExtension(originalFileExt);
1339
1340 mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
1341 }
1342
~nsExternalAppHandler()1343 nsExternalAppHandler::~nsExternalAppHandler() {
1344 MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
1345 }
1346
ShouldForceExtension(const nsString & aFileExt)1347 bool nsExternalAppHandler::ShouldForceExtension(const nsString& aFileExt) {
1348 nsAutoCString MIMEType;
1349 if (!mMimeInfo || NS_FAILED(mMimeInfo->GetMIMEType(MIMEType))) {
1350 return false;
1351 }
1352
1353 bool canForce = StringBeginsWith(MIMEType, "image/"_ns) ||
1354 StringBeginsWith(MIMEType, "audio/"_ns) ||
1355 StringBeginsWith(MIMEType, "video/"_ns);
1356
1357 if (!canForce &&
1358 StaticPrefs::browser_download_sanitize_non_media_extensions()) {
1359 for (const char* mime : forcedExtensionMimetypes) {
1360 if (MIMEType.Equals(mime)) {
1361 canForce = true;
1362 break;
1363 }
1364 }
1365 }
1366 if (!canForce) {
1367 return false;
1368 }
1369
1370 // If we get here, we know for sure the mimetype allows us to overwrite the
1371 // existing extension, if it's wrong. Return whether the extension is wrong:
1372
1373 bool knownExtension = false;
1374 // Note that aFileExt is either empty or consists of an extension
1375 // *including the dot* which we remove for ExtensionExists().
1376 return (
1377 aFileExt.IsEmpty() || aFileExt.EqualsLiteral(".") ||
1378 (NS_SUCCEEDED(mMimeInfo->ExtensionExists(
1379 Substring(NS_ConvertUTF16toUTF8(aFileExt), 1), &knownExtension)) &&
1380 !knownExtension));
1381 }
1382
EnsureCorrectExtension(const nsString & aFileExt)1383 void nsExternalAppHandler::EnsureCorrectExtension(const nsString& aFileExt) {
1384 // If we don't have an extension (which will include the .),
1385 // just short-circuit.
1386 if (mTempFileExtension.Length() <= 1) {
1387 return;
1388 }
1389
1390 // After removing trailing whitespaces from the name, if we have a
1391 // temp file extension, there are broadly 2 cases where we want to
1392 // replace the extension.
1393 // First, if the file extension contains invalid characters.
1394 // Second, for document type mimetypes, if the extension is either
1395 // missing or not valid for this mimetype.
1396 bool replaceExtension =
1397 (aFileExt.FindCharInSet(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS) !=
1398 kNotFound) ||
1399 ShouldForceExtension(aFileExt);
1400
1401 if (replaceExtension) {
1402 int32_t pos = mSuggestedFileName.RFindChar('.');
1403 if (pos != kNotFound) {
1404 mSuggestedFileName =
1405 Substring(mSuggestedFileName, 0, pos) + mTempFileExtension;
1406 } else {
1407 mSuggestedFileName.Append(mTempFileExtension);
1408 }
1409 }
1410
1411 /*
1412 * Ensure we don't double-append the file extension if it matches:
1413 */
1414 if (replaceExtension ||
1415 aFileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator)) {
1416 // Matches -> mTempFileExtension can be empty
1417 mTempFileExtension.Truncate();
1418 }
1419 }
1420
DidDivertRequest(nsIRequest * request)1421 void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
1422 MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1423 // Remove our request from the child loadGroup
1424 RetargetLoadNotifications(request);
1425 }
1426
SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)1427 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(
1428 nsIWebProgressListener2* aWebProgressListener) {
1429 // This is always called by nsHelperDlg.js. Go ahead and register the
1430 // progress listener. At this point, we don't have mTransfer.
1431 mDialogProgressListener = aWebProgressListener;
1432 return NS_OK;
1433 }
1434
GetTargetFile(nsIFile ** aTarget)1435 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) {
1436 if (mFinalFileDestination)
1437 *aTarget = mFinalFileDestination;
1438 else
1439 *aTarget = mTempFile;
1440
1441 NS_IF_ADDREF(*aTarget);
1442 return NS_OK;
1443 }
1444
GetTargetFileIsExecutable(bool * aExec)1445 NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec) {
1446 // Use the real target if it's been set
1447 if (mFinalFileDestination) return mFinalFileDestination->IsExecutable(aExec);
1448
1449 // Otherwise, use the stored executable-ness of the temporary
1450 *aExec = mTempFileIsExecutable;
1451 return NS_OK;
1452 }
1453
GetTimeDownloadStarted(PRTime * aTime)1454 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) {
1455 *aTime = mTimeDownloadStarted;
1456 return NS_OK;
1457 }
1458
GetContentLength(int64_t * aContentLength)1459 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t* aContentLength) {
1460 *aContentLength = mContentLength;
1461 return NS_OK;
1462 }
1463
GetBrowsingContextId(uint64_t * aBrowsingContextId)1464 NS_IMETHODIMP nsExternalAppHandler::GetBrowsingContextId(
1465 uint64_t* aBrowsingContextId) {
1466 *aBrowsingContextId = mBrowsingContext ? mBrowsingContext->Id() : 0;
1467 return NS_OK;
1468 }
1469
RetargetLoadNotifications(nsIRequest * request)1470 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest* request) {
1471 // we are going to run the downloading of the helper app in our own little
1472 // docloader / load group context. so go ahead and force the creation of a
1473 // load group and doc loader for us to use...
1474 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1475 if (!aChannel) return;
1476
1477 bool isPrivate = NS_UsePrivateBrowsing(aChannel);
1478
1479 nsCOMPtr<nsILoadGroup> oldLoadGroup;
1480 aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
1481
1482 if (oldLoadGroup) {
1483 oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
1484 }
1485
1486 aChannel->SetLoadGroup(nullptr);
1487 aChannel->SetNotificationCallbacks(nullptr);
1488
1489 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
1490 if (pbChannel) {
1491 pbChannel->SetPrivate(isPrivate);
1492 }
1493 }
1494
SetUpTempFile(nsIChannel * aChannel)1495 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
1496 // First we need to try to get the destination directory for the temporary
1497 // file.
1498 nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
1499 NS_ENSURE_SUCCESS(rv, rv);
1500
1501 // At this point, we do not have a filename for the temp file. For security
1502 // purposes, this cannot be predictable, so we must use a cryptographic
1503 // quality PRNG to generate one.
1504 // We will request raw random bytes, and transform that to a base64 string,
1505 // as all characters from the base64 set are acceptable for filenames. For
1506 // each three bytes of random data, we will get four bytes of ASCII. Request
1507 // a bit more, to be safe, and truncate to the length we want in the end.
1508
1509 const uint32_t wantedFileNameLength = 8;
1510 const uint32_t requiredBytesLength =
1511 static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
1512
1513 nsCOMPtr<nsIRandomGenerator> rg =
1514 do_GetService("@mozilla.org/security/random-generator;1", &rv);
1515 NS_ENSURE_SUCCESS(rv, rv);
1516
1517 uint8_t* buffer;
1518 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
1519 NS_ENSURE_SUCCESS(rv, rv);
1520
1521 nsAutoCString tempLeafName;
1522 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
1523 requiredBytesLength);
1524 rv = Base64Encode(randomData, tempLeafName);
1525 free(buffer);
1526 buffer = nullptr;
1527 NS_ENSURE_SUCCESS(rv, rv);
1528
1529 tempLeafName.Truncate(wantedFileNameLength);
1530
1531 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1532 // to replace illegal characters -- notably '/'
1533 tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1534
1535 // now append our extension.
1536 nsAutoCString ext;
1537 mMimeInfo->GetPrimaryExtension(ext);
1538 if (!ext.IsEmpty()) {
1539 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1540 if (ext.First() != '.') tempLeafName.Append('.');
1541 tempLeafName.Append(ext);
1542 }
1543
1544 // We need to temporarily create a dummy file with the correct
1545 // file extension to determine the executable-ness, so do this before adding
1546 // the extra .part extension.
1547 nsCOMPtr<nsIFile> dummyFile;
1548 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
1549 NS_ENSURE_SUCCESS(rv, rv);
1550
1551 // Set the file name without .part
1552 rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1553 NS_ENSURE_SUCCESS(rv, rv);
1554 rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1555 NS_ENSURE_SUCCESS(rv, rv);
1556
1557 // Store executable-ness then delete
1558 dummyFile->IsExecutable(&mTempFileIsExecutable);
1559 dummyFile->Remove(false);
1560
1561 // Add an additional .part to prevent the OS from running this file in the
1562 // default application.
1563 tempLeafName.AppendLiteral(".part");
1564
1565 rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1566 // make this file unique!!!
1567 NS_ENSURE_SUCCESS(rv, rv);
1568 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1569 NS_ENSURE_SUCCESS(rv, rv);
1570
1571 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1572 // This is a bit broken in the case when createUnique actually had to append
1573 // some numbers, because then we now have a filename like foo.bar-1.part and
1574 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1575 // this. But the other options are all bad too.... Ideally we'd have a way
1576 // to tell createUnique to put its unique marker before the extension that
1577 // comes before ".part" or something.
1578 rv = mTempFile->GetLeafName(mTempLeafName);
1579 NS_ENSURE_SUCCESS(rv, rv);
1580
1581 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, u".part"_ns),
1582 NS_ERROR_UNEXPECTED);
1583
1584 // Strip off the ".part" from mTempLeafName
1585 mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
1586
1587 MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
1588 mSaver =
1589 do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
1590 NS_ENSURE_SUCCESS(rv, rv);
1591
1592 rv = mSaver->SetObserver(this);
1593 if (NS_FAILED(rv)) {
1594 mSaver = nullptr;
1595 return rv;
1596 }
1597
1598 rv = mSaver->EnableSha256();
1599 NS_ENSURE_SUCCESS(rv, rv);
1600
1601 rv = mSaver->EnableSignatureInfo();
1602 NS_ENSURE_SUCCESS(rv, rv);
1603 LOG(("Enabled hashing and signature verification"));
1604
1605 rv = mSaver->SetTarget(mTempFile, false);
1606 NS_ENSURE_SUCCESS(rv, rv);
1607
1608 return rv;
1609 }
1610
MaybeApplyDecodingForExtension(nsIRequest * aRequest)1611 void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1612 nsIRequest* aRequest) {
1613 MOZ_ASSERT(aRequest);
1614
1615 nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
1616 if (!encChannel) {
1617 return;
1618 }
1619
1620 // Turn off content encoding conversions if needed
1621 bool applyConversion = true;
1622
1623 // First, check to see if conversion is already disabled. If so, we
1624 // have nothing to do here.
1625 encChannel->GetApplyConversion(&applyConversion);
1626 if (!applyConversion) {
1627 return;
1628 }
1629
1630 nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
1631 if (sourceURL) {
1632 nsAutoCString extension;
1633 sourceURL->GetFileExtension(extension);
1634 if (!extension.IsEmpty()) {
1635 nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
1636 encChannel->GetContentEncodings(getter_AddRefs(encEnum));
1637 if (encEnum) {
1638 bool hasMore;
1639 nsresult rv = encEnum->HasMore(&hasMore);
1640 if (NS_SUCCEEDED(rv) && hasMore) {
1641 nsAutoCString encType;
1642 rv = encEnum->GetNext(encType);
1643 if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) {
1644 MOZ_ASSERT(mExtProtSvc);
1645 mExtProtSvc->ApplyDecodingForExtension(extension, encType,
1646 &applyConversion);
1647 }
1648 }
1649 }
1650 }
1651 }
1652
1653 encChannel->SetApplyConversion(applyConversion);
1654 }
1655
1656 already_AddRefed<nsIInterfaceRequestor>
GetDialogParent()1657 nsExternalAppHandler::GetDialogParent() {
1658 nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext;
1659
1660 if (!dialogParent && mBrowsingContext) {
1661 dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
1662 }
1663 if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
1664 RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement();
1665 if (element) {
1666 dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
1667 }
1668 }
1669 return dialogParent.forget();
1670 }
1671
OnStartRequest(nsIRequest * request)1672 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
1673 MOZ_ASSERT(request, "OnStartRequest without request?");
1674
1675 // Set mTimeDownloadStarted here as the download has already started and
1676 // we want to record the start time before showing the filepicker.
1677 mTimeDownloadStarted = PR_Now();
1678
1679 mRequest = request;
1680
1681 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1682
1683 nsresult rv;
1684 nsAutoCString MIMEType;
1685 if (mMimeInfo) {
1686 mMimeInfo->GetMIMEType(MIMEType);
1687 }
1688 // Now get the URI
1689 if (aChannel) {
1690 aChannel->GetURI(getter_AddRefs(mSourceUrl));
1691 }
1692
1693 mDownloadClassification =
1694 nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType);
1695
1696 if (mDownloadClassification == nsITransfer::DOWNLOAD_FORBIDDEN) {
1697 // If the download is rated as forbidden,
1698 // cancel the request so no ui knows about this.
1699 mCanceled = true;
1700 request->Cancel(NS_ERROR_ABORT);
1701 return NS_OK;
1702 }
1703
1704 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
1705 mIsFileChannel = fileChan != nullptr;
1706 if (!mIsFileChannel) {
1707 // It's possible that this request came from the child process and the
1708 // file channel actually lives there. If this returns true, then our
1709 // mSourceUrl will be an nsIFileURL anyway.
1710 nsCOMPtr<dom::nsIExternalHelperAppParent> parent(
1711 do_QueryInterface(request));
1712 mIsFileChannel = parent && parent->WasFileChannel();
1713 }
1714
1715 // Get content length
1716 if (aChannel) {
1717 aChannel->GetContentLength(&mContentLength);
1718 }
1719
1720 if (mBrowsingContext) {
1721 mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mBrowsingContext);
1722 mMaybeCloseWindowHelper->SetShouldCloseWindow(mShouldCloseWindow);
1723 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
1724 // Determine whether a new window was opened specifically for this request
1725 if (props) {
1726 bool tmp = false;
1727 if (NS_SUCCEEDED(
1728 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp))) {
1729 mMaybeCloseWindowHelper->SetShouldCloseWindow(tmp);
1730 }
1731 }
1732 }
1733
1734 // retarget all load notifications to our docloader instead of the original
1735 // window's docloader...
1736 RetargetLoadNotifications(request);
1737
1738 // Close the underlying DOMWindow if it was opened specifically for the
1739 // download. We don't run this in the content process, since we have
1740 // an instance running in the parent as well, which will handle this
1741 // if needed.
1742 if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper) {
1743 mBrowsingContext = mMaybeCloseWindowHelper->MaybeCloseWindow();
1744 }
1745
1746 // In an IPC setting, we're allowing the child process, here, to make
1747 // decisions about decoding the channel (e.g. decompression). It will
1748 // still forward the decoded (uncompressed) data back to the parent.
1749 // Con: Uncompressed data means more IPC overhead.
1750 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1751 // Parent process doesn't need to expect CPU time on decompression.
1752 MaybeApplyDecodingForExtension(aChannel);
1753
1754 // At this point, the child process has done everything it can usefully do
1755 // for OnStartRequest.
1756 if (XRE_IsContentProcess()) {
1757 return NS_OK;
1758 }
1759
1760 rv = SetUpTempFile(aChannel);
1761 if (NS_FAILED(rv)) {
1762 nsresult transferError = rv;
1763
1764 rv = CreateFailedTransfer();
1765 if (NS_FAILED(rv)) {
1766 LOG(
1767 ("Failed to create transfer to report failure."
1768 "Will fallback to prompter!"));
1769 }
1770
1771 mCanceled = true;
1772 request->Cancel(transferError);
1773
1774 nsAutoString path;
1775 if (mTempFile) mTempFile->GetPath(path);
1776
1777 SendStatusChange(kWriteError, transferError, request, path);
1778
1779 return NS_OK;
1780 }
1781
1782 // Inform channel it is open on behalf of a download to throttle it during
1783 // page loads and prevent its caching.
1784 nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
1785 if (httpInternal) {
1786 rv = httpInternal->SetChannelIsForDownload(true);
1787 MOZ_ASSERT(NS_SUCCEEDED(rv));
1788 }
1789
1790 if (mSourceUrl->SchemeIs("data")) {
1791 // In case we're downloading a data:// uri
1792 // we don't want to apply AllowTopLevelNavigationToDataURI.
1793 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
1794 loadInfo->SetForceAllowDataURI(true);
1795 }
1796
1797 // now that the temp file is set up, find out if we need to invoke a dialog
1798 // asking the user what they want us to do with this content...
1799
1800 // We can get here for three reasons: "can't handle", "sniffed type", or
1801 // "server sent content-disposition:attachment". In the first case we want
1802 // to honor the user's "always ask" pref; in the other two cases we want to
1803 // honor it only if the default action is "save". Opening attachments in
1804 // helper apps by default breaks some websites (especially if the attachment
1805 // is one part of a multipart document). Opening sniffed content in helper
1806 // apps by default introduces security holes that we'd rather not have.
1807
1808 // So let's find out whether the user wants to be prompted. If he does not,
1809 // check mReason and the preferred action to see what we should do.
1810
1811 bool alwaysAsk = true;
1812
1813 // Skip showing UnknownContentType dialog, unless it is explicitly
1814 // set in preferences.
1815 bool skipShowingDialog =
1816 Preferences::GetBool("browser.download.useDownloadDir") &&
1817 StaticPrefs::browser_download_improvements_to_download_panel();
1818
1819 if (skipShowingDialog) {
1820 alwaysAsk = false;
1821 } else {
1822 mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
1823 }
1824
1825 if (alwaysAsk) {
1826 // But we *don't* ask if this mimeInfo didn't come from
1827 // our user configuration datastore and the user has said
1828 // at some point in the distant past that they don't
1829 // want to be asked. The latter fact would have been
1830 // stored in pref strings back in the old days.
1831
1832 bool mimeTypeIsInDatastore = false;
1833 nsCOMPtr<nsIHandlerService> handlerSvc =
1834 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1835 if (handlerSvc) {
1836 handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
1837 }
1838 if (!handlerSvc || !mimeTypeIsInDatastore) {
1839 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF,
1840 MIMEType.get())) {
1841 // Don't need to ask after all.
1842 alwaysAsk = false;
1843 // Make sure action matches pref (save to disk).
1844 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1845 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF,
1846 MIMEType.get())) {
1847 // Don't need to ask after all.
1848 alwaysAsk = false;
1849 }
1850 }
1851 } else if (MIMEType.EqualsLiteral("text/plain")) {
1852 nsAutoCString ext;
1853 mMimeInfo->GetPrimaryExtension(ext);
1854 // If people are sending us ApplicationReputation-eligible files with
1855 // text/plain mimetypes, enforce asking the user what to do.
1856 if (!ext.IsEmpty()) {
1857 nsAutoCString dummyFileName("f");
1858 if (ext.First() != '.') {
1859 dummyFileName.Append(".");
1860 }
1861 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1862 nsCOMPtr<nsIApplicationReputationService> appRep =
1863 components::ApplicationReputation::Service();
1864 appRep->IsBinary(dummyFileName + ext, &alwaysAsk);
1865 }
1866 }
1867
1868 int32_t action = nsIMIMEInfo::saveToDisk;
1869 mMimeInfo->GetPreferredAction(&action);
1870
1871 bool forcePrompt =
1872 mReason == nsIHelperAppLauncherDialog::REASON_TYPESNIFFED ||
1873 (mReason == nsIHelperAppLauncherDialog::REASON_SERVERREQUEST &&
1874 !skipShowingDialog);
1875
1876 // OK, now check why we're here
1877 if (!alwaysAsk && forcePrompt) {
1878 // Force asking if we're not saving. See comment back when we fetched the
1879 // alwaysAsk boolean for details.
1880 alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
1881 }
1882
1883 bool shouldAutomaticallyHandleInternally =
1884 action == nsIMIMEInfo::handleInternally &&
1885 StaticPrefs::browser_download_improvements_to_download_panel();
1886
1887 // If we're not asking, check we actually know what to do:
1888 if (!alwaysAsk) {
1889 alwaysAsk = action != nsIMIMEInfo::saveToDisk &&
1890 action != nsIMIMEInfo::useHelperApp &&
1891 action != nsIMIMEInfo::useSystemDefault &&
1892 !shouldAutomaticallyHandleInternally;
1893 }
1894
1895 // if we were told that we _must_ save to disk without asking, all the stuff
1896 // before this is irrelevant; override it
1897 if (mForceSave) {
1898 alwaysAsk = false;
1899 action = nsIMIMEInfo::saveToDisk;
1900 }
1901 // Additionally, if we are asked by the OS to open a local file,
1902 // automatically downloading it to create a second copy of that file doesn't
1903 // really make sense. We should ask the user what they want to do.
1904 if (mSourceUrl->SchemeIs("file") && !alwaysAsk &&
1905 action == nsIMIMEInfo::saveToDisk) {
1906 alwaysAsk = true;
1907 }
1908 if (alwaysAsk) {
1909 // Display the dialog
1910 mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
1911 NS_ENSURE_SUCCESS(rv, rv);
1912
1913 // this will create a reference cycle (the dialog holds a reference to us as
1914 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1915 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
1916 rv = mDialog->Show(this, dialogParent, mReason);
1917
1918 // what do we do if the dialog failed? I guess we should call Cancel and
1919 // abort the load....
1920 } else {
1921 // We need to do the save/open immediately, then.
1922 #ifdef XP_WIN
1923 /* We need to see whether the file we've got here could be
1924 * executable. If it could, we had better not try to open it!
1925 * We can skip this check, though, if we have a setting to open in a
1926 * helper app.
1927 * This code mirrors the code in
1928 * nsExternalAppHandler::LaunchWithApplication so that what we
1929 * test here is as close as possible to what will really be
1930 * happening if we decide to execute
1931 */
1932 nsCOMPtr<nsIHandlerApp> prefApp;
1933 mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
1934 if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
1935 nsCOMPtr<nsIFile> fileToTest;
1936 GetTargetFile(getter_AddRefs(fileToTest));
1937 if (fileToTest) {
1938 bool isExecutable;
1939 rv = fileToTest->IsExecutable(&isExecutable);
1940 if (NS_FAILED(rv) ||
1941 isExecutable) { // checking NS_FAILED, because paranoia is good
1942 action = nsIMIMEInfo::saveToDisk;
1943 }
1944 } else { // Paranoia is good here too, though this really should not
1945 // happen
1946 NS_WARNING(
1947 "GetDownloadInfo returned a null file after the temp file has been "
1948 "set up! ");
1949 action = nsIMIMEInfo::saveToDisk;
1950 }
1951 }
1952
1953 #endif
1954 if (action == nsIMIMEInfo::useHelperApp ||
1955 action == nsIMIMEInfo::useSystemDefault ||
1956 shouldAutomaticallyHandleInternally) {
1957 rv = LaunchWithApplication(shouldAutomaticallyHandleInternally);
1958 } else {
1959 rv = PromptForSaveDestination();
1960 }
1961 }
1962 return NS_OK;
1963 }
1964
1965 // Convert error info into proper message text and send OnStatusChange
1966 // notification to the dialog progress listener or nsITransfer implementation.
SendStatusChange(ErrorType type,nsresult rv,nsIRequest * aRequest,const nsString & path)1967 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
1968 nsIRequest* aRequest,
1969 const nsString& path) {
1970 const char* msgId = nullptr;
1971 switch (rv) {
1972 case NS_ERROR_OUT_OF_MEMORY:
1973 // No memory
1974 msgId = "noMemory";
1975 break;
1976
1977 case NS_ERROR_FILE_NO_DEVICE_SPACE:
1978 // Out of space on target volume.
1979 msgId = "diskFull";
1980 break;
1981
1982 case NS_ERROR_FILE_READ_ONLY:
1983 // Attempt to write to read/only file.
1984 msgId = "readOnly";
1985 break;
1986
1987 case NS_ERROR_FILE_ACCESS_DENIED:
1988 if (type == kWriteError) {
1989 // Attempt to write without sufficient permissions.
1990 #if defined(ANDROID)
1991 // On Android this means the SD card is present but
1992 // unavailable (read-only).
1993 msgId = "SDAccessErrorCardReadOnly";
1994 #else
1995 msgId = "accessError";
1996 #endif
1997 } else {
1998 msgId = "launchError";
1999 }
2000 break;
2001
2002 case NS_ERROR_FILE_NOT_FOUND:
2003 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
2004 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
2005 // Helper app not found, let's verify this happened on launch
2006 if (type == kLaunchError) {
2007 msgId = "helperAppNotFound";
2008 break;
2009 }
2010 #if defined(ANDROID)
2011 else if (type == kWriteError) {
2012 // On Android this means the SD card is missing (not in
2013 // SD slot).
2014 msgId = "SDAccessErrorCardMissing";
2015 break;
2016 }
2017 #endif
2018 [[fallthrough]];
2019
2020 default:
2021 // Generic read/write/launch error message.
2022 switch (type) {
2023 case kReadError:
2024 msgId = "readError";
2025 break;
2026 case kWriteError:
2027 msgId = "writeError";
2028 break;
2029 case kLaunchError:
2030 msgId = "launchError";
2031 break;
2032 }
2033 break;
2034 }
2035
2036 MOZ_LOG(
2037 nsExternalHelperAppService::mLog, LogLevel::Error,
2038 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32 "\n",
2039 msgId, type, mDialogProgressListener.get(), mTransfer.get(),
2040 static_cast<uint32_t>(rv)));
2041
2042 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
2043 (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
2044
2045 // Get properties file bundle and extract status string.
2046 nsCOMPtr<nsIStringBundleService> stringService =
2047 mozilla::components::StringBundle::Service();
2048 if (stringService) {
2049 nsCOMPtr<nsIStringBundle> bundle;
2050 if (NS_SUCCEEDED(stringService->CreateBundle(
2051 "chrome://global/locale/nsWebBrowserPersist.properties",
2052 getter_AddRefs(bundle)))) {
2053 nsAutoString msgText;
2054 AutoTArray<nsString, 1> strings = {path};
2055 if (NS_SUCCEEDED(bundle->FormatStringFromName(msgId, strings, msgText))) {
2056 if (mDialogProgressListener) {
2057 // We have a listener, let it handle the error.
2058 mDialogProgressListener->OnStatusChange(
2059 nullptr, (type == kReadError) ? aRequest : nullptr, rv,
2060 msgText.get());
2061 } else if (mTransfer) {
2062 mTransfer->OnStatusChange(nullptr,
2063 (type == kReadError) ? aRequest : nullptr,
2064 rv, msgText.get());
2065 } else if (XRE_IsParentProcess()) {
2066 // We don't have a listener. Simply show the alert ourselves.
2067 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
2068 nsresult qiRv;
2069 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(dialogParent, &qiRv));
2070 nsAutoString title;
2071 bundle->FormatStringFromName("title", strings, title);
2072
2073 MOZ_LOG(
2074 nsExternalHelperAppService::mLog, LogLevel::Debug,
2075 ("mBrowsingContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
2076 ", title='%s', msg='%s'",
2077 mBrowsingContext.get(), prompter.get(),
2078 static_cast<uint32_t>(qiRv), NS_ConvertUTF16toUTF8(title).get(),
2079 NS_ConvertUTF16toUTF8(msgText).get()));
2080
2081 // If we didn't have a prompter we will try and get a window
2082 // instead, get it's docshell and use it to alert the user.
2083 if (!prompter) {
2084 nsCOMPtr<nsPIDOMWindowOuter> window(do_GetInterface(dialogParent));
2085 if (!window || !window->GetDocShell()) {
2086 return;
2087 }
2088
2089 prompter = do_GetInterface(window->GetDocShell(), &qiRv);
2090
2091 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
2092 ("No prompter from mBrowsingContext, using DocShell, "
2093 "window=0x%p, docShell=0x%p, "
2094 "prompter=0x%p, qi rv=0x%08" PRIX32,
2095 window.get(), window->GetDocShell(), prompter.get(),
2096 static_cast<uint32_t>(qiRv)));
2097
2098 // If we still don't have a prompter, there's nothing else we
2099 // can do so just return.
2100 if (!prompter) {
2101 MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
2102 ("No prompter from DocShell, no way to alert user"));
2103 return;
2104 }
2105 }
2106
2107 // We should always have a prompter at this point.
2108 prompter->Alert(title.get(), msgText.get());
2109 }
2110 }
2111 }
2112 }
2113 }
2114
2115 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)2116 nsExternalAppHandler::OnDataAvailable(nsIRequest* request,
2117 nsIInputStream* inStr,
2118 uint64_t sourceOffset, uint32_t count) {
2119 nsresult rv = NS_OK;
2120 // first, check to see if we've been canceled....
2121 if (mCanceled || !mSaver) {
2122 // then go cancel our underlying channel too
2123 return request->Cancel(NS_BINDING_ABORTED);
2124 }
2125
2126 // read the data out of the stream and write it to the temp file.
2127 if (count > 0) {
2128 mProgress += count;
2129
2130 nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
2131 rv = saver->OnDataAvailable(request, inStr, sourceOffset, count);
2132 if (NS_SUCCEEDED(rv)) {
2133 // Send progress notification.
2134 if (mTransfer) {
2135 mTransfer->OnProgressChange64(nullptr, request, mProgress,
2136 mContentLength, mProgress,
2137 mContentLength);
2138 }
2139 } else {
2140 // An error occurred, notify listener.
2141 nsAutoString tempFilePath;
2142 if (mTempFile) {
2143 mTempFile->GetPath(tempFilePath);
2144 }
2145 SendStatusChange(kReadError, rv, request, tempFilePath);
2146
2147 // Cancel the download.
2148 Cancel(rv);
2149 }
2150 }
2151 return rv;
2152 }
2153
OnStopRequest(nsIRequest * request,nsresult aStatus)2154 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest* request,
2155 nsresult aStatus) {
2156 LOG(
2157 ("nsExternalAppHandler::OnStopRequest\n"
2158 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08" PRIX32 "\n",
2159 mCanceled, mTransfer.get(), static_cast<uint32_t>(aStatus)));
2160
2161 mStopRequestIssued = true;
2162
2163 // Cancel if the request did not complete successfully.
2164 if (!mCanceled && NS_FAILED(aStatus)) {
2165 // Send error notification.
2166 nsAutoString tempFilePath;
2167 if (mTempFile) mTempFile->GetPath(tempFilePath);
2168 SendStatusChange(kReadError, aStatus, request, tempFilePath);
2169
2170 Cancel(aStatus);
2171 }
2172
2173 // first, check to see if we've been canceled....
2174 if (mCanceled || !mSaver) {
2175 return NS_OK;
2176 }
2177
2178 return mSaver->Finish(NS_OK);
2179 }
2180
2181 NS_IMETHODIMP
OnTargetChange(nsIBackgroundFileSaver * aSaver,nsIFile * aTarget)2182 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver* aSaver,
2183 nsIFile* aTarget) {
2184 return NS_OK;
2185 }
2186
2187 NS_IMETHODIMP
OnSaveComplete(nsIBackgroundFileSaver * aSaver,nsresult aStatus)2188 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
2189 nsresult aStatus) {
2190 LOG(
2191 ("nsExternalAppHandler::OnSaveComplete\n"
2192 " aSaver=0x%p, aStatus=0x%08" PRIX32 ", mCanceled=%d, mTransfer=0x%p\n",
2193 aSaver, static_cast<uint32_t>(aStatus), mCanceled, mTransfer.get()));
2194
2195 if (!mCanceled) {
2196 // Save the hash and signature information
2197 (void)mSaver->GetSha256Hash(mHash);
2198 (void)mSaver->GetSignatureInfo(mSignatureInfo);
2199
2200 // Free the reference that the saver keeps on us, even if we couldn't get
2201 // the hash.
2202 mSaver = nullptr;
2203
2204 // Save the redirect information.
2205 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2206 if (channel) {
2207 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2208 nsresult rv = NS_OK;
2209 nsCOMPtr<nsIMutableArray> redirectChain =
2210 do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
2211 NS_ENSURE_SUCCESS(rv, rv);
2212 LOG(("nsExternalAppHandler: Got %zu redirects\n",
2213 loadInfo->RedirectChain().Length()));
2214 for (nsIRedirectHistoryEntry* entry : loadInfo->RedirectChain()) {
2215 redirectChain->AppendElement(entry);
2216 }
2217 mRedirects = redirectChain;
2218 }
2219
2220 if (NS_FAILED(aStatus)) {
2221 nsAutoString path;
2222 mTempFile->GetPath(path);
2223
2224 // It may happen when e10s is enabled that there will be no transfer
2225 // object available to communicate status as expected by the system.
2226 // Let's try and create a temporary transfer object to take care of this
2227 // for us, we'll fall back to using the prompt service if we absolutely
2228 // have to.
2229 if (!mTransfer) {
2230 // We don't care if this fails.
2231 CreateFailedTransfer();
2232 }
2233
2234 SendStatusChange(kWriteError, aStatus, nullptr, path);
2235 if (!mCanceled) Cancel(aStatus);
2236 return NS_OK;
2237 }
2238 }
2239
2240 // Notify the transfer object that we are done if the user has chosen an
2241 // action. If the user hasn't chosen an action, the progress listener
2242 // (nsITransfer) will be notified in CreateTransfer.
2243 if (mTransfer) {
2244 NotifyTransfer(aStatus);
2245 }
2246
2247 return NS_OK;
2248 }
2249
NotifyTransfer(nsresult aStatus)2250 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus) {
2251 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2252 MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
2253
2254 LOG(("Notifying progress listener"));
2255
2256 if (NS_SUCCEEDED(aStatus)) {
2257 (void)mTransfer->SetSha256Hash(mHash);
2258 (void)mTransfer->SetSignatureInfo(mSignatureInfo);
2259 (void)mTransfer->SetRedirects(mRedirects);
2260 (void)mTransfer->OnProgressChange64(
2261 nullptr, nullptr, mProgress, mContentLength, mProgress, mContentLength);
2262 }
2263
2264 (void)mTransfer->OnStateChange(nullptr, nullptr,
2265 nsIWebProgressListener::STATE_STOP |
2266 nsIWebProgressListener::STATE_IS_REQUEST |
2267 nsIWebProgressListener::STATE_IS_NETWORK,
2268 aStatus);
2269
2270 // This nsITransfer object holds a reference to us (we are its observer), so
2271 // we need to release the reference to break a reference cycle (and therefore
2272 // to prevent leaking). We do this even if the previous calls failed.
2273 mTransfer = nullptr;
2274 }
2275
GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)2276 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo** aMIMEInfo) {
2277 *aMIMEInfo = mMimeInfo;
2278 NS_ADDREF(*aMIMEInfo);
2279 return NS_OK;
2280 }
2281
GetSource(nsIURI ** aSourceURI)2282 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI** aSourceURI) {
2283 NS_ENSURE_ARG(aSourceURI);
2284 *aSourceURI = mSourceUrl;
2285 NS_IF_ADDREF(*aSourceURI);
2286 return NS_OK;
2287 }
2288
GetSuggestedFileName(nsAString & aSuggestedFileName)2289 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(
2290 nsAString& aSuggestedFileName) {
2291 aSuggestedFileName = mSuggestedFileName;
2292 return NS_OK;
2293 }
2294
CreateTransfer()2295 nsresult nsExternalAppHandler::CreateTransfer() {
2296 LOG(("nsExternalAppHandler::CreateTransfer"));
2297
2298 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2299 // We are back from the helper app dialog (where the user chooses to save or
2300 // open), but we aren't done processing the load. in this case, throw up a
2301 // progress dialog so the user can see what's going on.
2302 // Also, release our reference to mDialog. We don't need it anymore, and we
2303 // need to break the reference cycle.
2304 mDialog = nullptr;
2305 if (!mDialogProgressListener) {
2306 NS_WARNING("The dialog should nullify the dialog progress listener");
2307 }
2308 // In case of a non acceptable download, we need to cancel the request and
2309 // pass a FailedTransfer for the Download UI.
2310 if (mDownloadClassification != nsITransfer::DOWNLOAD_ACCEPTABLE) {
2311 mCanceled = true;
2312 mRequest->Cancel(NS_ERROR_ABORT);
2313 return CreateFailedTransfer();
2314 }
2315 nsresult rv;
2316
2317 // We must be able to create an nsITransfer object. If not, it doesn't matter
2318 // much that we can't launch the helper application or save to disk. Work on
2319 // a local copy rather than mTransfer until we know we succeeded, to make it
2320 // clearer that this function is re-entrant.
2321 nsCOMPtr<nsITransfer> transfer =
2322 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
2323 NS_ENSURE_SUCCESS(rv, rv);
2324
2325 // Initialize the download
2326 nsCOMPtr<nsIURI> target;
2327 rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
2328 NS_ENSURE_SUCCESS(rv, rv);
2329
2330 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2331 if (mBrowsingContext) {
2332 rv = transfer->InitWithBrowsingContext(
2333 mSourceUrl, target, u""_ns, mMimeInfo, mTimeDownloadStarted, mTempFile,
2334 this, channel && NS_UsePrivateBrowsing(channel),
2335 mDownloadClassification, mBrowsingContext, mHandleInternally);
2336 } else {
2337 rv = transfer->Init(mSourceUrl, target, u""_ns, mMimeInfo,
2338 mTimeDownloadStarted, mTempFile, this,
2339 channel && NS_UsePrivateBrowsing(channel),
2340 mDownloadClassification);
2341 }
2342
2343 NS_ENSURE_SUCCESS(rv, rv);
2344
2345 // If we were cancelled since creating the transfer, just return. It is
2346 // always ok to return NS_OK if we are cancelled. Callers of this function
2347 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2348 // twice.
2349 if (mCanceled) {
2350 return NS_OK;
2351 }
2352 rv = transfer->OnStateChange(nullptr, mRequest,
2353 nsIWebProgressListener::STATE_START |
2354 nsIWebProgressListener::STATE_IS_REQUEST |
2355 nsIWebProgressListener::STATE_IS_NETWORK,
2356 NS_OK);
2357 NS_ENSURE_SUCCESS(rv, rv);
2358
2359 if (mCanceled) {
2360 return NS_OK;
2361 }
2362
2363 mRequest = nullptr;
2364 // Finally, save the transfer to mTransfer.
2365 mTransfer = transfer;
2366 transfer = nullptr;
2367
2368 // While we were bringing up the progress dialog, we actually finished
2369 // processing the url. If that's the case then mStopRequestIssued will be
2370 // true and OnSaveComplete has been called.
2371 if (mStopRequestIssued && !mSaver && mTransfer) {
2372 NotifyTransfer(NS_OK);
2373 }
2374
2375 return rv;
2376 }
2377
CreateFailedTransfer()2378 nsresult nsExternalAppHandler::CreateFailedTransfer() {
2379 nsresult rv;
2380 nsCOMPtr<nsITransfer> transfer =
2381 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
2382 NS_ENSURE_SUCCESS(rv, rv);
2383
2384 // We won't pass the temp file to the transfer, so if we have one it needs to
2385 // get deleted now.
2386 if (mTempFile) {
2387 if (mSaver) {
2388 mSaver->Finish(NS_BINDING_ABORTED);
2389 mSaver = nullptr;
2390 }
2391 mTempFile->Remove(false);
2392 }
2393
2394 nsCOMPtr<nsIURI> pseudoTarget;
2395 if (!mFinalFileDestination) {
2396 // If we don't have a download directory we're kinda screwed but it's OK
2397 // we'll still report the error via the prompter.
2398 nsCOMPtr<nsIFile> pseudoFile;
2399 rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
2400 NS_ENSURE_SUCCESS(rv, rv);
2401
2402 // Append the default suggested filename. If the user restarts the transfer
2403 // we will re-trigger a filename check anyway to ensure that it is unique.
2404 rv = pseudoFile->Append(mSuggestedFileName);
2405 NS_ENSURE_SUCCESS(rv, rv);
2406
2407 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
2408 NS_ENSURE_SUCCESS(rv, rv);
2409 } else {
2410 // Initialize the target, if present
2411 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), mFinalFileDestination);
2412 NS_ENSURE_SUCCESS(rv, rv);
2413 }
2414
2415 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2416 if (mBrowsingContext) {
2417 rv = transfer->InitWithBrowsingContext(
2418 mSourceUrl, pseudoTarget, u""_ns, mMimeInfo, mTimeDownloadStarted,
2419 nullptr, this, channel && NS_UsePrivateBrowsing(channel),
2420 mDownloadClassification, mBrowsingContext, mHandleInternally);
2421 } else {
2422 rv = transfer->Init(mSourceUrl, pseudoTarget, u""_ns, mMimeInfo,
2423 mTimeDownloadStarted, nullptr, this,
2424 channel && NS_UsePrivateBrowsing(channel),
2425 mDownloadClassification);
2426 }
2427 NS_ENSURE_SUCCESS(rv, rv);
2428
2429 // Our failed transfer is ready.
2430 mTransfer = std::move(transfer);
2431
2432 return NS_OK;
2433 }
2434
SaveDestinationAvailable(nsIFile * aFile)2435 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile* aFile) {
2436 if (aFile)
2437 ContinueSave(aFile);
2438 else
2439 Cancel(NS_BINDING_ABORTED);
2440
2441 return NS_OK;
2442 }
2443
RequestSaveDestination(const nsString & aDefaultFile,const nsString & aFileExtension)2444 void nsExternalAppHandler::RequestSaveDestination(
2445 const nsString& aDefaultFile, const nsString& aFileExtension) {
2446 // Display the dialog
2447 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2448 // "AutoDownload" w/o showing a prompt
2449 nsresult rv = NS_OK;
2450 if (!mDialog) {
2451 // Get helper app launcher dialog.
2452 mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
2453 if (rv != NS_OK) {
2454 Cancel(NS_BINDING_ABORTED);
2455 return;
2456 }
2457 }
2458
2459 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we
2460 // can't unescape it because the dialog is implemented by a JS component which
2461 // doesn't have a window so no unescape routine is defined...
2462
2463 // Now, be sure to keep |this| alive, and the dialog
2464 // If we don't do this, users that close the helper app dialog while the file
2465 // picker is up would cause Cancel() to be called, and the dialog would be
2466 // released, which would release this object too, which would crash.
2467 // See Bug 249143
2468 RefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
2469 nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
2470 nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
2471
2472 rv = dlg->PromptForSaveToFileAsync(this, dialogParent, aDefaultFile.get(),
2473 aFileExtension.get(), mForceSave);
2474 if (NS_FAILED(rv)) {
2475 Cancel(NS_BINDING_ABORTED);
2476 }
2477 }
2478
2479 // PromptForSaveDestination should only be called by the helper app dialog which
2480 // allows the user to say launch with application or save to disk.
PromptForSaveDestination()2481 NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
2482 if (mCanceled) return NS_OK;
2483
2484 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
2485
2486 if (mSuggestedFileName.IsEmpty()) {
2487 RequestSaveDestination(mTempLeafName, mTempFileExtension);
2488 } else {
2489 nsAutoString fileExt;
2490 int32_t pos = mSuggestedFileName.RFindChar('.');
2491 if (pos >= 0) {
2492 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
2493 }
2494 if (fileExt.IsEmpty()) {
2495 fileExt = mTempFileExtension;
2496 }
2497
2498 RequestSaveDestination(mSuggestedFileName, fileExt);
2499 }
2500
2501 return NS_OK;
2502 }
ContinueSave(nsIFile * aNewFileLocation)2503 nsresult nsExternalAppHandler::ContinueSave(nsIFile* aNewFileLocation) {
2504 if (mCanceled) return NS_OK;
2505
2506 MOZ_ASSERT(aNewFileLocation, "Must be called with a non-null file");
2507
2508 nsresult rv = NS_OK;
2509 nsCOMPtr<nsIFile> fileToUse = aNewFileLocation;
2510 mFinalFileDestination = fileToUse;
2511
2512 // Move what we have in the final directory, but append .part
2513 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2514 // saver if we are done (Finish has been called) but OnSaverComplete has not
2515 // been called.
2516 if (mFinalFileDestination && mSaver && !mStopRequestIssued) {
2517 nsCOMPtr<nsIFile> movedFile;
2518 mFinalFileDestination->Clone(getter_AddRefs(movedFile));
2519 if (movedFile) {
2520 // Get the old leaf name and append .part to it
2521 nsAutoString name;
2522 mFinalFileDestination->GetLeafName(name);
2523 name.AppendLiteral(".part");
2524 movedFile->SetLeafName(name);
2525
2526 rv = mSaver->SetTarget(movedFile, true);
2527 if (NS_FAILED(rv)) {
2528 nsAutoString path;
2529 mTempFile->GetPath(path);
2530 SendStatusChange(kWriteError, rv, nullptr, path);
2531 Cancel(rv);
2532 return NS_OK;
2533 }
2534
2535 mTempFile = movedFile;
2536 }
2537 }
2538
2539 // The helper app dialog has told us what to do and we have a final file
2540 // destination.
2541 rv = CreateTransfer();
2542 // If we fail to create the transfer, Cancel.
2543 if (NS_FAILED(rv)) {
2544 Cancel(rv);
2545 return rv;
2546 }
2547
2548 return NS_OK;
2549 }
2550
2551 // LaunchWithApplication should only be called by the helper app dialog which
2552 // allows the user to say launch with application or save to disk.
LaunchWithApplication(bool aHandleInternally)2553 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(
2554 bool aHandleInternally) {
2555 if (mCanceled) return NS_OK;
2556
2557 mHandleInternally = aHandleInternally;
2558
2559 // Now check if the file is local, in which case we won't bother with saving
2560 // it to a temporary directory and just launch it from where it is
2561 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
2562 if (fileUrl && mIsFileChannel) {
2563 Cancel(NS_BINDING_ABORTED);
2564 nsCOMPtr<nsIFile> file;
2565 nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
2566
2567 if (NS_SUCCEEDED(rv)) {
2568 rv = mMimeInfo->LaunchWithFile(file);
2569 if (NS_SUCCEEDED(rv)) return NS_OK;
2570 }
2571 nsAutoString path;
2572 if (file) file->GetPath(path);
2573 // If we get here, an error happened
2574 SendStatusChange(kLaunchError, rv, nullptr, path);
2575 return rv;
2576 }
2577
2578 // Now that the user has elected to launch the downloaded file with a helper
2579 // app, we're justified in removing the 'salted' name. We'll rename to what
2580 // was specified in mSuggestedFileName after the download is done prior to
2581 // launching the helper app. So that any existing file of that name won't be
2582 // overwritten we call CreateUnique(). Also note that we use the same
2583 // directory as originally downloaded so the download can be renamed in place
2584 // later.
2585 nsCOMPtr<nsIFile> fileToUse;
2586 (void)GetDownloadDirectory(getter_AddRefs(fileToUse));
2587
2588 if (mSuggestedFileName.IsEmpty()) {
2589 // Keep using the leafname of the temp file, since we're just starting a
2590 // helper
2591 mSuggestedFileName = mTempLeafName;
2592 }
2593
2594 #ifdef XP_WIN
2595 fileToUse->Append(mSuggestedFileName + mTempFileExtension);
2596 #else
2597 fileToUse->Append(mSuggestedFileName);
2598 #endif
2599
2600 nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
2601 if (NS_SUCCEEDED(rv)) {
2602 mFinalFileDestination = fileToUse;
2603 // launch the progress window now that the user has picked the desired
2604 // action.
2605 rv = CreateTransfer();
2606 if (NS_FAILED(rv)) {
2607 Cancel(rv);
2608 }
2609 } else {
2610 // Cancel the download and report an error. We do not want to end up in
2611 // a state where it appears that we have a normal download that is
2612 // pointing to a file that we did not actually create.
2613 nsAutoString path;
2614 mTempFile->GetPath(path);
2615 SendStatusChange(kWriteError, rv, nullptr, path);
2616 Cancel(rv);
2617 }
2618 return rv;
2619 }
2620
Cancel(nsresult aReason)2621 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
2622 NS_ENSURE_ARG(NS_FAILED(aReason));
2623
2624 if (mCanceled) {
2625 return NS_OK;
2626 }
2627 mCanceled = true;
2628
2629 if (mSaver) {
2630 // We are still writing to the target file. Give the saver a chance to
2631 // close the target file, then notify the transfer object if necessary in
2632 // the OnSaveComplete callback.
2633 mSaver->Finish(aReason);
2634 mSaver = nullptr;
2635 } else {
2636 if (mStopRequestIssued && mTempFile) {
2637 // This branch can only happen when the user cancels the helper app dialog
2638 // when the request has completed. The temp file has to be removed here,
2639 // because mSaver has been released at that time with the temp file left.
2640 (void)mTempFile->Remove(false);
2641 }
2642
2643 // Notify the transfer object that the download has been canceled, if the
2644 // user has already chosen an action and we didn't notify already.
2645 if (mTransfer) {
2646 NotifyTransfer(aReason);
2647 }
2648 }
2649
2650 // Break our reference cycle with the helper app dialog (set up in
2651 // OnStartRequest)
2652 mDialog = nullptr;
2653
2654 mRequest = nullptr;
2655
2656 // Release the listener, to break the reference cycle with it (we are the
2657 // observer of the listener).
2658 mDialogProgressListener = nullptr;
2659
2660 return NS_OK;
2661 }
2662
GetNeverAskFlagFromPref(const char * prefName,const char * aContentType)2663 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char* prefName,
2664 const char* aContentType) {
2665 // Search the obsolete pref strings.
2666 nsAutoCString prefCString;
2667 Preferences::GetCString(prefName, prefCString);
2668 if (prefCString.IsEmpty()) {
2669 // Default is true, if not found in the pref string.
2670 return true;
2671 }
2672
2673 NS_UnescapeURL(prefCString);
2674 nsACString::const_iterator start, end;
2675 prefCString.BeginReading(start);
2676 prefCString.EndReading(end);
2677 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType), start,
2678 end);
2679 }
2680
2681 NS_IMETHODIMP
GetName(nsACString & aName)2682 nsExternalAppHandler::GetName(nsACString& aName) {
2683 aName.AssignLiteral("nsExternalAppHandler");
2684 return NS_OK;
2685 }
2686
2687 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2688 // The following section contains our nsIMIMEService implementation and related
2689 // methods.
2690 //
2691 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2692
2693 // nsIMIMEService methods
GetFromTypeAndExtension(const nsACString & aMIMEType,const nsACString & aFileExt,nsIMIMEInfo ** _retval)2694 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(
2695 const nsACString& aMIMEType, const nsACString& aFileExt,
2696 nsIMIMEInfo** _retval) {
2697 MOZ_ASSERT(!aMIMEType.IsEmpty() || !aFileExt.IsEmpty(),
2698 "Give me something to work with");
2699 MOZ_DIAGNOSTIC_ASSERT(aFileExt.FindChar('\0') == kNotFound,
2700 "The extension should never contain null characters");
2701 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2702 PromiseFlatCString(aMIMEType).get(),
2703 PromiseFlatCString(aFileExt).get()));
2704
2705 *_retval = nullptr;
2706
2707 // OK... we need a type. Get one.
2708 nsAutoCString typeToUse(aMIMEType);
2709 if (typeToUse.IsEmpty()) {
2710 nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
2711 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
2712 }
2713
2714 // We promise to only send lower case mime types to the OS
2715 ToLowerCase(typeToUse);
2716
2717 // First, ask the OS for a mime info
2718 bool found;
2719 nsresult rv = GetMIMEInfoFromOS(typeToUse, aFileExt, &found, _retval);
2720 if (NS_WARN_IF(NS_FAILED(rv))) {
2721 return rv;
2722 }
2723
2724 LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
2725 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2726 if (!*_retval) return NS_ERROR_OUT_OF_MEMORY;
2727
2728 // The handler service can make up for bad mime types by checking the file
2729 // extension. If the mime type is known (in extras or in the handler
2730 // service), we stop it doing so by flipping this bool to true.
2731 bool trustMIMEType = false;
2732
2733 // Check extras - not everything we support will be known by the OS store,
2734 // unfortunately, and it may even miss some extensions that we know should
2735 // be accepted. We only do this for non-octet-stream mimetypes, because
2736 // our information for octet-stream would lead to us trying to open all such
2737 // files as Binary file with exe, com or bin extension regardless of the
2738 // real extension.
2739 if (!typeToUse.Equals(APPLICATION_OCTET_STREAM,
2740 nsCaseInsensitiveCStringComparator)) {
2741 rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, !found, *_retval);
2742 LOG(("Searched extras (by type), rv 0x%08" PRIX32 "\n",
2743 static_cast<uint32_t>(rv)));
2744 trustMIMEType = NS_SUCCEEDED(rv);
2745 found = found || NS_SUCCEEDED(rv);
2746 }
2747
2748 // Now, let's see if we can find something in our datastore.
2749 // This will not overwrite the OS information that interests us
2750 // (i.e. default application, default app. description)
2751 nsCOMPtr<nsIHandlerService> handlerSvc =
2752 do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2753 if (handlerSvc) {
2754 bool hasHandler = false;
2755 (void)handlerSvc->Exists(*_retval, &hasHandler);
2756 if (hasHandler) {
2757 rv = handlerSvc->FillHandlerInfo(*_retval, ""_ns);
2758 LOG(("Data source: Via type: retval 0x%08" PRIx32 "\n",
2759 static_cast<uint32_t>(rv)));
2760 trustMIMEType = trustMIMEType || NS_SUCCEEDED(rv);
2761 } else {
2762 rv = NS_ERROR_NOT_AVAILABLE;
2763 }
2764
2765 found = found || NS_SUCCEEDED(rv);
2766 }
2767
2768 // If we still haven't found anything, try finding a match for
2769 // an extension in extras first:
2770 if (!found && !aFileExt.IsEmpty()) {
2771 rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
2772 LOG(("Searched extras (by ext), rv 0x%08" PRIX32 "\n",
2773 static_cast<uint32_t>(rv)));
2774 }
2775
2776 // Then check the handler service - but only do so if we really do not know
2777 // the mimetype. This avoids overwriting good mimetype info with bad file
2778 // extension info.
2779 if ((!found || !trustMIMEType) && handlerSvc && !aFileExt.IsEmpty()) {
2780 nsAutoCString overrideType;
2781 rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
2782 if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
2783 // We can't check handlerSvc->Exists() here, because we have a
2784 // overideType. That's ok, it just results in some console noise.
2785 // (If there's no handler for the override type, it throws)
2786 rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
2787 LOG(("Data source: Via ext: retval 0x%08" PRIx32 "\n",
2788 static_cast<uint32_t>(rv)));
2789 found = found || NS_SUCCEEDED(rv);
2790 }
2791 }
2792
2793 // If we still don't have a match, at least set the file description
2794 // to `${aFileExt} File` if it's empty:
2795 if (!found && !aFileExt.IsEmpty()) {
2796 // XXXzpao This should probably be localized
2797 nsAutoCString desc(aFileExt);
2798 desc.AppendLiteral(" File");
2799 (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
2800 LOG(("Falling back to 'File' file description\n"));
2801 }
2802
2803 // Sometimes, OSes give us bad data. We have a set of forbidden extensions
2804 // for some MIME types. If the primary extension is forbidden,
2805 // overwrite it with a known-good one. See bug 1571247 for context.
2806 nsAutoCString primaryExtension;
2807 (*_retval)->GetPrimaryExtension(primaryExtension);
2808 if (!primaryExtension.EqualsIgnoreCase(PromiseFlatCString(aFileExt).get())) {
2809 if (MaybeReplacePrimaryExtension(primaryExtension, *_retval)) {
2810 (*_retval)->GetPrimaryExtension(primaryExtension);
2811 }
2812 }
2813
2814 // Finally, check if we got a file extension and if yes, if it is an
2815 // extension on the mimeinfo, in which case we want it to be the primary one
2816 if (!aFileExt.IsEmpty()) {
2817 bool matches = false;
2818 (*_retval)->ExtensionExists(aFileExt, &matches);
2819 LOG(("Extension '%s' matches mime info: %i\n",
2820 PromiseFlatCString(aFileExt).get(), matches));
2821 if (matches) {
2822 nsAutoCString fileExt;
2823 ToLowerCase(aFileExt, fileExt);
2824 (*_retval)->SetPrimaryExtension(fileExt);
2825 primaryExtension = fileExt;
2826 }
2827 }
2828
2829 // Overwrite with a generic description if the primary extension for the
2830 // type is in our list; these are file formats supported by Firefox and
2831 // we don't want other brands positioning themselves as the sole viewer
2832 // for a system.
2833 if (!primaryExtension.IsEmpty()) {
2834 for (const char* ext : descriptionOverwriteExtensions) {
2835 if (primaryExtension.Equals(ext)) {
2836 nsCOMPtr<nsIStringBundleService> bundleService =
2837 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
2838 NS_ENSURE_SUCCESS(rv, rv);
2839 nsCOMPtr<nsIStringBundle> unknownContentTypeBundle;
2840 rv = bundleService->CreateBundle(
2841 "chrome://mozapps/locale/downloads/unknownContentType.properties",
2842 getter_AddRefs(unknownContentTypeBundle));
2843 if (NS_SUCCEEDED(rv)) {
2844 nsAutoCString stringName(ext);
2845 stringName.AppendLiteral("ExtHandlerDescription");
2846 nsAutoString handlerDescription;
2847 rv = unknownContentTypeBundle->GetStringFromName(stringName.get(),
2848 handlerDescription);
2849 if (NS_SUCCEEDED(rv)) {
2850 (*_retval)->SetDescription(handlerDescription);
2851 }
2852 }
2853 break;
2854 }
2855 }
2856 }
2857
2858 if (LOG_ENABLED()) {
2859 nsAutoCString type;
2860 (*_retval)->GetMIMEType(type);
2861
2862 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(),
2863 primaryExtension.get()));
2864 }
2865
2866 return NS_OK;
2867 }
2868
2869 NS_IMETHODIMP
GetTypeFromExtension(const nsACString & aFileExt,nsACString & aContentType)2870 nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt,
2871 nsACString& aContentType) {
2872 // OK. We want to try the following sources of mimetype information, in this
2873 // order:
2874 // 1. defaultMimeEntries array
2875 // 2. OS-provided information
2876 // 3. our "extras" array
2877 // 4. Information from plugins
2878 // 5. The "ext-to-type-mapping" category
2879 // Note that, we are intentionally not looking at the handler service, because
2880 // that can be affected by websites, which leads to undesired behavior.
2881
2882 // Early return if called with an empty extension parameter
2883 if (aFileExt.IsEmpty()) {
2884 return NS_ERROR_NOT_AVAILABLE;
2885 }
2886
2887 // First of all, check our default entries
2888 for (auto& entry : defaultMimeEntries) {
2889 if (aFileExt.LowerCaseEqualsASCII(entry.mFileExtension)) {
2890 aContentType = entry.mMimeType;
2891 return NS_OK;
2892 }
2893 }
2894
2895 // Ask OS.
2896 if (GetMIMETypeFromOSForExtension(aFileExt, aContentType)) {
2897 return NS_OK;
2898 }
2899
2900 // Check extras array.
2901 bool found = GetTypeFromExtras(aFileExt, aContentType);
2902 if (found) {
2903 return NS_OK;
2904 }
2905
2906 // Let's see if an extension added something
2907 nsCOMPtr<nsICategoryManager> catMan(
2908 do_GetService("@mozilla.org/categorymanager;1"));
2909 if (catMan) {
2910 // The extension in the category entry is always stored as lowercase
2911 nsAutoCString lowercaseFileExt(aFileExt);
2912 ToLowerCase(lowercaseFileExt);
2913 // Read the MIME type from the category entry, if available
2914 nsCString type;
2915 nsresult rv =
2916 catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt, type);
2917 if (NS_SUCCEEDED(rv)) {
2918 aContentType = type;
2919 return NS_OK;
2920 }
2921 }
2922
2923 return NS_ERROR_NOT_AVAILABLE;
2924 }
2925
GetPrimaryExtension(const nsACString & aMIMEType,const nsACString & aFileExt,nsACString & _retval)2926 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(
2927 const nsACString& aMIMEType, const nsACString& aFileExt,
2928 nsACString& _retval) {
2929 NS_ENSURE_ARG(!aMIMEType.IsEmpty());
2930
2931 nsCOMPtr<nsIMIMEInfo> mi;
2932 nsresult rv =
2933 GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
2934 if (NS_FAILED(rv)) return rv;
2935
2936 return mi->GetPrimaryExtension(_retval);
2937 }
2938
GetTypeFromURI(nsIURI * aURI,nsACString & aContentType)2939 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(
2940 nsIURI* aURI, nsACString& aContentType) {
2941 NS_ENSURE_ARG_POINTER(aURI);
2942 nsresult rv = NS_ERROR_NOT_AVAILABLE;
2943 aContentType.Truncate();
2944
2945 // First look for a file to use. If we have one, we just use that.
2946 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
2947 if (fileUrl) {
2948 nsCOMPtr<nsIFile> file;
2949 rv = fileUrl->GetFile(getter_AddRefs(file));
2950 if (NS_SUCCEEDED(rv)) {
2951 rv = GetTypeFromFile(file, aContentType);
2952 if (NS_SUCCEEDED(rv)) {
2953 // we got something!
2954 return rv;
2955 }
2956 }
2957 }
2958
2959 // Now try to get an nsIURL so we don't have to do our own parsing
2960 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
2961 if (url) {
2962 nsAutoCString ext;
2963 rv = url->GetFileExtension(ext);
2964 if (NS_FAILED(rv)) return rv;
2965 if (ext.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
2966
2967 UnescapeFragment(ext, url, ext);
2968
2969 return GetTypeFromExtension(ext, aContentType);
2970 }
2971
2972 // no url, let's give the raw spec a shot
2973 nsAutoCString specStr;
2974 rv = aURI->GetSpec(specStr);
2975 if (NS_FAILED(rv)) return rv;
2976 UnescapeFragment(specStr, aURI, specStr);
2977
2978 // find the file extension (if any)
2979 int32_t extLoc = specStr.RFindChar('.');
2980 int32_t specLength = specStr.Length();
2981 if (-1 != extLoc && extLoc != specLength - 1 &&
2982 // nothing over 20 chars long can be sanely considered an
2983 // extension.... Dat dere would be just data.
2984 specLength - extLoc < 20) {
2985 return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
2986 }
2987
2988 // We found no information; say so.
2989 return NS_ERROR_NOT_AVAILABLE;
2990 }
2991
GetTypeFromFile(nsIFile * aFile,nsACString & aContentType)2992 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(
2993 nsIFile* aFile, nsACString& aContentType) {
2994 NS_ENSURE_ARG_POINTER(aFile);
2995 nsresult rv;
2996
2997 // Get the Extension
2998 nsAutoString fileName;
2999 rv = aFile->GetLeafName(fileName);
3000 if (NS_FAILED(rv)) return rv;
3001
3002 nsAutoCString fileExt;
3003 if (!fileName.IsEmpty()) {
3004 int32_t len = fileName.Length();
3005 for (int32_t i = len; i >= 0; i--) {
3006 if (fileName[i] == char16_t('.')) {
3007 CopyUTF16toUTF8(Substring(fileName, i + 1), fileExt);
3008 break;
3009 }
3010 }
3011 }
3012
3013 if (fileExt.IsEmpty()) return NS_ERROR_FAILURE;
3014
3015 return GetTypeFromExtension(fileExt, aContentType);
3016 }
3017
FillMIMEInfoForMimeTypeFromExtras(const nsACString & aContentType,bool aOverwriteDescription,nsIMIMEInfo * aMIMEInfo)3018 nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
3019 const nsACString& aContentType, bool aOverwriteDescription,
3020 nsIMIMEInfo* aMIMEInfo) {
3021 NS_ENSURE_ARG(aMIMEInfo);
3022
3023 NS_ENSURE_ARG(!aContentType.IsEmpty());
3024
3025 // Look for default entry with matching mime type.
3026 nsAutoCString MIMEType(aContentType);
3027 ToLowerCase(MIMEType);
3028 for (auto entry : extraMimeEntries) {
3029 if (MIMEType.Equals(entry.mMimeType)) {
3030 // This is the one. Set attributes appropriately.
3031 nsDependentCString extensions(entry.mFileExtensions);
3032 nsACString::const_iterator start, end;
3033 extensions.BeginReading(start);
3034 extensions.EndReading(end);
3035 while (start != end) {
3036 nsACString::const_iterator cursor = start;
3037 mozilla::Unused << FindCharInReadable(',', cursor, end);
3038 aMIMEInfo->AppendExtension(Substring(start, cursor));
3039 // If a comma was found, skip it for the next search.
3040 start = cursor != end ? ++cursor : cursor;
3041 }
3042
3043 nsAutoString desc;
3044 aMIMEInfo->GetDescription(desc);
3045 if (aOverwriteDescription || desc.IsEmpty()) {
3046 aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(entry.mDescription));
3047 }
3048 return NS_OK;
3049 }
3050 }
3051
3052 return NS_ERROR_NOT_AVAILABLE;
3053 }
3054
FillMIMEInfoForExtensionFromExtras(const nsACString & aExtension,nsIMIMEInfo * aMIMEInfo)3055 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
3056 const nsACString& aExtension, nsIMIMEInfo* aMIMEInfo) {
3057 nsAutoCString type;
3058 bool found = GetTypeFromExtras(aExtension, type);
3059 if (!found) return NS_ERROR_NOT_AVAILABLE;
3060 return FillMIMEInfoForMimeTypeFromExtras(type, true, aMIMEInfo);
3061 }
3062
MaybeReplacePrimaryExtension(const nsACString & aPrimaryExtension,nsIMIMEInfo * aMIMEInfo)3063 bool nsExternalHelperAppService::MaybeReplacePrimaryExtension(
3064 const nsACString& aPrimaryExtension, nsIMIMEInfo* aMIMEInfo) {
3065 for (const auto& entry : sForbiddenPrimaryExtensions) {
3066 if (aPrimaryExtension.LowerCaseEqualsASCII(entry.mFileExtension)) {
3067 nsDependentCString mime(entry.mMimeType);
3068 for (const auto& extraEntry : extraMimeEntries) {
3069 if (mime.LowerCaseEqualsASCII(extraEntry.mMimeType)) {
3070 nsDependentCString goodExts(extraEntry.mFileExtensions);
3071 int32_t commaPos = goodExts.FindChar(',');
3072 commaPos = commaPos == kNotFound ? goodExts.Length() : commaPos;
3073 auto goodExt = Substring(goodExts, 0, commaPos);
3074 aMIMEInfo->SetPrimaryExtension(goodExt);
3075 return true;
3076 }
3077 }
3078 }
3079 }
3080 return false;
3081 }
3082
GetTypeFromExtras(const nsACString & aExtension,nsACString & aMIMEType)3083 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension,
3084 nsACString& aMIMEType) {
3085 NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
3086
3087 // Look for default entry with matching extension.
3088 nsDependentCString::const_iterator start, end, iter;
3089 int32_t numEntries = ArrayLength(extraMimeEntries);
3090 for (int32_t index = 0; index < numEntries; index++) {
3091 nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
3092 extList.BeginReading(start);
3093 extList.EndReading(end);
3094 iter = start;
3095 while (start != end) {
3096 FindCharInReadable(',', iter, end);
3097 if (Substring(start, iter)
3098 .Equals(aExtension, nsCaseInsensitiveCStringComparator)) {
3099 aMIMEType = extraMimeEntries[index].mMimeType;
3100 return true;
3101 }
3102 if (iter != end) {
3103 ++iter;
3104 }
3105 start = iter;
3106 }
3107 }
3108
3109 return false;
3110 }
3111
GetMIMETypeFromOSForExtension(const nsACString & aExtension,nsACString & aMIMEType)3112 bool nsExternalHelperAppService::GetMIMETypeFromOSForExtension(
3113 const nsACString& aExtension, nsACString& aMIMEType) {
3114 bool found = false;
3115 nsCOMPtr<nsIMIMEInfo> mimeInfo;
3116 nsresult rv =
3117 GetMIMEInfoFromOS(""_ns, aExtension, &found, getter_AddRefs(mimeInfo));
3118 return NS_SUCCEEDED(rv) && found && mimeInfo &&
3119 NS_SUCCEEDED(mimeInfo->GetMIMEType(aMIMEType));
3120 }
3121
GetMIMEInfoFromOS(const nsACString & aMIMEType,const nsACString & aFileExt,bool * aFound,nsIMIMEInfo ** aMIMEInfo)3122 nsresult nsExternalHelperAppService::GetMIMEInfoFromOS(
3123 const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound,
3124 nsIMIMEInfo** aMIMEInfo) {
3125 *aMIMEInfo = nullptr;
3126 *aFound = false;
3127 return NS_ERROR_NOT_IMPLEMENTED;
3128 }
3129