1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMIMEInfoImpl.h"
9 #include "nsReadableUtils.h"
10 #include "nsStringEnumerator.h"
12 #include "nsIFileURL.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsCURILoader.h"
16 #include "nsCExternalHandlerService.h"
17 #include "nsIExternalProtocolService.h"
18 #include "nsIObserverService.h"
19 #include "nsISupportsPrimitives.h"
20 #include "mozilla/ClearOnShutdown.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/StaticPtr.h"
23 #include "mozilla/StaticPrefs_browser.h"
24 #include "xpcpublic.h"
26 static bool sInitializedOurData
= false;
27 mozilla::StaticRefPtr
<nsIFile
> sOurAppFile
;
30 already_AddRefed
<nsIFile
> nsMIMEInfoBase::GetCanonicalExecutable(
32 nsCOMPtr
<nsIFile
> binary
= aFile
;
36 binary
->GetPath(path
);
38 if (!StringEndsWith(path
, u
".app"_ns
) && path
.RFind(u
".app/"_ns
) == -1) {
39 // This shouldn't ever happen with Firefox's own binary, tracked in
40 // sOurAppFile, but might happen when called with other files.
41 return binary
.forget();
43 nsAutoString leafName
;
45 binary
->GetLeafName(leafName
);
47 while (binary
&& !StringEndsWith(leafName
, u
".app"_ns
)) {
48 nsCOMPtr
<nsIFile
> parent
;
49 binary
->GetParent(getter_AddRefs(parent
));
50 binary
= std::move(parent
);
52 binary
->GetLeafName(leafName
);
56 return binary
.forget();
59 static void EnsureAppDetailsAvailable() {
60 if (sInitializedOurData
) {
63 sInitializedOurData
= true;
64 nsCOMPtr
<nsIFile
> binary
;
65 XRE_GetBinaryPath(getter_AddRefs(binary
));
66 sOurAppFile
= nsMIMEInfoBase::GetCanonicalExecutable(binary
);
67 ClearOnShutdown(&sOurAppFile
);
70 // nsISupports methods
71 NS_IMPL_ADDREF(nsMIMEInfoBase
)
72 NS_IMPL_RELEASE(nsMIMEInfoBase
)
74 NS_INTERFACE_MAP_BEGIN(nsMIMEInfoBase
)
75 NS_INTERFACE_MAP_ENTRY(nsIHandlerInfo
)
76 // This is only an nsIMIMEInfo if it's a MIME handler.
77 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMIMEInfo
, mClass
== eMIMEInfo
)
78 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIHandlerInfo
)
81 // nsMIMEInfoImpl methods
83 // Constructors for a MIME handler.
84 nsMIMEInfoBase::nsMIMEInfoBase(const char* aMIMEType
)
85 : mSchemeOrType(aMIMEType
),
87 mAlwaysAskBeforeHandling(
88 mozilla::StaticPrefs::
89 browser_download_always_ask_before_handling_new_types()) {}
91 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString
& aMIMEType
)
92 : mSchemeOrType(aMIMEType
),
94 mAlwaysAskBeforeHandling(
95 mozilla::StaticPrefs::
96 browser_download_always_ask_before_handling_new_types()) {}
98 // Constructor for a handler that lets the caller specify whether this is a
99 // MIME handler or a protocol handler. In the long run, these will be distinct
100 // classes (f.e. nsMIMEInfo and nsProtocolInfo), but for now we reuse this class
101 // for both and distinguish between the two kinds of handlers via the aClass
102 // argument to this method, which can be either eMIMEInfo or eProtocolInfo.
103 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString
& aType
, HandlerClass aClass
)
104 : mSchemeOrType(aType
),
106 mAlwaysAskBeforeHandling(
107 mozilla::StaticPrefs::
108 browser_download_always_ask_before_handling_new_types() ||
109 aClass
!= eMIMEInfo
) {}
111 nsMIMEInfoBase::~nsMIMEInfoBase() {}
114 nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator
** aResult
) {
115 return NS_NewUTF8StringEnumerator(aResult
, &mExtensions
, this);
119 nsMIMEInfoBase::ExtensionExists(const nsACString
& aExtension
, bool* _retval
) {
120 MOZ_ASSERT(!aExtension
.IsEmpty(), "no extension");
121 *_retval
= mExtensions
.Contains(aExtension
,
122 nsCaseInsensitiveCStringArrayComparator());
127 nsMIMEInfoBase::GetPrimaryExtension(nsACString
& _retval
) {
128 if (!mExtensions
.Length()) {
130 return NS_ERROR_NOT_INITIALIZED
;
132 _retval
= mExtensions
[0];
137 nsMIMEInfoBase::SetPrimaryExtension(const nsACString
& aExtension
) {
138 if (MOZ_UNLIKELY(aExtension
.IsEmpty())) {
139 MOZ_ASSERT(false, "No extension");
140 return NS_ERROR_INVALID_ARG
;
142 int32_t i
= mExtensions
.IndexOf(aExtension
, 0,
143 nsCaseInsensitiveCStringArrayComparator());
145 mExtensions
.RemoveElementAt(i
);
147 mExtensions
.InsertElementAt(0, aExtension
);
148 mIsDefaultAppInfoFresh
= false;
152 void nsMIMEInfoBase::AddUniqueExtension(const nsACString
& aExtension
) {
153 if (mExtensions
.IsEmpty()) {
154 mIsDefaultAppInfoFresh
= false;
156 if (!aExtension
.IsEmpty() &&
157 !mExtensions
.Contains(aExtension
,
158 nsCaseInsensitiveCStringArrayComparator())) {
159 mExtensions
.AppendElement(aExtension
);
164 nsMIMEInfoBase::AppendExtension(const nsACString
& aExtension
) {
165 MOZ_ASSERT(!aExtension
.IsEmpty(), "No extension");
166 AddUniqueExtension(aExtension
);
171 nsMIMEInfoBase::GetType(nsACString
& aType
) {
172 if (mSchemeOrType
.IsEmpty()) return NS_ERROR_NOT_INITIALIZED
;
174 aType
= mSchemeOrType
;
179 nsMIMEInfoBase::GetMIMEType(nsACString
& aMIMEType
) {
180 if (mSchemeOrType
.IsEmpty()) return NS_ERROR_NOT_INITIALIZED
;
182 aMIMEType
= mSchemeOrType
;
187 nsMIMEInfoBase::GetDescription(nsAString
& aDescription
) {
188 aDescription
= mDescription
;
193 nsMIMEInfoBase::SetDescription(const nsAString
& aDescription
) {
194 mDescription
= aDescription
;
199 nsMIMEInfoBase::Equals(nsIMIMEInfo
* aMIMEInfo
, bool* _retval
) {
200 if (!aMIMEInfo
) return NS_ERROR_NULL_POINTER
;
203 nsresult rv
= aMIMEInfo
->GetMIMEType(type
);
204 if (NS_FAILED(rv
)) return rv
;
206 *_retval
= mSchemeOrType
.Equals(type
);
212 nsMIMEInfoBase::SetFileExtensions(const nsACString
& aExtensions
) {
214 nsACString::const_iterator start
, end
;
215 aExtensions
.BeginReading(start
);
216 aExtensions
.EndReading(end
);
217 while (start
!= end
) {
218 nsACString::const_iterator cursor
= start
;
219 mozilla::Unused
<< FindCharInReadable(',', cursor
, end
);
220 AddUniqueExtension(Substring(start
, cursor
));
221 // If a comma was found, skip it for the next search.
222 start
= cursor
!= end
? ++cursor
: cursor
;
228 nsMIMEInfoBase::GetDefaultDescription(nsAString
& aDefaultDescription
) {
229 aDefaultDescription
= mDefaultAppDescription
;
234 nsMIMEInfoBase::GetDefaultExecutable(nsIFile
** aExecutable
) {
235 return NS_ERROR_NOT_IMPLEMENTED
;
239 nsMIMEInfoBase::GetPreferredApplicationHandler(
240 nsIHandlerApp
** aPreferredAppHandler
) {
241 *aPreferredAppHandler
= mPreferredApplication
;
242 NS_IF_ADDREF(*aPreferredAppHandler
);
247 nsMIMEInfoBase::SetPreferredApplicationHandler(
248 nsIHandlerApp
* aPreferredAppHandler
) {
249 mPreferredApplication
= aPreferredAppHandler
;
254 nsMIMEInfoBase::GetPossibleApplicationHandlers(
255 nsIMutableArray
** aPossibleAppHandlers
) {
256 if (!mPossibleApplications
)
257 mPossibleApplications
= do_CreateInstance(NS_ARRAY_CONTRACTID
);
259 if (!mPossibleApplications
) return NS_ERROR_OUT_OF_MEMORY
;
261 *aPossibleAppHandlers
= mPossibleApplications
;
262 NS_IF_ADDREF(*aPossibleAppHandlers
);
267 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction
* aPreferredAction
) {
268 *aPreferredAction
= mPreferredAction
;
273 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction
) {
274 mPreferredAction
= aPreferredAction
;
279 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk
) {
280 *aAlwaysAsk
= mAlwaysAskBeforeHandling
;
286 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk
) {
287 mAlwaysAskBeforeHandling
= aAlwaysAsk
;
292 nsresult
nsMIMEInfoBase::GetLocalFileFromURI(nsIURI
* aURI
, nsIFile
** aFile
) {
295 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
, &rv
);
300 nsCOMPtr
<nsIFile
> file
;
301 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
311 nsMIMEInfoBase::LaunchWithFile(nsIFile
* aFile
) {
314 // it doesn't make any sense to call this on protocol handlers
315 NS_ASSERTION(mClass
== eMIMEInfo
,
316 "nsMIMEInfoBase should have mClass == eMIMEInfo");
318 if (AutomationOnlyCheckIfLaunchStubbed(aFile
)) {
322 if (mPreferredAction
== useSystemDefault
) {
323 return LaunchDefaultWithFile(aFile
);
326 if (mPreferredAction
== useHelperApp
) {
327 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
329 // at the moment, we only know how to hand files off to local handlers
330 nsCOMPtr
<nsILocalHandlerApp
> localHandler
=
331 do_QueryInterface(mPreferredApplication
, &rv
);
332 NS_ENSURE_SUCCESS(rv
, rv
);
334 nsCOMPtr
<nsIFile
> executable
;
335 rv
= localHandler
->GetExecutable(getter_AddRefs(executable
));
336 NS_ENSURE_SUCCESS(rv
, rv
);
338 return LaunchWithIProcess(executable
, aFile
->NativePath());
341 return NS_ERROR_INVALID_ARG
;
344 bool nsMIMEInfoBase::AutomationOnlyCheckIfLaunchStubbed(nsIFile
* aFile
) {
345 // This is pretty gross and hacky, but otherwise we can't automatically
346 // test this, and we keep breaking edgecases around this, so...
347 if (!xpc::IsInAutomation()) {
351 aFile
->GetPath(path
);
352 nsCOMPtr
<nsISupportsPRBool
> canOpen
=
353 do_CreateInstance("@mozilla.org/supports-PRBool;1");
354 canOpen
->SetData(true);
355 nsCOMPtr
<nsIObserverService
> observerService
=
356 mozilla::services::GetObserverService();
357 observerService
->NotifyObservers(canOpen
, "test-only-opening-downloaded-file",
360 canOpen
->GetData(&data
);
365 nsMIMEInfoBase::LaunchWithURI(nsIURI
* aURI
,
366 mozilla::dom::BrowsingContext
* aBrowsingContext
) {
367 // This is only being called with protocol handlers
368 NS_ASSERTION(mClass
== eProtocolInfo
,
369 "nsMIMEInfoBase should be a protocol handler");
371 if (mPreferredAction
== useSystemDefault
) {
372 // First, ensure we're not accidentally going to call ourselves.
373 // That'd lead to an infinite loop (see bug 215554).
374 nsCOMPtr
<nsIExternalProtocolService
> extProtService
=
375 do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID
);
376 if (!extProtService
) {
377 return NS_ERROR_FAILURE
;
379 nsAutoCString scheme
;
380 aURI
->GetScheme(scheme
);
381 bool isDefault
= false;
383 extProtService
->IsCurrentAppOSDefaultForProtocol(scheme
, &isDefault
);
384 if (NS_SUCCEEDED(rv
) && isDefault
) {
385 // Lie. This will trip the handler service into showing a dialog asking
386 // what the user wants.
387 return NS_ERROR_FILE_NOT_FOUND
;
389 return LoadUriInternal(aURI
);
392 if (mPreferredAction
== useHelperApp
) {
393 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
395 EnsureAppDetailsAvailable();
396 nsCOMPtr
<nsILocalHandlerApp
> localPreferredHandler
=
397 do_QueryInterface(mPreferredApplication
);
398 if (localPreferredHandler
) {
399 nsCOMPtr
<nsIFile
> executable
;
400 localPreferredHandler
->GetExecutable(getter_AddRefs(executable
));
401 executable
= GetCanonicalExecutable(executable
);
402 bool isOurExecutable
= false;
404 NS_FAILED(executable
->Equals(sOurAppFile
, &isOurExecutable
)) ||
406 // Lie. This will trip the handler service into showing a dialog asking
407 // what the user wants.
408 return NS_ERROR_FILE_NOT_FOUND
;
411 return mPreferredApplication
->LaunchWithURI(aURI
, aBrowsingContext
);
414 return NS_ERROR_INVALID_ARG
;
417 void nsMIMEInfoBase::CopyBasicDataTo(nsMIMEInfoBase
* aOther
) {
418 aOther
->mSchemeOrType
= mSchemeOrType
;
419 aOther
->mDefaultAppDescription
= mDefaultAppDescription
;
420 aOther
->mExtensions
= mExtensions
.Clone();
424 already_AddRefed
<nsIProcess
> nsMIMEInfoBase::InitProcess(nsIFile
* aApp
,
426 NS_ASSERTION(aApp
, "Unexpected null pointer, fix caller");
428 nsCOMPtr
<nsIProcess
> process
=
429 do_CreateInstance(NS_PROCESS_CONTRACTID
, aResult
);
430 if (NS_FAILED(*aResult
)) return nullptr;
432 *aResult
= process
->Init(aApp
);
433 if (NS_FAILED(*aResult
)) return nullptr;
435 return process
.forget();
439 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
440 const nsCString
& aArg
) {
442 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
443 if (NS_FAILED(rv
)) return rv
;
445 const char* string
= aArg
.get();
447 return process
->Run(false, &string
, 1);
451 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
452 const nsString
& aArg
) {
454 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
455 if (NS_FAILED(rv
)) return rv
;
457 const char16_t
* string
= aArg
.get();
459 return process
->Runw(false, &string
, 1);
463 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
, const int aArgc
,
464 const char16_t
** aArgv
) {
466 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
471 return process
->Runw(false, aArgv
, aArgc
);
474 // nsMIMEInfoImpl implementation
476 nsMIMEInfoImpl::GetDefaultDescription(nsAString
& aDefaultDescription
) {
477 if (mDefaultAppDescription
.IsEmpty()) {
478 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
480 // Don't want to cache this, just in case someone resets the app
481 // without changing the description....
482 defaultApp
->GetLeafName(aDefaultDescription
);
486 aDefaultDescription
= mDefaultAppDescription
;
491 NS_IMETHODIMP
nsMIMEInfoImpl::GetDefaultExecutable(nsIFile
** aExecutable
) {
492 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
494 defaultApp
.forget(aExecutable
);
498 return NS_ERROR_FAILURE
;
502 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval
) {
503 *_retval
= !mDefaultAppDescription
.IsEmpty();
504 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
507 *_retval
= NS_SUCCEEDED(defaultApp
->Exists(&exists
)) && exists
;
513 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval
) {
515 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
517 // Determine if the default executable is our executable.
518 EnsureAppDetailsAvailable();
520 nsresult rv
= defaultApp
->Equals(sOurAppFile
, &isSame
);
529 nsresult
nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile
* aFile
) {
530 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
532 return NS_ERROR_FILE_NOT_FOUND
;
535 return LaunchWithIProcess(defaultApp
, aFile
->NativePath());
539 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray
** _retval
) {
540 return NS_ERROR_NOT_IMPLEMENTED
;