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/. */
11 #include "base/basictypes.h"
13 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
14 #include "mozilla/ArrayUtils.h"
15 #include "mozilla/Base64.h"
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/dom/TabChild.h"
19 #include "nsXULAppAPI.h"
21 #include "nsExternalHelperAppService.h"
22 #include "nsCExternalHandlerService.h"
26 #include "nsIFileURL.h"
27 #include "nsIChannel.h"
28 #include "nsIRedirectHistory.h"
29 #include "nsIDirectoryService.h"
30 #include "nsAppDirectoryServiceDefs.h"
31 #include "nsICategoryManager.h"
32 #include "nsDependentSubstring.h"
33 #include "nsXPIDLString.h"
34 #include "nsUnicharUtils.h"
35 #include "nsIStringEnumerator.h"
37 #include "nsIStreamListener.h"
38 #include "nsIMIMEService.h"
39 #include "nsILoadGroup.h"
40 #include "nsIWebProgressListener.h"
41 #include "nsITransfer.h"
42 #include "nsReadableUtils.h"
43 #include "nsIRequest.h"
44 #include "nsDirectoryServiceDefs.h"
45 #include "nsIInterfaceRequestor.h"
46 #include "nsThreadUtils.h"
47 #include "nsAutoPtr.h"
48 #include "nsIMutableArray.h"
50 // used to access our datastore of user-configured helper applications
51 #include "nsIHandlerService.h"
52 #include "nsIMIMEInfo.h"
53 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
54 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
55 #include "nsIHelperAppLauncherDialog.h"
56 #include "nsIContentDispatchChooser.h"
57 #include "nsNetUtil.h"
58 #include "nsIIOService.h"
60 #include "nsChannelProperties.h"
62 #include "nsMimeTypes.h"
63 // used for header disposition information.
64 #include "nsIHttpChannel.h"
65 #include "nsIHttpChannelInternal.h"
66 #include "nsIEncodedChannel.h"
67 #include "nsIMultiPartChannel.h"
68 #include "nsIFileChannel.h"
69 #include "nsIObserverService.h" // so we can be a profile change observer
70 #include "nsIPropertyBag2.h" // for the 64-bit content length
73 #include "nsILocalFileMac.h"
76 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
77 #include "nsPluginHost.h"
80 #include "nsIStringBundle.h" // XXX needed to localize error msgs
81 #include "nsIPrompt.h"
83 #include "nsITextToSubURI.h" // to unescape the filename
84 #include "nsIMIMEHeaderParam.h"
86 #include "nsIWindowWatcher.h"
88 #include "nsIDownloadHistory.h" // to mark downloads as visited
89 #include "nsDocShellCID.h"
92 #include "nsLocalHandlerApp.h"
94 #include "nsIRandomGenerator.h"
96 #include "ContentChild.h"
97 #include "nsXULAppAPI.h"
98 #include "nsPIDOMWindow.h"
99 #include "nsIDocShellTreeOwner.h"
100 #include "nsIDocShellTreeItem.h"
101 #include "ExternalHelperAppChild.h"
104 #include "nsWindowsHelpers.h"
107 #ifdef MOZ_WIDGET_ANDROID
108 #include "AndroidBridge.h"
111 #include "mozilla/Preferences.h"
112 #include "mozilla/ipc/URIUtils.h"
114 #ifdef MOZ_WIDGET_GONK
115 #include "nsDeviceStorage.h"
118 using namespace mozilla
;
119 using namespace mozilla::ipc
;
121 // Download Folder location constants
122 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
123 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
125 NS_FOLDER_VALUE_DESKTOP
= 0
126 , NS_FOLDER_VALUE_DOWNLOADS
= 1
127 , NS_FOLDER_VALUE_CUSTOM
= 2
131 PRLogModuleInfo
* nsExternalHelperAppService::mLog
= nullptr;
134 // Using level 3 here because the OSHelperAppServices use a log level
135 // of PR_LOG_DEBUG (4), and we want less detailed output here
136 // Using 3 instead of PR_LOG_WARN because we don't output warnings
138 #define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
139 #define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
141 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF
[] =
142 "browser.helperApps.neverAsk.saveToDisk";
143 static const char NEVER_ASK_FOR_OPEN_FILE_PREF
[] =
144 "browser.helperApps.neverAsk.openFile";
146 // Helper functions for Content-Disposition headers
149 * Given a URI fragment, unescape it
150 * @param aFragment The string to unescape
151 * @param aURI The URI from which this fragment is taken. Only its character set
153 * @param aResult [out] Unescaped string.
155 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
158 // First, we need a charset
159 nsAutoCString originCharset
;
160 nsresult rv
= aURI
->GetOriginCharset(originCharset
);
161 NS_ENSURE_SUCCESS(rv
, rv
);
163 // Now, we need the unescaper
164 nsCOMPtr
<nsITextToSubURI
> textToSubURI
= do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
165 NS_ENSURE_SUCCESS(rv
, rv
);
167 return textToSubURI
->UnEscapeURIForUI(originCharset
, aFragment
, aResult
);
171 * UTF-8 version of UnescapeFragment.
172 * @param aFragment The string to unescape
173 * @param aURI The URI from which this fragment is taken. Only its character set
175 * @param aResult [out] Unescaped string, UTF-8 encoded.
176 * @note It is safe to pass the same string for aFragment and aResult.
177 * @note When this function fails, aResult will not be modified.
179 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
183 nsresult rv
= UnescapeFragment(aFragment
, aURI
, result
);
184 if (NS_SUCCEEDED(rv
))
185 CopyUTF16toUTF8(result
, aResult
);
190 * Given a channel, returns the filename and extension the channel has.
191 * This uses the URL and other sources (nsIMultiPartChannel).
192 * Also gives back whether the channel requested external handling (i.e.
193 * whether Content-Disposition: attachment was sent)
194 * @param aChannel The channel to extract the filename/extension from
195 * @param aFileName [out] Reference to the string where the filename should be
196 * stored. Empty if it could not be retrieved.
197 * WARNING - this filename may contain characters which the OS does not
198 * allow as part of filenames!
199 * @param aExtension [out] Reference to the string where the extension should
200 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
201 * @param aAllowURLExtension (optional) Get the extension from the URL if no
202 * Content-Disposition header is present. Default is true.
203 * @retval true The server sent Content-Disposition:attachment or equivalent
204 * @retval false Content-Disposition: inline or no content-disposition header
207 static bool GetFilenameAndExtensionFromChannel(nsIChannel
* aChannel
,
209 nsCString
& aExtension
,
210 bool aAllowURLExtension
= true)
212 aExtension
.Truncate();
214 * If the channel is an http or part of a multipart channel and we
215 * have a content disposition header set, then use the file name
216 * suggested there as the preferred file name to SUGGEST to the
217 * user. we shouldn't actually use that without their
218 * permission... otherwise just use our temp file
220 bool handleExternally
= false;
222 nsresult rv
= aChannel
->GetContentDisposition(&disp
);
223 if (NS_SUCCEEDED(rv
))
225 aChannel
->GetContentDispositionFilename(aFileName
);
226 if (disp
== nsIChannel::DISPOSITION_ATTACHMENT
)
227 handleExternally
= true;
230 // If the disposition header didn't work, try the filename from nsIURL
231 nsCOMPtr
<nsIURI
> uri
;
232 aChannel
->GetURI(getter_AddRefs(uri
));
233 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
234 if (url
&& aFileName
.IsEmpty())
236 if (aAllowURLExtension
) {
237 url
->GetFileExtension(aExtension
);
238 UnescapeFragment(aExtension
, url
, aExtension
);
240 // Windows ignores terminating dots. So we have to as well, so
241 // that our security checks do "the right thing"
242 // In case the aExtension consisted only of the dot, the code below will
243 // extract an aExtension from the filename
244 aExtension
.Trim(".", false);
247 // try to extract the file name from the url and use that as a first pass as the
248 // leaf name of our temp file...
249 nsAutoCString leafName
;
250 url
->GetFileName(leafName
);
251 if (!leafName
.IsEmpty())
253 rv
= UnescapeFragment(leafName
, url
, aFileName
);
256 CopyUTF8toUTF16(leafName
, aFileName
); // use escaped name
261 // Extract Extension, if we have a filename; otherwise,
262 // truncate the string
263 if (aExtension
.IsEmpty()) {
264 if (!aFileName
.IsEmpty())
266 // Windows ignores terminating dots. So we have to as well, so
267 // that our security checks do "the right thing"
268 aFileName
.Trim(".", false);
270 // XXX RFindCharInReadable!!
271 nsAutoString
fileNameStr(aFileName
);
272 int32_t idx
= fileNameStr
.RFindChar(char16_t('.'));
273 if (idx
!= kNotFound
)
274 CopyUTF16toUTF8(StringTail(fileNameStr
, fileNameStr
.Length() - idx
- 1), aExtension
);
279 return handleExternally
;
283 * Obtains the directory to use. This tends to vary per platform, and
284 * needs to be consistent throughout our codepaths. For platforms where
285 * helper apps use the downloads directory, this should be kept in
286 * sync with nsDownloadManager.cpp
288 * Optionally skip availability of the directory and storage.
290 static nsresult
GetDownloadDirectory(nsIFile
**_directory
,
291 bool aSkipChecks
= false)
293 nsCOMPtr
<nsIFile
> dir
;
295 // On OS X, we first try to get the users download location, if it's set.
296 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST
, -1)) {
297 case NS_FOLDER_VALUE_DESKTOP
:
298 (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR
, getter_AddRefs(dir
));
300 case NS_FOLDER_VALUE_CUSTOM
:
302 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR
,
304 getter_AddRefs(dir
));
307 // If we're not checking for availability we're done.
309 dir
.forget(_directory
);
313 // We have the directory, and now we need to make sure it exists
314 bool dirExists
= false;
315 (void) dir
->Exists(&dirExists
);
316 if (dirExists
) break;
318 nsresult rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
325 case NS_FOLDER_VALUE_DOWNLOADS
:
326 // This is just the OS default location, so fall out
331 // If not, we default to the OS X default download location.
332 nsresult rv
= NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR
,
333 getter_AddRefs(dir
));
334 NS_ENSURE_SUCCESS(rv
, rv
);
336 #elif defined(MOZ_WIDGET_GONK)
337 // On Gonk, store the files on the sdcard in the downloads directory.
338 // We need to check with the volume manager which storage point is
341 // Pick the default storage in case multiple (internal and external) ones
343 nsString storageName
;
344 nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
347 nsRefPtr
<DeviceStorageFile
> dsf(
348 new DeviceStorageFile(NS_LITERAL_STRING("sdcard"),
350 NS_LITERAL_STRING("downloads")));
351 NS_ENSURE_TRUE(dsf
->mFile
, NS_ERROR_FILE_ACCESS_DENIED
);
353 // If we're not checking for availability we're done.
355 dsf
->mFile
.forget(_directory
);
359 // Check device storage status before continuing.
360 nsString storageStatus
;
361 dsf
->GetStatus(storageStatus
);
363 // If we get an "unavailable" status, it means the sd card is not present.
364 // We'll also catch internal errors by looking for an empty string and assume
365 // the SD card isn't present when this occurs.
366 if (storageStatus
.EqualsLiteral("unavailable") ||
367 storageStatus
.IsEmpty()) {
368 return NS_ERROR_FILE_NOT_FOUND
;
371 // If we get a status other than 'available' here it means the card is busy
372 // because it's mounted via USB or it is being formatted.
373 if (!storageStatus
.EqualsLiteral("available")) {
374 return NS_ERROR_FILE_ACCESS_DENIED
;
378 nsresult rv
= dsf
->mFile
->Exists(&alreadyThere
);
379 NS_ENSURE_SUCCESS(rv
, rv
);
381 rv
= dsf
->mFile
->Create(nsIFile::DIRECTORY_TYPE
, 0770);
382 NS_ENSURE_SUCCESS(rv
, rv
);
385 #elif defined(ANDROID)
386 // On mobile devices, we are avoiding exposing users to the file
387 // system, and don't save downloads to temp directories
389 // On Android we only return something if we have and SD-card
390 char* downloadDir
= getenv("DOWNLOADS_DIRECTORY");
393 nsCOMPtr
<nsIFile
> ldir
;
394 rv
= NS_NewNativeLocalFile(nsDependentCString(downloadDir
),
395 true, getter_AddRefs(ldir
));
396 NS_ENSURE_SUCCESS(rv
, rv
);
397 dir
= do_QueryInterface(ldir
);
399 // If we're not checking for availability we're done.
401 dir
.forget(_directory
);
406 return NS_ERROR_FAILURE
;
408 #elif defined(XP_WIN)
409 // On metro we want to be able to search opened files and the temp directory
410 // is exlcuded in searches.
412 if (IsRunningInWindowsMetro()) {
413 rv
= NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR
, getter_AddRefs(dir
));
415 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dir
));
417 NS_ENSURE_SUCCESS(rv
, rv
);
419 // On all other platforms, we default to the systems temporary directory.
420 nsresult rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dir
));
421 NS_ENSURE_SUCCESS(rv
, rv
);
424 NS_ASSERTION(dir
, "Somehow we didn't get a download directory!");
425 dir
.forget(_directory
);
430 * Structure for storing extension->type mappings.
431 * @see defaultMimeEntries
433 struct nsDefaultMimeTypeEntry
{
434 const char* mMimeType
;
435 const char* mFileExtension
;
439 * Default extension->mimetype mappings. These are not overridable.
440 * If you add types here, make sure they are lowercase, or you'll regret it.
442 static nsDefaultMimeTypeEntry defaultMimeEntries
[] =
444 // The following are those extensions that we're asked about during startup,
445 // sorted by order used
446 { IMAGE_GIF
, "gif" },
448 { APPLICATION_RDF
, "rdf" },
450 { IMAGE_PNG
, "png" },
451 // -- end extensions used during startup
453 { IMAGE_JPEG
, "jpeg" },
454 { IMAGE_JPEG
, "jpg" },
455 { IMAGE_SVG_XML
, "svg" },
456 { TEXT_HTML
, "html" },
457 { TEXT_HTML
, "htm" },
458 { APPLICATION_XPINSTALL
, "xpi" },
459 { "application/xhtml+xml", "xhtml" },
460 { "application/xhtml+xml", "xht" },
461 { TEXT_PLAIN
, "txt" },
462 { VIDEO_OGG
, "ogv" },
463 { VIDEO_OGG
, "ogg" },
464 { APPLICATION_OGG
, "ogg" },
465 { AUDIO_OGG
, "oga" },
467 { AUDIO_OGG
, "opus" },
470 { VIDEO_WEBM
, "webm" },
471 { AUDIO_WEBM
, "webm" },
473 #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
474 { VIDEO_MP4
, "mp4" },
475 { AUDIO_MP4
, "m4a" },
476 { AUDIO_MP3
, "mp3" },
484 * This is a small private struct used to help us initialize some
485 * default mime types.
487 struct nsExtraMimeTypeEntry
{
488 const char* mMimeType
;
489 const char* mFileExtensions
;
490 const char* mDescription
;
494 #define MAC_TYPE(x) x
496 #define MAC_TYPE(x) 0
500 * This table lists all of the 'extra' content types that we can deduce from particular
501 * file extensions. These entries also ensure that we provide a good descriptive name
502 * when we encounter files with these content types and/or extensions. These can be
503 * overridden by user helper app prefs.
504 * If you add types here, make sure they are lowercase, or you'll regret it.
506 static nsExtraMimeTypeEntry extraMimeEntries
[] =
509 { APPLICATION_OCTET_STREAM
, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" },
510 #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
511 { APPLICATION_OCTET_STREAM
, "exe,com", "Binary File" },
513 { APPLICATION_OCTET_STREAM
, "exe,com,bin", "Binary File" },
515 { APPLICATION_GZIP2
, "gz", "gzip" },
516 { "application/x-arj", "arj", "ARJ file" },
517 { "application/rtf", "rtf", "Rich Text Format File" },
518 { APPLICATION_XPINSTALL
, "xpi", "XPInstall Install" },
519 { APPLICATION_PDF
, "pdf", "Portable Document Format" },
520 { APPLICATION_POSTSCRIPT
, "ps,eps,ai", "Postscript File" },
521 { APPLICATION_XJAVASCRIPT
, "js", "Javascript Source File" },
522 { APPLICATION_XJAVASCRIPT
, "jsm", "Javascript Module Source File" },
523 #ifdef MOZ_WIDGET_ANDROID
524 { "application/vnd.android.package-archive", "apk", "Android Package" },
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
, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
531 { IMAGE_PNG
, "png", "PNG Image" },
532 { IMAGE_TIFF
, "tiff,tif", "TIFF Image" },
533 { IMAGE_XBM
, "xbm", "XBM Image" },
534 { IMAGE_SVG_XML
, "svg", "Scalable Vector Graphics" },
535 { MESSAGE_RFC822
, "eml", "RFC-822 data" },
536 { TEXT_PLAIN
, "txt,text", "Text File" },
537 { TEXT_HTML
, "html,htm,shtml,ehtml", "HyperText Markup Language" },
538 { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
539 { APPLICATION_MATHML_XML
, "mml", "Mathematical Markup Language" },
540 { APPLICATION_RDF
, "rdf", "Resource Description Framework" },
541 { TEXT_XUL
, "xul", "XML-Based User Interface Language" },
542 { TEXT_XML
, "xml,xsl,xbl", "Extensible Markup Language" },
543 { TEXT_CSS
, "css", "Style Sheet" },
544 { TEXT_VCARD
, "vcf,vcard", "Contact Information" },
545 { VIDEO_OGG
, "ogv", "Ogg Video" },
546 { VIDEO_OGG
, "ogg", "Ogg Video" },
547 { APPLICATION_OGG
, "ogg", "Ogg Video"},
548 { AUDIO_OGG
, "oga", "Ogg Audio" },
549 { AUDIO_OGG
, "opus", "Opus Audio" },
550 #ifdef MOZ_WIDGET_GONK
551 { AUDIO_AMR
, "amr", "Adaptive Multi-Rate Audio" },
553 { VIDEO_WEBM
, "webm", "Web Media Video" },
554 { AUDIO_WEBM
, "webm", "Web Media Audio" },
555 { AUDIO_MP3
, "mp3", "MPEG Audio" },
556 { VIDEO_MP4
, "mp4", "MPEG-4 Video" },
557 { AUDIO_MP4
, "m4a", "MPEG-4 Audio" },
558 { VIDEO_RAW
, "yuv", "Raw YUV Video" },
559 { AUDIO_WAV
, "wav", "Waveform Audio" },
560 { VIDEO_3GPP
, "3gpp,3gp", "3GPP Video" },
561 { VIDEO_3GPP2
,"3g2", "3GPP2 Video" },
562 { AUDIO_MIDI
, "mid", "Standard MIDI Audio" }
568 * File extensions for which decoding should be disabled.
569 * NOTE: These MUST be lower-case and ASCII.
571 static nsDefaultMimeTypeEntry nonDecodableExtensions
[] = {
572 { APPLICATION_GZIP
, "gz" },
573 { APPLICATION_GZIP
, "tgz" },
574 { APPLICATION_ZIP
, "zip" },
575 { APPLICATION_COMPRESS
, "z" },
576 { APPLICATION_GZIP
, "svgz" }
580 nsExternalHelperAppService
,
581 nsIExternalHelperAppService
,
582 nsPIExternalAppLauncher
,
583 nsIExternalProtocolService
,
586 nsISupportsWeakReference
)
588 nsExternalHelperAppService::nsExternalHelperAppService()
591 nsresult
nsExternalHelperAppService::Init()
593 // Add an observer for profile change
594 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
596 return NS_ERROR_FAILURE
;
600 mLog
= PR_NewLogModule("HelperAppService");
602 return NS_ERROR_OUT_OF_MEMORY
;
606 nsresult rv
= obs
->AddObserver(this, "profile-before-change", true);
607 NS_ENSURE_SUCCESS(rv
, rv
);
608 return obs
->AddObserver(this, "last-pb-context-exited", true);
611 nsExternalHelperAppService::~nsExternalHelperAppService()
617 nsExternalHelperAppService::DoContentContentProcessHelper(const nsACString
& aMimeContentType
,
618 nsIRequest
*aRequest
,
619 nsIInterfaceRequestor
*aContentContext
,
621 nsIInterfaceRequestor
*aWindowContext
,
622 nsIStreamListener
** aStreamListener
)
624 nsCOMPtr
<nsIDOMWindow
> window
= do_GetInterface(aContentContext
);
625 NS_ENSURE_STATE(window
);
627 // We need to get a hold of a ContentChild so that we can begin forwarding
628 // this data to the parent. In the HTTP case, this is unfortunate, since
629 // we're actually passing data from parent->child->parent wastefully, but
630 // the Right Fix will eventually be to short-circuit those channels on the
631 // parent side based on some sort of subscription concept.
632 using mozilla::dom::ContentChild
;
633 using mozilla::dom::ExternalHelperAppChild
;
634 ContentChild
*child
= ContentChild::GetSingleton();
636 return NS_ERROR_FAILURE
;
640 nsCOMPtr
<nsIURI
> uri
;
641 int64_t contentLength
= -1;
642 uint32_t contentDisposition
= -1;
643 nsAutoString fileName
;
645 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
647 channel
->GetURI(getter_AddRefs(uri
));
648 channel
->GetContentLength(&contentLength
);
649 channel
->GetContentDisposition(&contentDisposition
);
650 channel
->GetContentDispositionFilename(fileName
);
651 channel
->GetContentDispositionHeader(disp
);
654 nsCOMPtr
<nsIURI
> referrer
;
655 NS_GetReferrerFromChannel(channel
, getter_AddRefs(referrer
));
657 OptionalURIParams uriParams
, referrerParams
;
658 SerializeURI(uri
, uriParams
);
659 SerializeURI(referrer
, referrerParams
);
661 // Now we build a protocol for forwarding our data to the parent. The
662 // protocol will act as a listener on the child-side and create a "real"
663 // helperAppService listener on the parent-side, via another call to
665 mozilla::dom::PExternalHelperAppChild
*pc
=
666 child
->SendPExternalHelperAppConstructor(uriParams
,
667 nsCString(aMimeContentType
),
668 disp
, contentDisposition
,
669 fileName
, aForceSave
,
670 contentLength
, referrerParams
,
671 mozilla::dom::TabChild::GetFrom(window
));
672 ExternalHelperAppChild
*childListener
= static_cast<ExternalHelperAppChild
*>(pc
);
674 NS_ADDREF(*aStreamListener
= childListener
);
676 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
678 nsRefPtr
<nsExternalAppHandler
> handler
=
679 new nsExternalAppHandler(nullptr, EmptyCString(), aContentContext
, aWindowContext
, this,
680 fileName
, reason
, aForceSave
);
682 return NS_ERROR_OUT_OF_MEMORY
;
685 childListener
->SetHandler(handler
);
689 NS_IMETHODIMP
nsExternalHelperAppService::DoContent(const nsACString
& aMimeContentType
,
690 nsIRequest
*aRequest
,
691 nsIInterfaceRequestor
*aContentContext
,
693 nsIInterfaceRequestor
*aWindowContext
,
694 nsIStreamListener
** aStreamListener
)
696 if (XRE_GetProcessType() == GeckoProcessType_Content
) {
697 return DoContentContentProcessHelper(aMimeContentType
, aRequest
, aContentContext
,
698 aForceSave
, aWindowContext
, aStreamListener
);
701 nsAutoString fileName
;
702 nsAutoCString fileExtension
;
703 uint32_t reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
704 uint32_t contentDisposition
= -1;
706 // Get the file extension and name that we will need later
707 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
708 nsCOMPtr
<nsIURI
> uri
;
709 int64_t contentLength
= -1;
711 channel
->GetURI(getter_AddRefs(uri
));
712 channel
->GetContentLength(&contentLength
);
713 channel
->GetContentDisposition(&contentDisposition
);
714 channel
->GetContentDispositionFilename(fileName
);
716 // Check if we have a POST request, in which case we don't want to use
717 // the url's extension
718 bool allowURLExt
= true;
719 nsCOMPtr
<nsIHttpChannel
> httpChan
= do_QueryInterface(channel
);
721 nsAutoCString requestMethod
;
722 httpChan
->GetRequestMethod(requestMethod
);
723 allowURLExt
= !requestMethod
.EqualsLiteral("POST");
726 // Check if we had a query string - we don't want to check the URL
727 // extension if a query is present in the URI
728 // If we already know we don't want to check the URL extension, don't
729 // bother checking the query
730 if (uri
&& allowURLExt
) {
731 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(uri
);
736 // We only care about the query for HTTP and HTTPS URLs
738 bool isHTTP
, isHTTPS
;
739 rv
= uri
->SchemeIs("http", &isHTTP
);
743 rv
= uri
->SchemeIs("https", &isHTTPS
);
747 if (isHTTP
|| isHTTPS
) {
748 url
->GetQuery(query
);
751 // Only get the extension if the query is empty; if it isn't, then the
752 // extension likely belongs to a cgi script and isn't helpful
753 allowURLExt
= query
.IsEmpty();
756 // Extract name & extension
757 bool isAttachment
= GetFilenameAndExtensionFromChannel(channel
, fileName
,
760 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
761 fileExtension
.get(), NS_ConvertUTF16toUTF8(fileName
).get(),
764 reason
= nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
;
768 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
769 PromiseFlatCString(aMimeContentType
).get(), fileExtension
.get()));
771 // We get the mime service here even though we're the default implementation
772 // of it, so it's possible to override only the mime service and not need to
773 // reimplement the whole external helper app service itself.
774 nsCOMPtr
<nsIMIMEService
> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID
));
775 NS_ENSURE_TRUE(mimeSvc
, NS_ERROR_FAILURE
);
777 // Try to find a mime object by looking at the mime type/extension
778 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
779 if (aMimeContentType
.Equals(APPLICATION_GUESS_FROM_EXT
, nsCaseInsensitiveCStringComparator())) {
780 nsAutoCString mimeType
;
781 if (!fileExtension
.IsEmpty()) {
782 mimeSvc
->GetFromTypeAndExtension(EmptyCString(), fileExtension
, getter_AddRefs(mimeInfo
));
784 mimeInfo
->GetMIMEType(mimeType
);
786 LOG(("OS-Provided mime type '%s' for extension '%s'\n",
787 mimeType
.get(), fileExtension
.get()));
791 if (fileExtension
.IsEmpty() || mimeType
.IsEmpty()) {
792 // Extension lookup gave us no useful match
793 mimeSvc
->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM
), fileExtension
,
794 getter_AddRefs(mimeInfo
));
795 mimeType
.AssignLiteral(APPLICATION_OCTET_STREAM
);
799 channel
->SetContentType(mimeType
);
802 // Don't overwrite SERVERREQUEST
803 if (reason
== nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
804 reason
= nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
807 mimeSvc
->GetFromTypeAndExtension(aMimeContentType
, fileExtension
,
808 getter_AddRefs(mimeInfo
));
810 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo
.get()));
812 // No mimeinfo -> we can't continue. probably OOM.
814 return NS_ERROR_OUT_OF_MEMORY
;
817 *aStreamListener
= nullptr;
818 // We want the mimeInfo's primary extension to pass it to
819 // nsExternalAppHandler
821 mimeInfo
->GetPrimaryExtension(buf
);
823 nsExternalAppHandler
* handler
= new nsExternalAppHandler(mimeInfo
,
832 return NS_ERROR_OUT_OF_MEMORY
;
835 NS_ADDREF(*aStreamListener
= handler
);
839 NS_IMETHODIMP
nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString
& aExtension
,
840 const nsACString
& aEncodingType
,
841 bool *aApplyDecoding
)
843 *aApplyDecoding
= true;
845 for(i
= 0; i
< ArrayLength(nonDecodableExtensions
); ++i
) {
846 if (aExtension
.LowerCaseEqualsASCII(nonDecodableExtensions
[i
].mFileExtension
) &&
847 aEncodingType
.LowerCaseEqualsASCII(nonDecodableExtensions
[i
].mMimeType
)) {
848 *aApplyDecoding
= false;
855 nsresult
nsExternalHelperAppService::GetFileTokenForPath(const char16_t
* aPlatformAppPath
,
858 nsDependentString
platformAppPath(aPlatformAppPath
);
859 // First, check if we have an absolute path
860 nsIFile
* localFile
= nullptr;
861 nsresult rv
= NS_NewLocalFile(platformAppPath
, true, &localFile
);
862 if (NS_SUCCEEDED(rv
)) {
865 if (NS_FAILED((*aFile
)->Exists(&exists
)) || !exists
) {
867 return NS_ERROR_FILE_NOT_FOUND
;
873 // Second, check if file exists in mozilla program directory
874 rv
= NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR
, aFile
);
875 if (NS_SUCCEEDED(rv
)) {
876 rv
= (*aFile
)->Append(platformAppPath
);
877 if (NS_SUCCEEDED(rv
)) {
879 rv
= (*aFile
)->Exists(&exists
);
880 if (NS_SUCCEEDED(rv
) && exists
)
887 return NS_ERROR_NOT_AVAILABLE
;
890 //////////////////////////////////////////////////////////////////////////////////////////////////////
891 // begin external protocol service default implementation...
892 //////////////////////////////////////////////////////////////////////////////////////////////////////
893 NS_IMETHODIMP
nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme
,
894 bool * aHandlerExists
)
896 nsCOMPtr
<nsIHandlerInfo
> handlerInfo
;
897 nsresult rv
= GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme
),
898 getter_AddRefs(handlerInfo
));
899 NS_ENSURE_SUCCESS(rv
, rv
);
901 // See if we have any known possible handler apps for this
902 nsCOMPtr
<nsIMutableArray
> possibleHandlers
;
903 handlerInfo
->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers
));
906 possibleHandlers
->GetLength(&length
);
908 *aHandlerExists
= true;
912 // if not, fall back on an os-based handler
913 return OSProtocolHandlerExists(aProtocolScheme
, aHandlerExists
);
916 NS_IMETHODIMP
nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme
, bool * aResult
)
918 // check the per protocol setting first. it always takes precedence.
919 // if not set, then use the global setting.
921 nsAutoCString
prefName("network.protocol-handler.expose.");
922 prefName
+= aProtocolScheme
;
924 if (NS_SUCCEEDED(Preferences::GetBool(prefName
.get(), &val
))) {
929 // by default, no protocol is exposed. i.e., by default all link clicks must
930 // go through the external protocol service. most applications override this
933 Preferences::GetBool("network.protocol-handler.expose-all", false);
938 NS_IMETHODIMP
nsExternalHelperAppService::LoadUrl(nsIURI
* aURL
)
940 return LoadURI(aURL
, nullptr);
943 static const char kExternalProtocolPrefPrefix
[] = "network.protocol-handler.external.";
944 static const char kExternalProtocolDefaultPref
[] = "network.protocol-handler.external-default";
947 nsExternalHelperAppService::LoadURI(nsIURI
*aURI
,
948 nsIInterfaceRequestor
*aWindowContext
)
950 NS_ENSURE_ARG_POINTER(aURI
);
952 if (XRE_GetProcessType() == GeckoProcessType_Content
) {
954 SerializeURI(aURI
, uri
);
956 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri
);
963 if (spec
.Find("%00") != -1)
964 return NS_ERROR_MALFORMED_URI
;
966 spec
.ReplaceSubstring("\"", "%22");
967 spec
.ReplaceSubstring("`", "%60");
969 nsCOMPtr
<nsIIOService
> ios(do_GetIOService());
970 nsCOMPtr
<nsIURI
> uri
;
971 nsresult rv
= ios
->NewURI(spec
, nullptr, nullptr, getter_AddRefs(uri
));
972 NS_ENSURE_SUCCESS(rv
, rv
);
974 nsAutoCString scheme
;
975 uri
->GetScheme(scheme
);
976 if (scheme
.IsEmpty())
977 return NS_OK
; // must have a scheme
979 // Deny load if the prefs say to do so
980 nsAutoCString
externalPref(kExternalProtocolPrefPrefix
);
981 externalPref
+= scheme
;
982 bool allowLoad
= false;
983 if (NS_FAILED(Preferences::GetBool(externalPref
.get(), &allowLoad
))) {
984 // no scheme-specific value, check the default
985 if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref
,
987 return NS_OK
; // missing default pref
992 return NS_OK
; // explicitly denied
995 nsCOMPtr
<nsIHandlerInfo
> handler
;
996 rv
= GetProtocolHandlerInfo(scheme
, getter_AddRefs(handler
));
997 NS_ENSURE_SUCCESS(rv
, rv
);
999 nsHandlerInfoAction preferredAction
;
1000 handler
->GetPreferredAction(&preferredAction
);
1001 bool alwaysAsk
= true;
1002 handler
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1004 // if we are not supposed to ask, and the preferred action is to use
1005 // a helper app or the system default, we just launch the URI.
1006 if (!alwaysAsk
&& (preferredAction
== nsIHandlerInfo::useHelperApp
||
1007 preferredAction
== nsIHandlerInfo::useSystemDefault
))
1008 return handler
->LaunchWithURI(uri
, aWindowContext
);
1010 nsCOMPtr
<nsIContentDispatchChooser
> chooser
=
1011 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv
);
1012 NS_ENSURE_SUCCESS(rv
, rv
);
1014 return chooser
->Ask(handler
, aWindowContext
, uri
,
1015 nsIContentDispatchChooser::REASON_CANNOT_HANDLE
);
1018 NS_IMETHODIMP
nsExternalHelperAppService::GetApplicationDescription(const nsACString
& aScheme
, nsAString
& _retval
)
1020 // this method should only be implemented by each OS specific implementation of this service.
1021 return NS_ERROR_NOT_IMPLEMENTED
;
1025 //////////////////////////////////////////////////////////////////////////////////////////////////////
1026 // Methods related to deleting temporary files on exit
1027 //////////////////////////////////////////////////////////////////////////////////////////////////////
1031 nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile
* aTemporaryFile
,
1032 nsCOMArray
<nsIFile
> &aFileList
)
1034 bool isFile
= false;
1036 // as a safety measure, make sure the nsIFile is really a file and not a directory object.
1037 aTemporaryFile
->IsFile(&isFile
);
1038 if (!isFile
) return NS_OK
;
1040 aFileList
.AppendObject(aTemporaryFile
);
1046 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile
* aTemporaryFile
)
1048 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryFilesList
);
1052 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile
* aTemporaryFile
)
1054 return DeleteTemporaryFileHelper(aTemporaryFile
, mTemporaryPrivateFilesList
);
1057 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray
<nsIFile
> &fileList
)
1059 int32_t numEntries
= fileList
.Count();
1061 for (int32_t index
= 0; index
< numEntries
; index
++)
1063 localFile
= fileList
[index
];
1065 // First make the file writable, since the temp file is probably readonly.
1066 localFile
->SetPermissions(0600);
1067 localFile
->Remove(false);
1074 void nsExternalHelperAppService::ExpungeTemporaryFiles()
1076 ExpungeTemporaryFilesHelper(mTemporaryFilesList
);
1079 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
1081 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList
);
1084 static const char kExternalWarningPrefPrefix
[] =
1085 "network.protocol-handler.warn-external.";
1086 static const char kExternalWarningDefaultPref
[] =
1087 "network.protocol-handler.warn-external-default";
1090 nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString
&aScheme
,
1091 nsIHandlerInfo
**aHandlerInfo
)
1093 // XXX enterprise customers should be able to turn this support off with a
1094 // single master pref (maybe use one of the "exposed" prefs here?)
1097 nsresult rv
= GetProtocolHandlerInfoFromOS(aScheme
, &exists
, aHandlerInfo
);
1098 if (NS_FAILED(rv
)) {
1099 // Either it knows nothing, or we ran out of memory
1100 return NS_ERROR_FAILURE
;
1103 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1105 bool hasHandler
= false;
1106 (void) handlerSvc
->Exists(*aHandlerInfo
, &hasHandler
);
1108 rv
= handlerSvc
->FillHandlerInfo(*aHandlerInfo
, EmptyCString());
1109 if (NS_SUCCEEDED(rv
))
1114 return SetProtocolHandlerDefaults(*aHandlerInfo
, exists
);
1118 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString
&aScheme
,
1120 nsIHandlerInfo
**aHandlerInfo
)
1122 // intended to be implemented by the subclass
1123 return NS_ERROR_NOT_IMPLEMENTED
;
1127 nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo
*aHandlerInfo
,
1128 bool aOSHandlerExists
)
1130 // this type isn't in our database, so we've only got an OS default handler,
1133 if (aOSHandlerExists
) {
1134 // we've got a default, so use it
1135 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::useSystemDefault
);
1137 // whether or not to ask the user depends on the warning preference
1138 nsAutoCString scheme
;
1139 aHandlerInfo
->GetType(scheme
);
1141 nsAutoCString
warningPref(kExternalWarningPrefPrefix
);
1142 warningPref
+= scheme
;
1144 if (NS_FAILED(Preferences::GetBool(warningPref
.get(), &warn
))) {
1145 // no scheme-specific value, check the default
1146 warn
= Preferences::GetBool(kExternalWarningDefaultPref
, true);
1148 aHandlerInfo
->SetAlwaysAskBeforeHandling(warn
);
1150 // If no OS default existed, we set the preferred action to alwaysAsk.
1151 // This really means not initialized (i.e. there's no available handler)
1152 // to all the code...
1153 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::alwaysAsk
);
1159 // XPCOM profile change observer
1161 nsExternalHelperAppService::Observe(nsISupports
*aSubject
, const char *aTopic
, const char16_t
*someData
)
1163 if (!strcmp(aTopic
, "profile-before-change")) {
1164 ExpungeTemporaryFiles();
1165 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
1166 ExpungeTemporaryPrivateFiles();
1171 //////////////////////////////////////////////////////////////////////////////////////////////////////
1172 // begin external app handler implementation
1173 //////////////////////////////////////////////////////////////////////////////////////////////////////
1175 NS_IMPL_ADDREF(nsExternalAppHandler
)
1176 NS_IMPL_RELEASE(nsExternalAppHandler
)
1178 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler
)
1179 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStreamListener
)
1180 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
1181 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
1182 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher
)
1183 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
1184 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
1185 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver
)
1186 NS_INTERFACE_MAP_END_THREADSAFE
1188 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo
* aMIMEInfo
,
1189 const nsCSubstring
& aTempFileExtension
,
1190 nsIInterfaceRequestor
* aContentContext
,
1191 nsIInterfaceRequestor
* aWindowContext
,
1192 nsExternalHelperAppService
*aExtProtSvc
,
1193 const nsAString
& aSuggestedFilename
,
1194 uint32_t aReason
, bool aForceSave
)
1195 : mMimeInfo(aMIMEInfo
)
1196 , mContentContext(aContentContext
)
1197 , mWindowContext(aWindowContext
)
1198 , mWindowToClose(nullptr)
1199 , mSuggestedFileName(aSuggestedFilename
)
1200 , mForceSave(aForceSave
)
1202 , mShouldCloseWindow(false)
1203 , mStopRequestIssued(false)
1205 , mContentLength(-1)
1208 , mDialogProgressListener(nullptr)
1209 , mTransfer(nullptr)
1211 , mExtProtSvc(aExtProtSvc
)
1214 // make sure the extention includes the '.'
1215 if (!aTempFileExtension
.IsEmpty() && aTempFileExtension
.First() != '.')
1216 mTempFileExtension
= char16_t('.');
1217 AppendUTF8toUTF16(aTempFileExtension
, mTempFileExtension
);
1219 // replace platform specific path separator and illegal characters to avoid any confusion
1220 mSuggestedFileName
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1221 mTempFileExtension
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1223 // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
1224 const char16_t unsafeBidiCharacters
[] = {
1225 char16_t(0x061c), // Arabic Letter Mark
1226 char16_t(0x200e), // Left-to-Right Mark
1227 char16_t(0x200f), // Right-to-Left Mark
1228 char16_t(0x202a), // Left-to-Right Embedding
1229 char16_t(0x202b), // Right-to-Left Embedding
1230 char16_t(0x202c), // Pop Directional Formatting
1231 char16_t(0x202d), // Left-to-Right Override
1232 char16_t(0x202e), // Right-to-Left Override
1233 char16_t(0x2066), // Left-to-Right Isolate
1234 char16_t(0x2067), // Right-to-Left Isolate
1235 char16_t(0x2068), // First Strong Isolate
1236 char16_t(0x2069), // Pop Directional Isolate
1239 mSuggestedFileName
.ReplaceChar(unsafeBidiCharacters
, '_');
1240 mTempFileExtension
.ReplaceChar(unsafeBidiCharacters
, '_');
1242 // Make sure extension is correct.
1243 EnsureSuggestedFileName();
1245 mBufferSize
= Preferences::GetUint("network.buffer.cache.size", 4096);
1248 nsExternalAppHandler::~nsExternalAppHandler()
1250 MOZ_ASSERT(!mSaver
, "Saver should hold a reference to us until deleted");
1254 nsExternalAppHandler::DidDivertRequest(nsIRequest
*request
)
1256 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content
, "in child process");
1257 // Remove our request from the child loadGroup
1258 RetargetLoadNotifications(request
);
1262 NS_IMETHODIMP
nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2
* aWebProgressListener
)
1264 // This is always called by nsHelperDlg.js. Go ahead and register the
1265 // progress listener. At this point, we don't have mTransfer.
1266 mDialogProgressListener
= aWebProgressListener
;
1270 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFile(nsIFile
** aTarget
)
1272 if (mFinalFileDestination
)
1273 *aTarget
= mFinalFileDestination
;
1275 *aTarget
= mTempFile
;
1277 NS_IF_ADDREF(*aTarget
);
1281 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec
)
1283 // Use the real target if it's been set
1284 if (mFinalFileDestination
)
1285 return mFinalFileDestination
->IsExecutable(aExec
);
1287 // Otherwise, use the stored executable-ness of the temporary
1288 *aExec
= mTempFileIsExecutable
;
1292 NS_IMETHODIMP
nsExternalAppHandler::GetTimeDownloadStarted(PRTime
* aTime
)
1294 *aTime
= mTimeDownloadStarted
;
1298 NS_IMETHODIMP
nsExternalAppHandler::GetContentLength(int64_t *aContentLength
)
1300 *aContentLength
= mContentLength
;
1304 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest
*request
)
1306 // we are going to run the downloading of the helper app in our own little docloader / load group context.
1307 // so go ahead and force the creation of a load group and doc loader for us to use...
1308 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1312 // we need to store off the original (pre redirect!) channel that initiated the load. We do
1313 // this so later on, we can pass any refresh urls associated with the original channel back to the
1314 // window context which started the whole process. More comments about that are listed below....
1315 // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
1316 // ideally we should be able to just use mChannel (the channel we are extracting content from) or
1317 // the default load channel associated with the original load group. Unfortunately because
1318 // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
1319 // which is what we really want....
1321 // Note that we need to do this before removing aChannel from the loadgroup,
1322 // since that would mess with the original channel on the loader.
1323 nsCOMPtr
<nsIDocumentLoader
> origContextLoader
=
1324 do_GetInterface(mContentContext
);
1325 if (origContextLoader
) {
1326 origContextLoader
->GetDocumentChannel(getter_AddRefs(mOriginalChannel
));
1329 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
1331 nsCOMPtr
<nsILoadGroup
> oldLoadGroup
;
1332 aChannel
->GetLoadGroup(getter_AddRefs(oldLoadGroup
));
1335 oldLoadGroup
->RemoveRequest(request
, nullptr, NS_BINDING_RETARGETED
);
1338 aChannel
->SetLoadGroup(nullptr);
1339 aChannel
->SetNotificationCallbacks(nullptr);
1341 nsCOMPtr
<nsIPrivateBrowsingChannel
> pbChannel
= do_QueryInterface(aChannel
);
1343 pbChannel
->SetPrivate(isPrivate
);
1348 * Make mTempFileExtension contain an extension exactly when its previous value
1349 * is different from mSuggestedFileName's extension, so that it can be appended
1350 * to mSuggestedFileName and form a valid, useful leaf name.
1351 * This is required so that the (renamed) temporary file has the correct extension
1352 * after downloading to make sure the OS will launch the application corresponding
1353 * to the MIME type (which was used to calculate mTempFileExtension). This prevents
1354 * a cgi-script named foobar.exe that returns application/zip from being named
1355 * foobar.exe and executed as an executable file. It also blocks content that
1356 * a web site might provide with a content-disposition header indicating
1357 * filename="foobar.exe" from being downloaded to a file with extension .exe
1360 void nsExternalAppHandler::EnsureSuggestedFileName()
1362 // Make sure there is a mTempFileExtension (not "" or ".").
1363 // Remember that mTempFileExtension will always have the leading "."
1364 // (the check for empty is just to be safe).
1365 if (mTempFileExtension
.Length() > 1)
1367 // Get mSuggestedFileName's current extension.
1368 nsAutoString fileExt
;
1369 int32_t pos
= mSuggestedFileName
.RFindChar('.');
1370 if (pos
!= kNotFound
)
1371 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
1373 // Now, compare fileExt to mTempFileExtension.
1374 if (fileExt
.Equals(mTempFileExtension
, nsCaseInsensitiveStringComparator()))
1376 // Matches -> mTempFileExtension can be empty
1377 mTempFileExtension
.Truncate();
1382 nsresult
nsExternalAppHandler::SetUpTempFile(nsIChannel
* aChannel
)
1384 // First we need to try to get the destination directory for the temporary
1386 nsresult rv
= GetDownloadDirectory(getter_AddRefs(mTempFile
));
1387 NS_ENSURE_SUCCESS(rv
, rv
);
1389 // At this point, we do not have a filename for the temp file. For security
1390 // purposes, this cannot be predictable, so we must use a cryptographic
1391 // quality PRNG to generate one.
1392 // We will request raw random bytes, and transform that to a base64 string,
1393 // as all characters from the base64 set are acceptable for filenames. For
1394 // each three bytes of random data, we will get four bytes of ASCII. Request
1395 // a bit more, to be safe, and truncate to the length we want in the end.
1397 const uint32_t wantedFileNameLength
= 8;
1398 const uint32_t requiredBytesLength
=
1399 static_cast<uint32_t>((wantedFileNameLength
+ 1) / 4 * 3);
1401 nsCOMPtr
<nsIRandomGenerator
> rg
=
1402 do_GetService("@mozilla.org/security/random-generator;1", &rv
);
1403 NS_ENSURE_SUCCESS(rv
, rv
);
1406 rv
= rg
->GenerateRandomBytes(requiredBytesLength
, &buffer
);
1407 NS_ENSURE_SUCCESS(rv
, rv
);
1409 nsAutoCString tempLeafName
;
1410 nsDependentCSubstring
randomData(reinterpret_cast<const char*>(buffer
), requiredBytesLength
);
1411 rv
= Base64Encode(randomData
, tempLeafName
);
1414 NS_ENSURE_SUCCESS(rv
, rv
);
1416 tempLeafName
.Truncate(wantedFileNameLength
);
1418 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1419 // to replace illegal characters -- notably '/'
1420 tempLeafName
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1422 // now append our extension.
1424 mMimeInfo
->GetPrimaryExtension(ext
);
1425 if (!ext
.IsEmpty()) {
1426 ext
.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS
, '_');
1427 if (ext
.First() != '.')
1428 tempLeafName
.Append('.');
1429 tempLeafName
.Append(ext
);
1432 // We need to temporarily create a dummy file with the correct
1433 // file extension to determine the executable-ness, so do this before adding
1434 // the extra .part extension.
1435 nsCOMPtr
<nsIFile
> dummyFile
;
1436 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dummyFile
));
1437 NS_ENSURE_SUCCESS(rv
, rv
);
1439 // Set the file name without .part
1440 rv
= dummyFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1441 NS_ENSURE_SUCCESS(rv
, rv
);
1442 rv
= dummyFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1443 NS_ENSURE_SUCCESS(rv
, rv
);
1445 // Store executable-ness then delete
1446 dummyFile
->IsExecutable(&mTempFileIsExecutable
);
1447 dummyFile
->Remove(false);
1449 // Add an additional .part to prevent the OS from running this file in the
1450 // default application.
1451 tempLeafName
.AppendLiteral(".part");
1453 rv
= mTempFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1454 // make this file unique!!!
1455 NS_ENSURE_SUCCESS(rv
, rv
);
1456 rv
= mTempFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0644);
1457 NS_ENSURE_SUCCESS(rv
, rv
);
1459 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1460 // This is a bit broken in the case when createUnique actually had to append
1461 // some numbers, because then we now have a filename like foo.bar-1.part and
1462 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1463 // this. But the other options are all bad too.... Ideally we'd have a way
1464 // to tell createUnique to put its unique marker before the extension that
1465 // comes before ".part" or something.
1466 rv
= mTempFile
->GetLeafName(mTempLeafName
);
1467 NS_ENSURE_SUCCESS(rv
, rv
);
1469 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName
, NS_LITERAL_STRING(".part")),
1470 NS_ERROR_UNEXPECTED
);
1472 // Strip off the ".part" from mTempLeafName
1473 mTempLeafName
.Truncate(mTempLeafName
.Length() - ArrayLength(".part") + 1);
1475 MOZ_ASSERT(!mSaver
, "Output file initialization called more than once!");
1476 mSaver
= do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID
,
1478 NS_ENSURE_SUCCESS(rv
, rv
);
1480 rv
= mSaver
->SetObserver(this);
1481 if (NS_FAILED(rv
)) {
1486 rv
= mSaver
->EnableSha256();
1487 NS_ENSURE_SUCCESS(rv
, rv
);
1489 rv
= mSaver
->EnableSignatureInfo();
1490 NS_ENSURE_SUCCESS(rv
, rv
);
1491 LOG(("Enabled hashing and signature verification"));
1493 rv
= mSaver
->SetTarget(mTempFile
, false);
1494 NS_ENSURE_SUCCESS(rv
, rv
);
1499 NS_IMETHODIMP
nsExternalAppHandler::OnStartRequest(nsIRequest
*request
, nsISupports
* aCtxt
)
1501 NS_PRECONDITION(request
, "OnStartRequest without request?");
1503 // Set mTimeDownloadStarted here as the download has already started and
1504 // we want to record the start time before showing the filepicker.
1505 mTimeDownloadStarted
= PR_Now();
1509 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1513 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(request
));
1514 mIsFileChannel
= fileChan
!= nullptr;
1516 // Get content length
1518 aChannel
->GetContentLength(&mContentLength
);
1521 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(request
, &rv
));
1522 // Determine whether a new window was opened specifically for this request
1525 props
->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
1527 mShouldCloseWindow
= tmp
;
1532 aChannel
->GetURI(getter_AddRefs(mSourceUrl
));
1535 // retarget all load notifications to our docloader instead of the original window's docloader...
1536 RetargetLoadNotifications(request
);
1538 // Check to see if there is a refresh header on the original channel.
1539 if (mOriginalChannel
) {
1540 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(mOriginalChannel
));
1542 nsAutoCString refreshHeader
;
1543 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
1545 if (!refreshHeader
.IsEmpty()) {
1546 mShouldCloseWindow
= false;
1551 // Close the underlying DOMWindow if there is no refresh header
1552 // and it was opened specifically for the download
1555 // In an IPC setting, we're allowing the child process, here, to make
1556 // decisions about decoding the channel (e.g. decompression). It will
1557 // still forward the decoded (uncompressed) data back to the parent.
1558 // Con: Uncompressed data means more IPC overhead.
1559 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1560 // Parent process doesn't need to expect CPU time on decompression.
1561 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface( aChannel
);
1563 // Turn off content encoding conversions if needed
1564 bool applyConversion
= true;
1566 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(mSourceUrl
));
1568 nsAutoCString extension
;
1569 sourceURL
->GetFileExtension(extension
);
1570 if (!extension
.IsEmpty()) {
1571 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
1572 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
1575 rv
= encEnum
->HasMore(&hasMore
);
1576 if (NS_SUCCEEDED(rv
) && hasMore
) {
1577 nsAutoCString encType
;
1578 rv
= encEnum
->GetNext(encType
);
1579 if (NS_SUCCEEDED(rv
) && !encType
.IsEmpty()) {
1580 mExtProtSvc
->ApplyDecodingForExtension(extension
, encType
,
1588 encChannel
->SetApplyConversion( applyConversion
);
1591 // At this point, the child process has done everything it can usefully do
1592 // for OnStartRequest.
1593 if (XRE_GetProcessType() == GeckoProcessType_Content
) {
1597 rv
= SetUpTempFile(aChannel
);
1598 if (NS_FAILED(rv
)) {
1599 nsresult transferError
= rv
;
1601 rv
= CreateFailedTransfer(aChannel
&& NS_UsePrivateBrowsing(aChannel
));
1603 if (NS_FAILED(rv
)) {
1604 LOG(("Failed to create transfer to report failure."
1605 "Will fallback to prompter!"));
1610 request
->Cancel(transferError
);
1614 mTempFile
->GetPath(path
);
1616 SendStatusChange(kWriteError
, transferError
, request
, path
);
1621 // Inform channel it is open on behalf of a download to prevent caching.
1622 nsCOMPtr
<nsIHttpChannelInternal
> httpInternal
= do_QueryInterface(aChannel
);
1624 httpInternal
->SetChannelIsForDownload(true);
1627 // now that the temp file is set up, find out if we need to invoke a dialog
1628 // asking the user what they want us to do with this content...
1630 // We can get here for three reasons: "can't handle", "sniffed type", or
1631 // "server sent content-disposition:attachment". In the first case we want
1632 // to honor the user's "always ask" pref; in the other two cases we want to
1633 // honor it only if the default action is "save". Opening attachments in
1634 // helper apps by default breaks some websites (especially if the attachment
1635 // is one part of a multipart document). Opening sniffed content in helper
1636 // apps by default introduces security holes that we'd rather not have.
1638 // So let's find out whether the user wants to be prompted. If he does not,
1639 // check mReason and the preferred action to see what we should do.
1641 bool alwaysAsk
= true;
1642 mMimeInfo
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1644 // But we *don't* ask if this mimeInfo didn't come from
1645 // our user configuration datastore and the user has said
1646 // at some point in the distant past that they don't
1647 // want to be asked. The latter fact would have been
1648 // stored in pref strings back in the old days.
1650 bool mimeTypeIsInDatastore
= false;
1651 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1653 handlerSvc
->Exists(mMimeInfo
, &mimeTypeIsInDatastore
);
1655 if (!handlerSvc
|| !mimeTypeIsInDatastore
) {
1656 nsAutoCString MIMEType
;
1657 mMimeInfo
->GetMIMEType(MIMEType
);
1658 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF
, MIMEType
.get())) {
1659 // Don't need to ask after all.
1661 // Make sure action matches pref (save to disk).
1662 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
1663 } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF
, MIMEType
.get())) {
1664 // Don't need to ask after all.
1670 int32_t action
= nsIMIMEInfo::saveToDisk
;
1671 mMimeInfo
->GetPreferredAction( &action
);
1673 // OK, now check why we're here
1674 if (!alwaysAsk
&& mReason
!= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
1675 // Force asking if we're not saving. See comment back when we fetched the
1676 // alwaysAsk boolean for details.
1677 alwaysAsk
= (action
!= nsIMIMEInfo::saveToDisk
);
1680 // if we were told that we _must_ save to disk without asking, all the stuff
1681 // before this is irrelevant; override it
1684 action
= nsIMIMEInfo::saveToDisk
;
1689 // Display the dialog
1690 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1691 NS_ENSURE_SUCCESS(rv
, rv
);
1693 // this will create a reference cycle (the dialog holds a reference to us as
1694 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1695 rv
= mDialog
->Show(this, GetDialogParent(), mReason
);
1697 // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
1702 // We need to do the save/open immediately, then.
1704 /* We need to see whether the file we've got here could be
1705 * executable. If it could, we had better not try to open it!
1706 * We can skip this check, though, if we have a setting to open in a
1708 * This code mirrors the code in
1709 * nsExternalAppHandler::LaunchWithApplication so that what we
1710 * test here is as close as possible to what will really be
1711 * happening if we decide to execute
1713 nsCOMPtr
<nsIHandlerApp
> prefApp
;
1714 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(prefApp
));
1715 if (action
!= nsIMIMEInfo::useHelperApp
|| !prefApp
) {
1716 nsCOMPtr
<nsIFile
> fileToTest
;
1717 GetTargetFile(getter_AddRefs(fileToTest
));
1720 rv
= fileToTest
->IsExecutable(&isExecutable
);
1721 if (NS_FAILED(rv
) || isExecutable
) { // checking NS_FAILED, because paranoia is good
1722 action
= nsIMIMEInfo::saveToDisk
;
1724 } else { // Paranoia is good here too, though this really should not happen
1725 NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
1726 action
= nsIMIMEInfo::saveToDisk
;
1731 if (action
== nsIMIMEInfo::useHelperApp
||
1732 action
== nsIMIMEInfo::useSystemDefault
) {
1733 rv
= LaunchWithApplication(nullptr, false);
1735 rv
= SaveToDisk(nullptr, false);
1742 // Convert error info into proper message text and send OnStatusChange
1743 // notification to the dialog progress listener or nsITransfer implementation.
1744 void nsExternalAppHandler::SendStatusChange(ErrorType type
, nsresult rv
, nsIRequest
*aRequest
, const nsAFlatString
&path
)
1748 case NS_ERROR_OUT_OF_MEMORY
:
1750 msgId
.AssignLiteral("noMemory");
1753 case NS_ERROR_FILE_DISK_FULL
:
1754 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1755 // Out of space on target volume.
1756 msgId
.AssignLiteral("diskFull");
1759 case NS_ERROR_FILE_READ_ONLY
:
1760 // Attempt to write to read/only file.
1761 msgId
.AssignLiteral("readOnly");
1764 case NS_ERROR_FILE_ACCESS_DENIED
:
1765 if (type
== kWriteError
) {
1766 // Attempt to write without sufficient permissions.
1767 #if defined(ANDROID)
1768 // On Android (and Gonk), this means the SD card is present but
1769 // unavailable (read-only).
1770 msgId
.AssignLiteral("SDAccessErrorCardReadOnly");
1772 msgId
.AssignLiteral("accessError");
1775 msgId
.AssignLiteral("launchError");
1779 case NS_ERROR_FILE_NOT_FOUND
:
1780 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
:
1781 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
1782 // Helper app not found, let's verify this happened on launch
1783 if (type
== kLaunchError
) {
1784 msgId
.AssignLiteral("helperAppNotFound");
1787 #if defined(ANDROID)
1788 else if (type
== kWriteError
) {
1789 // On Android (and Gonk), this means the SD card is missing (not in
1791 msgId
.AssignLiteral("SDAccessErrorCardMissing");
1798 // Generic read/write/launch error message.
1801 msgId
.AssignLiteral("readError");
1804 msgId
.AssignLiteral("writeError");
1807 msgId
.AssignLiteral("launchError");
1812 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_ERROR
,
1813 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
1814 NS_LossyConvertUTF16toASCII(msgId
).get(), type
, mDialogProgressListener
.get(), mTransfer
.get(), rv
));
1815 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_ERROR
,
1816 (" path='%s'\n", NS_ConvertUTF16toUTF8(path
).get()));
1818 // Get properties file bundle and extract status string.
1819 nsCOMPtr
<nsIStringBundleService
> stringService
=
1820 mozilla::services::GetStringBundleService();
1821 if (stringService
) {
1822 nsCOMPtr
<nsIStringBundle
> bundle
;
1823 if (NS_SUCCEEDED(stringService
->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties",
1824 getter_AddRefs(bundle
)))) {
1825 nsXPIDLString msgText
;
1826 const char16_t
*strings
[] = { path
.get() };
1827 if (NS_SUCCEEDED(bundle
->FormatStringFromName(msgId
.get(), strings
, 1,
1828 getter_Copies(msgText
)))) {
1829 if (mDialogProgressListener
) {
1830 // We have a listener, let it handle the error.
1831 mDialogProgressListener
->OnStatusChange(nullptr, (type
== kReadError
) ? aRequest
: nullptr, rv
, msgText
);
1832 } else if (mTransfer
) {
1833 mTransfer
->OnStatusChange(nullptr, (type
== kReadError
) ? aRequest
: nullptr, rv
, msgText
);
1834 } else if (XRE_GetProcessType() == GeckoProcessType_Default
) {
1835 // We don't have a listener. Simply show the alert ourselves.
1837 nsCOMPtr
<nsIPrompt
> prompter(do_GetInterface(GetDialogParent(), &qiRv
));
1838 nsXPIDLString title
;
1839 bundle
->FormatStringFromName(MOZ_UTF16("title"),
1842 getter_Copies(title
));
1844 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_DEBUG
,
1845 ("mContentContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
1846 mContentContext
.get(),
1849 NS_ConvertUTF16toUTF8(title
).get(),
1850 NS_ConvertUTF16toUTF8(msgText
).get()));
1852 // If we didn't have a prompter we will try and get a window
1853 // instead, get it's docshell and use it to alert the user.
1855 nsCOMPtr
<nsPIDOMWindow
> window(do_GetInterface(GetDialogParent()));
1856 if (!window
|| !window
->GetDocShell()) {
1860 prompter
= do_GetInterface(window
->GetDocShell(), &qiRv
);
1862 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_DEBUG
,
1863 ("No prompter from mContentContext, using DocShell, " \
1864 "window=0x%p, docShell=0x%p, " \
1865 "prompter=0x%p, qi rv=0x%08X",
1867 window
->GetDocShell(),
1871 // If we still don't have a prompter, there's nothing else we
1872 // can do so just return.
1874 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_ERROR
,
1875 ("No prompter from DocShell, no way to alert user"));
1880 // We should always have a prompter at this point.
1881 prompter
->Alert(title
, msgText
);
1889 nsExternalAppHandler::OnDataAvailable(nsIRequest
*request
, nsISupports
* aCtxt
,
1890 nsIInputStream
* inStr
,
1891 uint64_t sourceOffset
, uint32_t count
)
1893 nsresult rv
= NS_OK
;
1894 // first, check to see if we've been canceled....
1895 if (mCanceled
|| !mSaver
) {
1896 // then go cancel our underlying channel too
1897 return request
->Cancel(NS_BINDING_ABORTED
);
1900 // read the data out of the stream and write it to the temp file.
1904 nsCOMPtr
<nsIStreamListener
> saver
= do_QueryInterface(mSaver
);
1905 rv
= saver
->OnDataAvailable(request
, aCtxt
, inStr
, sourceOffset
, count
);
1906 if (NS_SUCCEEDED(rv
)) {
1907 // Send progress notification.
1909 mTransfer
->OnProgressChange64(nullptr, request
, mProgress
,
1910 mContentLength
, mProgress
,
1914 // An error occurred, notify listener.
1915 nsAutoString tempFilePath
;
1917 mTempFile
->GetPath(tempFilePath
);
1919 SendStatusChange(kReadError
, rv
, request
, tempFilePath
);
1921 // Cancel the download.
1928 NS_IMETHODIMP
nsExternalAppHandler::OnStopRequest(nsIRequest
*request
, nsISupports
*aCtxt
,
1931 LOG(("nsExternalAppHandler::OnStopRequest\n"
1932 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
1933 mCanceled
, mTransfer
.get(), aStatus
));
1935 mStopRequestIssued
= true;
1937 // Cancel if the request did not complete successfully.
1938 if (!mCanceled
&& NS_FAILED(aStatus
)) {
1939 // Send error notification.
1940 nsAutoString tempFilePath
;
1942 mTempFile
->GetPath(tempFilePath
);
1943 SendStatusChange( kReadError
, aStatus
, request
, tempFilePath
);
1948 // first, check to see if we've been canceled....
1949 if (mCanceled
|| !mSaver
) {
1953 return mSaver
->Finish(NS_OK
);
1957 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver
*aSaver
,
1964 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver
*aSaver
,
1967 LOG(("nsExternalAppHandler::OnSaveComplete\n"
1968 " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
1969 aSaver
, aStatus
, mCanceled
, mTransfer
.get()));
1972 // Save the hash and signature information
1973 (void)mSaver
->GetSha256Hash(mHash
);
1974 (void)mSaver
->GetSignatureInfo(getter_AddRefs(mSignatureInfo
));
1976 // Free the reference that the saver keeps on us, even if we couldn't get
1980 // Save the redirect information.
1981 nsCOMPtr
<nsIRedirectHistory
> history
= do_QueryInterface(mRequest
);
1983 (void)history
->GetRedirects(getter_AddRefs(mRedirects
));
1984 uint32_t length
= 0;
1985 mRedirects
->GetLength(&length
);
1986 LOG(("nsExternalAppHandler: Got %u redirects\n", length
));
1988 LOG(("nsExternalAppHandler: No redirects\n"));
1991 if (NS_FAILED(aStatus
)) {
1993 mTempFile
->GetPath(path
);
1995 // It may happen when e10s is enabled that there will be no transfer
1996 // object available to communicate status as expected by the system.
1997 // Let's try and create a temporary transfer object to take care of this
1998 // for us, we'll fall back to using the prompt service if we absolutely
2001 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2002 // We don't care if this fails.
2003 CreateFailedTransfer(channel
&& NS_UsePrivateBrowsing(channel
));
2006 SendStatusChange(kWriteError
, aStatus
, nullptr, path
);
2013 // Notify the transfer object that we are done if the user has chosen an
2014 // action. If the user hasn't chosen an action, the progress listener
2015 // (nsITransfer) will be notified in CreateTransfer.
2017 NotifyTransfer(aStatus
);
2023 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus
)
2025 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2026 MOZ_ASSERT(mTransfer
, "We must have an nsITransfer");
2028 LOG(("Notifying progress listener"));
2030 if (NS_SUCCEEDED(aStatus
)) {
2031 (void)mTransfer
->SetSha256Hash(mHash
);
2032 (void)mTransfer
->SetSignatureInfo(mSignatureInfo
);
2033 (void)mTransfer
->SetRedirects(mRedirects
);
2034 (void)mTransfer
->OnProgressChange64(nullptr, nullptr, mProgress
,
2035 mContentLength
, mProgress
, mContentLength
);
2038 (void)mTransfer
->OnStateChange(nullptr, nullptr,
2039 nsIWebProgressListener::STATE_STOP
|
2040 nsIWebProgressListener::STATE_IS_REQUEST
|
2041 nsIWebProgressListener::STATE_IS_NETWORK
, aStatus
);
2043 // This nsITransfer object holds a reference to us (we are its observer), so
2044 // we need to release the reference to break a reference cycle (and therefore
2045 // to prevent leaking). We do this even if the previous calls failed.
2046 mTransfer
= nullptr;
2049 NS_IMETHODIMP
nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo
** aMIMEInfo
)
2051 *aMIMEInfo
= mMimeInfo
;
2052 NS_ADDREF(*aMIMEInfo
);
2056 NS_IMETHODIMP
nsExternalAppHandler::GetSource(nsIURI
** aSourceURI
)
2058 NS_ENSURE_ARG(aSourceURI
);
2059 *aSourceURI
= mSourceUrl
;
2060 NS_IF_ADDREF(*aSourceURI
);
2064 NS_IMETHODIMP
nsExternalAppHandler::GetSuggestedFileName(nsAString
& aSuggestedFileName
)
2066 aSuggestedFileName
= mSuggestedFileName
;
2070 nsresult
nsExternalAppHandler::CreateTransfer()
2072 LOG(("nsExternalAppHandler::CreateTransfer"));
2074 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2075 // We are back from the helper app dialog (where the user chooses to save or
2076 // open), but we aren't done processing the load. in this case, throw up a
2077 // progress dialog so the user can see what's going on.
2078 // Also, release our reference to mDialog. We don't need it anymore, and we
2079 // need to break the reference cycle.
2081 if (!mDialogProgressListener
) {
2082 NS_WARNING("The dialog should nullify the dialog progress listener");
2086 // We must be able to create an nsITransfer object. If not, it doesn't matter
2087 // much that we can't launch the helper application or save to disk. Work on
2088 // a local copy rather than mTransfer until we know we succeeded, to make it
2089 // clearer that this function is re-entrant.
2090 nsCOMPtr
<nsITransfer
> transfer
= do_CreateInstance(
2091 NS_TRANSFER_CONTRACTID
, &rv
);
2092 NS_ENSURE_SUCCESS(rv
, rv
);
2094 // Initialize the download
2095 nsCOMPtr
<nsIURI
> target
;
2096 rv
= NS_NewFileURI(getter_AddRefs(target
), mFinalFileDestination
);
2097 NS_ENSURE_SUCCESS(rv
, rv
);
2099 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2101 rv
= transfer
->Init(mSourceUrl
, target
, EmptyString(),
2102 mMimeInfo
, mTimeDownloadStarted
, mTempFile
, this,
2103 channel
&& NS_UsePrivateBrowsing(channel
));
2104 NS_ENSURE_SUCCESS(rv
, rv
);
2106 // Now let's add the download to history
2107 nsCOMPtr
<nsIDownloadHistory
> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID
));
2109 nsCOMPtr
<nsIURI
> referrer
;
2110 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mRequest
);
2112 NS_GetReferrerFromChannel(channel
, getter_AddRefs(referrer
));
2115 if (channel
&& !NS_UsePrivateBrowsing(channel
)) {
2116 dh
->AddDownload(mSourceUrl
, referrer
, mTimeDownloadStarted
, target
);
2120 // If we were cancelled since creating the transfer, just return. It is
2121 // always ok to return NS_OK if we are cancelled. Callers of this function
2122 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2127 rv
= transfer
->OnStateChange(nullptr, mRequest
,
2128 nsIWebProgressListener::STATE_START
|
2129 nsIWebProgressListener::STATE_IS_REQUEST
|
2130 nsIWebProgressListener::STATE_IS_NETWORK
, NS_OK
);
2131 NS_ENSURE_SUCCESS(rv
, rv
);
2138 // Finally, save the transfer to mTransfer.
2139 mTransfer
= transfer
;
2142 // While we were bringing up the progress dialog, we actually finished
2143 // processing the url. If that's the case then mStopRequestIssued will be
2144 // true and OnSaveComplete has been called.
2145 if (mStopRequestIssued
&& !mSaver
&& mTransfer
) {
2146 NotifyTransfer(NS_OK
);
2152 nsresult
nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing
)
2155 nsCOMPtr
<nsITransfer
> transfer
=
2156 do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
2157 NS_ENSURE_SUCCESS(rv
, rv
);
2159 // If we don't have a download directory we're kinda screwed but it's OK
2160 // we'll still report the error via the prompter.
2161 nsCOMPtr
<nsIFile
> pseudoFile
;
2162 rv
= GetDownloadDirectory(getter_AddRefs(pseudoFile
), true);
2163 NS_ENSURE_SUCCESS(rv
, rv
);
2165 // Append the default suggested filename. If the user restarts the transfer
2166 // we will re-trigger a filename check anyway to ensure that it is unique.
2167 rv
= pseudoFile
->Append(mSuggestedFileName
);
2168 NS_ENSURE_SUCCESS(rv
, rv
);
2170 nsCOMPtr
<nsIURI
> pseudoTarget
;
2171 rv
= NS_NewFileURI(getter_AddRefs(pseudoTarget
), pseudoFile
);
2172 NS_ENSURE_SUCCESS(rv
, rv
);
2174 rv
= transfer
->Init(mSourceUrl
, pseudoTarget
, EmptyString(),
2175 mMimeInfo
, mTimeDownloadStarted
, nullptr, this,
2176 aIsPrivateBrowsing
);
2177 NS_ENSURE_SUCCESS(rv
, rv
);
2179 // Our failed transfer is ready.
2180 mTransfer
= transfer
.forget();
2185 nsresult
nsExternalAppHandler::SaveDestinationAvailable(nsIFile
* aFile
)
2188 ContinueSave(aFile
);
2190 Cancel(NS_BINDING_ABORTED
);
2195 void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString
&aDefaultFile
, const nsAFlatString
&aFileExtension
)
2197 // Display the dialog
2198 // XXX Convert to use file picker? No, then embeddors could not do any sort of
2199 // "AutoDownload" w/o showing a prompt
2200 nsresult rv
= NS_OK
;
2202 // Get helper app launcher dialog.
2203 mDialog
= do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
2205 Cancel(NS_BINDING_ABORTED
);
2210 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
2211 // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
2213 // Now, be sure to keep |this| alive, and the dialog
2214 // If we don't do this, users that close the helper app dialog while the file
2215 // picker is up would cause Cancel() to be called, and the dialog would be
2216 // released, which would release this object too, which would crash.
2219 nsRefPtr
<nsExternalAppHandler
> kungFuDeathGrip(this);
2220 nsCOMPtr
<nsIHelperAppLauncherDialog
> dlg(mDialog
);
2221 rv
= mDialog
->PromptForSaveToFile(this,
2224 aFileExtension
.get(),
2225 mForceSave
, &fileToUse
);
2227 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2228 // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
2229 rv
= mDialog
->PromptForSaveToFileAsync(this,
2232 aFileExtension
.get(),
2235 SaveDestinationAvailable(rv
== NS_OK
? fileToUse
: nullptr);
2239 // SaveToDisk should only be called by the helper app dialog which allows
2240 // the user to say launch with application or save to disk. It doesn't actually
2241 // perform the save, it just prompts for the destination file name.
2242 NS_IMETHODIMP
nsExternalAppHandler::SaveToDisk(nsIFile
* aNewFileLocation
, bool aRememberThisPreference
)
2247 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
2249 if (!aNewFileLocation
) {
2250 if (mSuggestedFileName
.IsEmpty())
2251 RequestSaveDestination(mTempLeafName
, mTempFileExtension
);
2254 nsAutoString fileExt
;
2255 int32_t pos
= mSuggestedFileName
.RFindChar('.');
2257 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
2258 if (fileExt
.IsEmpty())
2259 fileExt
= mTempFileExtension
;
2261 RequestSaveDestination(mSuggestedFileName
, fileExt
);
2264 ContinueSave(aNewFileLocation
);
2269 nsresult
nsExternalAppHandler::ContinueSave(nsIFile
* aNewFileLocation
)
2274 NS_PRECONDITION(aNewFileLocation
, "Must be called with a non-null file");
2276 nsresult rv
= NS_OK
;
2277 nsCOMPtr
<nsIFile
> fileToUse
= do_QueryInterface(aNewFileLocation
);
2278 mFinalFileDestination
= do_QueryInterface(fileToUse
);
2280 // Move what we have in the final directory, but append .part
2281 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2282 // saver if we are done (Finish has been called) but OnSaverComplete has not
2284 if (mFinalFileDestination
&& mSaver
&& !mStopRequestIssued
)
2286 nsCOMPtr
<nsIFile
> movedFile
;
2287 mFinalFileDestination
->Clone(getter_AddRefs(movedFile
));
2289 // Get the old leaf name and append .part to it
2291 mFinalFileDestination
->GetLeafName(name
);
2292 name
.AppendLiteral(".part");
2293 movedFile
->SetLeafName(name
);
2295 rv
= mSaver
->SetTarget(movedFile
, true);
2296 if (NS_FAILED(rv
)) {
2298 mTempFile
->GetPath(path
);
2299 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2304 mTempFile
= movedFile
;
2308 // The helper app dialog has told us what to do and we have a final file
2310 rv
= CreateTransfer();
2311 // If we fail to create the transfer, Cancel.
2312 if (NS_FAILED(rv
)) {
2317 // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
2318 // if there is one. We don't want to do this before the save as dialog goes away because this dialog
2319 // is modal and we do bad things if you try to load a web page in the underlying window while a modal
2320 // dialog is still up.
2321 ProcessAnyRefreshTags();
2327 // LaunchWithApplication should only be called by the helper app dialog which
2328 // allows the user to say launch with application or save to disk. It doesn't
2329 // actually perform launch with application.
2330 NS_IMETHODIMP
nsExternalAppHandler::LaunchWithApplication(nsIFile
* aApplication
, bool aRememberThisPreference
)
2335 // user has chosen to launch using an application, fire any refresh tags now...
2336 ProcessAnyRefreshTags();
2338 if (mMimeInfo
&& aApplication
) {
2339 PlatformLocalHandlerApp_t
*handlerApp
=
2340 new PlatformLocalHandlerApp_t(EmptyString(), aApplication
);
2341 mMimeInfo
->SetPreferredApplicationHandler(handlerApp
);
2344 // Now check if the file is local, in which case we won't bother with saving
2345 // it to a temporary directory and just launch it from where it is
2346 nsCOMPtr
<nsIFileURL
> fileUrl(do_QueryInterface(mSourceUrl
));
2347 if (fileUrl
&& mIsFileChannel
) {
2348 Cancel(NS_BINDING_ABORTED
);
2349 nsCOMPtr
<nsIFile
> file
;
2350 nsresult rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2352 if (NS_SUCCEEDED(rv
)) {
2353 rv
= mMimeInfo
->LaunchWithFile(file
);
2354 if (NS_SUCCEEDED(rv
))
2359 file
->GetPath(path
);
2360 // If we get here, an error happened
2361 SendStatusChange(kLaunchError
, rv
, nullptr, path
);
2365 // Now that the user has elected to launch the downloaded file with a helper
2366 // app, we're justified in removing the 'salted' name. We'll rename to what
2367 // was specified in mSuggestedFileName after the download is done prior to
2368 // launching the helper app. So that any existing file of that name won't be
2369 // overwritten we call CreateUnique(). Also note that we use the same
2370 // directory as originally downloaded so nsDownload can rename in place
2372 nsCOMPtr
<nsIFile
> fileToUse
;
2373 (void) GetDownloadDirectory(getter_AddRefs(fileToUse
));
2375 if (mSuggestedFileName
.IsEmpty()) {
2376 // Keep using the leafname of the temp file, since we're just starting a helper
2377 mSuggestedFileName
= mTempLeafName
;
2381 fileToUse
->Append(mSuggestedFileName
+ mTempFileExtension
);
2383 fileToUse
->Append(mSuggestedFileName
);
2386 nsresult rv
= fileToUse
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0644);
2387 if(NS_SUCCEEDED(rv
)) {
2388 mFinalFileDestination
= do_QueryInterface(fileToUse
);
2389 // launch the progress window now that the user has picked the desired action.
2390 rv
= CreateTransfer();
2391 if (NS_FAILED(rv
)) {
2395 // Cancel the download and report an error. We do not want to end up in
2396 // a state where it appears that we have a normal download that is
2397 // pointing to a file that we did not actually create.
2399 mTempFile
->GetPath(path
);
2400 SendStatusChange(kWriteError
, rv
, nullptr, path
);
2406 NS_IMETHODIMP
nsExternalAppHandler::Cancel(nsresult aReason
)
2408 NS_ENSURE_ARG(NS_FAILED(aReason
));
2416 // We are still writing to the target file. Give the saver a chance to
2417 // close the target file, then notify the transfer object if necessary in
2418 // the OnSaveComplete callback.
2419 mSaver
->Finish(aReason
);
2422 if (mStopRequestIssued
&& mTempFile
) {
2423 // This branch can only happen when the user cancels the helper app dialog
2424 // when the request has completed. The temp file has to be removed here,
2425 // because mSaver has been released at that time with the temp file left.
2426 (void)mTempFile
->Remove(false);
2429 // Notify the transfer object that the download has been canceled, if the
2430 // user has already chosen an action and we didn't notify already.
2432 NotifyTransfer(aReason
);
2436 // Break our reference cycle with the helper app dialog (set up in
2442 // Release the listener, to break the reference cycle with it (we are the
2443 // observer of the listener).
2444 mDialogProgressListener
= nullptr;
2449 void nsExternalAppHandler::ProcessAnyRefreshTags()
2451 // one last thing, try to see if the original window context supports a refresh interface...
2452 // Sometimes, when you download content that requires an external handler, there is
2453 // a refresh header associated with the download. This refresh header points to a page
2454 // the content provider wants the user to see after they download the content. How do we
2455 // pass this refresh information back to the caller? For now, try to get the refresh URI
2456 // interface. If the window context where the request originated came from supports this
2457 // then we can force it to process the refresh information (if there is any) from this channel.
2458 if (mContentContext
&& mOriginalChannel
) {
2459 nsCOMPtr
<nsIRefreshURI
> refreshHandler (do_GetInterface(mContentContext
));
2460 if (refreshHandler
) {
2461 refreshHandler
->SetupRefreshURI(mOriginalChannel
);
2463 mOriginalChannel
= nullptr;
2467 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName
, const char * aContentType
)
2469 // Search the obsolete pref strings.
2470 nsAdoptingCString prefCString
= Preferences::GetCString(prefName
);
2471 if (prefCString
.IsEmpty()) {
2472 // Default is true, if not found in the pref string.
2476 NS_UnescapeURL(prefCString
);
2477 nsACString::const_iterator start
, end
;
2478 prefCString
.BeginReading(start
);
2479 prefCString
.EndReading(end
);
2480 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType
),
2484 nsresult
nsExternalAppHandler::MaybeCloseWindow()
2486 nsCOMPtr
<nsIDOMWindow
> window
= do_GetInterface(mContentContext
);
2487 NS_ENSURE_STATE(window
);
2489 if (mShouldCloseWindow
) {
2490 // Reset the window context to the opener window so that the dependent
2491 // dialogs have a parent
2492 nsCOMPtr
<nsIDOMWindow
> opener
;
2493 window
->GetOpener(getter_AddRefs(opener
));
2496 if (opener
&& NS_SUCCEEDED(opener
->GetClosed(&isClosed
)) && !isClosed
) {
2497 mContentContext
= do_GetInterface(opener
);
2499 // Now close the old window. Do it on a timer so that we don't run
2500 // into issues trying to close the window before it has fully opened.
2501 NS_ASSERTION(!mTimer
, "mTimer was already initialized once!");
2502 mTimer
= do_CreateInstance("@mozilla.org/timer;1");
2504 return NS_ERROR_FAILURE
;
2507 mTimer
->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT
);
2508 mWindowToClose
= window
;
2516 nsExternalAppHandler::Notify(nsITimer
* timer
)
2518 NS_ASSERTION(mWindowToClose
, "No window to close after timer fired");
2520 mWindowToClose
->Close();
2521 mWindowToClose
= nullptr;
2526 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2527 // The following section contains our nsIMIMEService implementation and related methods.
2529 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2531 // nsIMIMEService methods
2532 NS_IMETHODIMP
nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString
& aMIMEType
, const nsACString
& aFileExt
, nsIMIMEInfo
**_retval
)
2534 NS_PRECONDITION(!aMIMEType
.IsEmpty() ||
2535 !aFileExt
.IsEmpty(),
2536 "Give me something to work with");
2537 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2538 PromiseFlatCString(aMIMEType
).get(), PromiseFlatCString(aFileExt
).get()));
2542 // OK... we need a type. Get one.
2543 nsAutoCString
typeToUse(aMIMEType
);
2544 if (typeToUse
.IsEmpty()) {
2545 nsresult rv
= GetTypeFromExtension(aFileExt
, typeToUse
);
2547 return NS_ERROR_NOT_AVAILABLE
;
2550 // We promise to only send lower case mime types to the OS
2551 ToLowerCase(typeToUse
);
2553 // (1) Ask the OS for a mime info
2555 *_retval
= GetMIMEInfoFromOS(typeToUse
, aFileExt
, &found
).take();
2556 LOG(("OS gave back 0x%p - found: %i\n", *_retval
, found
));
2557 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2559 return NS_ERROR_OUT_OF_MEMORY
;
2561 // (2) Now, let's see if we can find something in our datastore
2562 // This will not overwrite the OS information that interests us
2563 // (i.e. default application, default app. description)
2565 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2567 bool hasHandler
= false;
2568 (void) handlerSvc
->Exists(*_retval
, &hasHandler
);
2570 rv
= handlerSvc
->FillHandlerInfo(*_retval
, EmptyCString());
2571 LOG(("Data source: Via type: retval 0x%08x\n", rv
));
2573 rv
= NS_ERROR_NOT_AVAILABLE
;
2576 found
= found
|| NS_SUCCEEDED(rv
);
2578 if (!found
|| NS_FAILED(rv
)) {
2579 // No type match, try extension match
2580 if (!aFileExt
.IsEmpty()) {
2581 nsAutoCString overrideType
;
2582 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, overrideType
);
2583 if (NS_SUCCEEDED(rv
) && !overrideType
.IsEmpty()) {
2584 // We can't check handlerSvc->Exists() here, because we have a
2585 // overideType. That's ok, it just results in some console noise.
2586 // (If there's no handler for the override type, it throws)
2587 rv
= handlerSvc
->FillHandlerInfo(*_retval
, overrideType
);
2588 LOG(("Data source: Via ext: retval 0x%08x\n", rv
));
2589 found
= found
|| NS_SUCCEEDED(rv
);
2595 // (3) No match yet. Ask extras.
2597 rv
= NS_ERROR_FAILURE
;
2599 /* XXX Gross hack to wallpaper over the most common Win32
2600 * extension issues caused by the fix for bug 116938. See bug
2601 * 120327, comment 271 for why this is needed. Not even sure we
2602 * want to remove this once we have fixed all this stuff to work
2603 * right; any info we get from extras on this type is pretty much
2606 if (!typeToUse
.Equals(APPLICATION_OCTET_STREAM
, nsCaseInsensitiveCStringComparator()))
2608 rv
= FillMIMEInfoForMimeTypeFromExtras(typeToUse
, *_retval
);
2609 LOG(("Searched extras (by type), rv 0x%08X\n", rv
));
2610 // If that didn't work out, try file extension from extras
2611 if (NS_FAILED(rv
) && !aFileExt
.IsEmpty()) {
2612 rv
= FillMIMEInfoForExtensionFromExtras(aFileExt
, *_retval
);
2613 LOG(("Searched extras (by ext), rv 0x%08X\n", rv
));
2615 // If that still didn't work, set the file description to "ext File"
2616 if (NS_FAILED(rv
) && !aFileExt
.IsEmpty()) {
2617 // XXXzpao This should probably be localized
2618 nsAutoCString
desc(aFileExt
);
2619 desc
.AppendLiteral(" File");
2620 (*_retval
)->SetDescription(NS_ConvertASCIItoUTF16(desc
));
2621 LOG(("Falling back to 'File' file description\n"));
2625 // Finally, check if we got a file extension and if yes, if it is an
2626 // extension on the mimeinfo, in which case we want it to be the primary one
2627 if (!aFileExt
.IsEmpty()) {
2628 bool matches
= false;
2629 (*_retval
)->ExtensionExists(aFileExt
, &matches
);
2630 LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt
).get(), matches
));
2632 (*_retval
)->SetPrimaryExtension(aFileExt
);
2636 if (LOG_ENABLED()) {
2638 (*_retval
)->GetMIMEType(type
);
2641 (*_retval
)->GetPrimaryExtension(ext
);
2642 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type
.get(), ext
.get()));
2649 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromExtension(const nsACString
& aFileExt
, nsACString
& aContentType
)
2651 // OK. We want to try the following sources of mimetype information, in this order:
2652 // 1. defaultMimeEntries array
2653 // 2. User-set preferences (managed by the handler service)
2654 // 3. OS-provided information
2655 // 4. our "extras" array
2656 // 5. Information from plugins
2657 // 6. The "ext-to-type-mapping" category
2659 // Early return if called with an empty extension parameter
2660 if (aFileExt
.IsEmpty())
2661 return NS_ERROR_NOT_AVAILABLE
;
2663 nsresult rv
= NS_OK
;
2664 // First of all, check our default entries
2665 for (size_t i
= 0; i
< ArrayLength(defaultMimeEntries
); i
++)
2667 if (aFileExt
.LowerCaseEqualsASCII(defaultMimeEntries
[i
].mFileExtension
)) {
2668 aContentType
= defaultMimeEntries
[i
].mMimeType
;
2673 // Check user-set prefs
2674 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2676 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, aContentType
);
2677 if (NS_SUCCEEDED(rv
) && !aContentType
.IsEmpty())
2682 nsCOMPtr
<nsIMIMEInfo
> mi
= GetMIMEInfoFromOS(EmptyCString(), aFileExt
, &found
);
2684 return mi
->GetMIMEType(aContentType
);
2686 // Check extras array.
2687 found
= GetTypeFromExtras(aFileExt
, aContentType
);
2691 const nsCString
& flatExt
= PromiseFlatCString(aFileExt
);
2693 const char* mimeType
;
2694 nsCOMPtr
<nsIPluginHost
> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID
, &rv
));
2695 nsPluginHost
* pluginHost
= static_cast<nsPluginHost
*>(pluginHostCOM
.get());
2696 if (NS_SUCCEEDED(rv
)) {
2697 if (NS_SUCCEEDED(pluginHost
->IsPluginEnabledForExtension(flatExt
.get(), mimeType
))) {
2698 aContentType
= mimeType
;
2704 // Let's see if an extension added something
2705 nsCOMPtr
<nsICategoryManager
> catMan(do_GetService("@mozilla.org/categorymanager;1"));
2707 // The extension in the category entry is always stored as lowercase
2708 nsAutoCString
lowercaseFileExt(aFileExt
);
2709 ToLowerCase(lowercaseFileExt
);
2710 // Read the MIME type from the category entry, if available
2711 nsXPIDLCString type
;
2712 rv
= catMan
->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt
.get(),
2713 getter_Copies(type
));
2714 aContentType
= type
;
2717 rv
= NS_ERROR_NOT_AVAILABLE
;
2723 NS_IMETHODIMP
nsExternalHelperAppService::GetPrimaryExtension(const nsACString
& aMIMEType
, const nsACString
& aFileExt
, nsACString
& _retval
)
2725 NS_ENSURE_ARG(!aMIMEType
.IsEmpty());
2727 nsCOMPtr
<nsIMIMEInfo
> mi
;
2728 nsresult rv
= GetFromTypeAndExtension(aMIMEType
, aFileExt
, getter_AddRefs(mi
));
2732 return mi
->GetPrimaryExtension(_retval
);
2735 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromURI(nsIURI
*aURI
, nsACString
& aContentType
)
2737 NS_ENSURE_ARG_POINTER(aURI
);
2738 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
2739 aContentType
.Truncate();
2741 // First look for a file to use. If we have one, we just use that.
2742 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
);
2744 nsCOMPtr
<nsIFile
> file
;
2745 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2746 if (NS_SUCCEEDED(rv
)) {
2747 rv
= GetTypeFromFile(file
, aContentType
);
2748 if (NS_SUCCEEDED(rv
)) {
2749 // we got something!
2755 // Now try to get an nsIURL so we don't have to do our own parsing
2756 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
2759 rv
= url
->GetFileExtension(ext
);
2763 return NS_ERROR_NOT_AVAILABLE
;
2765 UnescapeFragment(ext
, url
, ext
);
2767 return GetTypeFromExtension(ext
, aContentType
);
2770 // no url, let's give the raw spec a shot
2771 nsAutoCString specStr
;
2772 rv
= aURI
->GetSpec(specStr
);
2775 UnescapeFragment(specStr
, aURI
, specStr
);
2777 // find the file extension (if any)
2778 int32_t extLoc
= specStr
.RFindChar('.');
2779 int32_t specLength
= specStr
.Length();
2781 extLoc
!= specLength
- 1 &&
2782 // nothing over 20 chars long can be sanely considered an
2783 // extension.... Dat dere would be just data.
2784 specLength
- extLoc
< 20)
2786 return GetTypeFromExtension(Substring(specStr
, extLoc
+ 1), aContentType
);
2789 // We found no information; say so.
2790 return NS_ERROR_NOT_AVAILABLE
;
2793 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromFile(nsIFile
* aFile
, nsACString
& aContentType
)
2795 NS_ENSURE_ARG_POINTER(aFile
);
2797 nsCOMPtr
<nsIMIMEInfo
> info
;
2799 // Get the Extension
2800 nsAutoString fileName
;
2801 rv
= aFile
->GetLeafName(fileName
);
2802 if (NS_FAILED(rv
)) return rv
;
2804 nsAutoCString fileExt
;
2805 if (!fileName
.IsEmpty())
2807 int32_t len
= fileName
.Length();
2808 for (int32_t i
= len
; i
>= 0; i
--)
2810 if (fileName
[i
] == char16_t('.'))
2812 CopyUTF16toUTF8(fileName
.get() + i
+ 1, fileExt
);
2818 if (fileExt
.IsEmpty())
2819 return NS_ERROR_FAILURE
;
2821 return GetTypeFromExtension(fileExt
, aContentType
);
2824 nsresult
nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
2825 const nsACString
& aContentType
, nsIMIMEInfo
* aMIMEInfo
)
2827 NS_ENSURE_ARG( aMIMEInfo
);
2829 NS_ENSURE_ARG( !aContentType
.IsEmpty() );
2831 // Look for default entry with matching mime type.
2832 nsAutoCString
MIMEType(aContentType
);
2833 ToLowerCase(MIMEType
);
2834 int32_t numEntries
= ArrayLength(extraMimeEntries
);
2835 for (int32_t index
= 0; index
< numEntries
; index
++)
2837 if ( MIMEType
.Equals(extraMimeEntries
[index
].mMimeType
) )
2839 // This is the one. Set attributes appropriately.
2840 aMIMEInfo
->SetFileExtensions(nsDependentCString(extraMimeEntries
[index
].mFileExtensions
));
2841 aMIMEInfo
->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries
[index
].mDescription
));
2846 return NS_ERROR_NOT_AVAILABLE
;
2849 nsresult
nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
2850 const nsACString
& aExtension
, nsIMIMEInfo
* aMIMEInfo
)
2853 bool found
= GetTypeFromExtras(aExtension
, type
);
2855 return NS_ERROR_NOT_AVAILABLE
;
2856 return FillMIMEInfoForMimeTypeFromExtras(type
, aMIMEInfo
);
2859 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString
& aExtension
, nsACString
& aMIMEType
)
2861 NS_ASSERTION(!aExtension
.IsEmpty(), "Empty aExtension parameter!");
2863 // Look for default entry with matching extension.
2864 nsDependentCString::const_iterator start
, end
, iter
;
2865 int32_t numEntries
= ArrayLength(extraMimeEntries
);
2866 for (int32_t index
= 0; index
< numEntries
; index
++)
2868 nsDependentCString
extList(extraMimeEntries
[index
].mFileExtensions
);
2869 extList
.BeginReading(start
);
2870 extList
.EndReading(end
);
2872 while (start
!= end
)
2874 FindCharInReadable(',', iter
, end
);
2875 if (Substring(start
, iter
).Equals(aExtension
,
2876 nsCaseInsensitiveCStringComparator()))
2878 aMIMEType
= extraMimeEntries
[index
].mMimeType
;