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::GetPreferredApplicationHandler(
235 nsIHandlerApp
** aPreferredAppHandler
) {
236 *aPreferredAppHandler
= mPreferredApplication
;
237 NS_IF_ADDREF(*aPreferredAppHandler
);
242 nsMIMEInfoBase::SetPreferredApplicationHandler(
243 nsIHandlerApp
* aPreferredAppHandler
) {
244 mPreferredApplication
= aPreferredAppHandler
;
249 nsMIMEInfoBase::GetPossibleApplicationHandlers(
250 nsIMutableArray
** aPossibleAppHandlers
) {
251 if (!mPossibleApplications
)
252 mPossibleApplications
= do_CreateInstance(NS_ARRAY_CONTRACTID
);
254 if (!mPossibleApplications
) return NS_ERROR_OUT_OF_MEMORY
;
256 *aPossibleAppHandlers
= mPossibleApplications
;
257 NS_IF_ADDREF(*aPossibleAppHandlers
);
262 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction
* aPreferredAction
) {
263 *aPreferredAction
= mPreferredAction
;
268 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction
) {
269 mPreferredAction
= aPreferredAction
;
274 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk
) {
275 *aAlwaysAsk
= mAlwaysAskBeforeHandling
;
281 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk
) {
282 mAlwaysAskBeforeHandling
= aAlwaysAsk
;
287 nsresult
nsMIMEInfoBase::GetLocalFileFromURI(nsIURI
* aURI
, nsIFile
** aFile
) {
290 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
, &rv
);
295 nsCOMPtr
<nsIFile
> file
;
296 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
306 nsMIMEInfoBase::LaunchWithFile(nsIFile
* aFile
) {
309 // it doesn't make any sense to call this on protocol handlers
310 NS_ASSERTION(mClass
== eMIMEInfo
,
311 "nsMIMEInfoBase should have mClass == eMIMEInfo");
313 if (AutomationOnlyCheckIfLaunchStubbed(aFile
)) {
317 if (mPreferredAction
== useSystemDefault
) {
318 return LaunchDefaultWithFile(aFile
);
321 if (mPreferredAction
== useHelperApp
) {
322 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
324 // at the moment, we only know how to hand files off to local handlers
325 nsCOMPtr
<nsILocalHandlerApp
> localHandler
=
326 do_QueryInterface(mPreferredApplication
, &rv
);
327 NS_ENSURE_SUCCESS(rv
, rv
);
329 nsCOMPtr
<nsIFile
> executable
;
330 rv
= localHandler
->GetExecutable(getter_AddRefs(executable
));
331 NS_ENSURE_SUCCESS(rv
, rv
);
333 return LaunchWithIProcess(executable
, aFile
->NativePath());
336 return NS_ERROR_INVALID_ARG
;
339 bool nsMIMEInfoBase::AutomationOnlyCheckIfLaunchStubbed(nsIFile
* aFile
) {
340 // This is pretty gross and hacky, but otherwise we can't automatically
341 // test this, and we keep breaking edgecases around this, so...
342 if (!xpc::IsInAutomation()) {
346 aFile
->GetPath(path
);
347 nsCOMPtr
<nsISupportsPRBool
> canOpen
=
348 do_CreateInstance("@mozilla.org/supports-PRBool;1");
349 canOpen
->SetData(true);
350 nsCOMPtr
<nsIObserverService
> observerService
=
351 mozilla::services::GetObserverService();
352 observerService
->NotifyObservers(canOpen
, "test-only-opening-downloaded-file",
355 canOpen
->GetData(&data
);
360 nsMIMEInfoBase::LaunchWithURI(nsIURI
* aURI
,
361 mozilla::dom::BrowsingContext
* aBrowsingContext
) {
362 // This is only being called with protocol handlers
363 NS_ASSERTION(mClass
== eProtocolInfo
,
364 "nsMIMEInfoBase should be a protocol handler");
366 if (mPreferredAction
== useSystemDefault
) {
367 // First, ensure we're not accidentally going to call ourselves.
368 // That'd lead to an infinite loop (see bug 215554).
369 nsCOMPtr
<nsIExternalProtocolService
> extProtService
=
370 do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID
);
371 if (!extProtService
) {
372 return NS_ERROR_FAILURE
;
374 nsAutoCString scheme
;
375 aURI
->GetScheme(scheme
);
376 bool isDefault
= false;
378 extProtService
->IsCurrentAppOSDefaultForProtocol(scheme
, &isDefault
);
379 if (NS_SUCCEEDED(rv
) && isDefault
) {
380 // Lie. This will trip the handler service into showing a dialog asking
381 // what the user wants.
382 return NS_ERROR_FILE_NOT_FOUND
;
384 return LoadUriInternal(aURI
);
387 if (mPreferredAction
== useHelperApp
) {
388 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
390 EnsureAppDetailsAvailable();
391 nsCOMPtr
<nsILocalHandlerApp
> localPreferredHandler
=
392 do_QueryInterface(mPreferredApplication
);
393 if (localPreferredHandler
) {
394 nsCOMPtr
<nsIFile
> executable
;
395 localPreferredHandler
->GetExecutable(getter_AddRefs(executable
));
396 executable
= GetCanonicalExecutable(executable
);
397 bool isOurExecutable
= false;
399 NS_FAILED(executable
->Equals(sOurAppFile
, &isOurExecutable
)) ||
401 // Lie. This will trip the handler service into showing a dialog asking
402 // what the user wants.
403 return NS_ERROR_FILE_NOT_FOUND
;
406 return mPreferredApplication
->LaunchWithURI(aURI
, aBrowsingContext
);
409 return NS_ERROR_INVALID_ARG
;
412 void nsMIMEInfoBase::CopyBasicDataTo(nsMIMEInfoBase
* aOther
) {
413 aOther
->mSchemeOrType
= mSchemeOrType
;
414 aOther
->mDefaultAppDescription
= mDefaultAppDescription
;
415 aOther
->mExtensions
= mExtensions
.Clone();
419 already_AddRefed
<nsIProcess
> nsMIMEInfoBase::InitProcess(nsIFile
* aApp
,
421 NS_ASSERTION(aApp
, "Unexpected null pointer, fix caller");
423 nsCOMPtr
<nsIProcess
> process
=
424 do_CreateInstance(NS_PROCESS_CONTRACTID
, aResult
);
425 if (NS_FAILED(*aResult
)) return nullptr;
427 *aResult
= process
->Init(aApp
);
428 if (NS_FAILED(*aResult
)) return nullptr;
430 return process
.forget();
434 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
435 const nsCString
& aArg
) {
437 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
438 if (NS_FAILED(rv
)) return rv
;
440 const char* string
= aArg
.get();
442 return process
->Run(false, &string
, 1);
446 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
447 const nsString
& aArg
) {
449 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
450 if (NS_FAILED(rv
)) return rv
;
452 const char16_t
* string
= aArg
.get();
454 return process
->Runw(false, &string
, 1);
458 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
, const int aArgc
,
459 const char16_t
** aArgv
) {
461 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
466 return process
->Runw(false, aArgv
, aArgc
);
469 // nsMIMEInfoImpl implementation
471 nsMIMEInfoImpl::GetDefaultDescription(nsAString
& aDefaultDescription
) {
472 if (mDefaultAppDescription
.IsEmpty()) {
473 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
475 // Don't want to cache this, just in case someone resets the app
476 // without changing the description....
477 defaultApp
->GetLeafName(aDefaultDescription
);
481 aDefaultDescription
= mDefaultAppDescription
;
487 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval
) {
488 *_retval
= !mDefaultAppDescription
.IsEmpty();
489 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
492 *_retval
= NS_SUCCEEDED(defaultApp
->Exists(&exists
)) && exists
;
498 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval
) {
500 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
502 // Determine if the default executable is our executable.
503 EnsureAppDetailsAvailable();
505 nsresult rv
= defaultApp
->Equals(sOurAppFile
, &isSame
);
514 nsresult
nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile
* aFile
) {
515 nsCOMPtr
<nsIFile
> defaultApp
= GetDefaultApplication();
517 return NS_ERROR_FILE_NOT_FOUND
;
520 return LaunchWithIProcess(defaultApp
, aFile
->NativePath());
524 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray
** _retval
) {
525 return NS_ERROR_NOT_IMPLEMENTED
;