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 "nsCURILoader.h"
15 #include "nsCExternalHandlerService.h"
16 #include "nsIExternalProtocolService.h"
17 #include "nsIObserverService.h"
18 #include "mozilla/StaticPtr.h"
19 #include "xpcpublic.h"
21 static bool sInitializedOurData
= false;
22 StaticRefPtr
<nsIFile
> sOurAppFile
;
25 already_AddRefed
<nsIFile
> nsMIMEInfoBase::GetCanonicalExecutable(
27 nsCOMPtr
<nsIFile
> binary
= aFile
;
31 binary
->GetPath(path
);
33 if (!StringEndsWith(path
, u
".app"_ns
) && path
.RFind(u
".app/"_ns
) == -1) {
34 // This shouldn't ever happen with Firefox's own binary, tracked in
35 // sOurAppFile, but might happen when called with other files.
36 return binary
.forget();
38 nsAutoString leafName
;
40 binary
->GetLeafName(leafName
);
42 while (binary
&& !StringEndsWith(leafName
, u
".app"_ns
)) {
43 nsCOMPtr
<nsIFile
> parent
;
44 binary
->GetParent(getter_AddRefs(parent
));
45 binary
= std::move(parent
);
47 binary
->GetLeafName(leafName
);
51 return binary
.forget();
54 static void EnsureAppDetailsAvailable() {
55 if (sInitializedOurData
) {
58 sInitializedOurData
= true;
59 nsCOMPtr
<nsIFile
> binary
;
60 XRE_GetBinaryPath(getter_AddRefs(binary
));
61 sOurAppFile
= nsMIMEInfoBase::GetCanonicalExecutable(binary
);
62 ClearOnShutdown(&sOurAppFile
);
65 // nsISupports methods
66 NS_IMPL_ADDREF(nsMIMEInfoBase
)
67 NS_IMPL_RELEASE(nsMIMEInfoBase
)
69 NS_INTERFACE_MAP_BEGIN(nsMIMEInfoBase
)
70 NS_INTERFACE_MAP_ENTRY(nsIHandlerInfo
)
71 // This is only an nsIMIMEInfo if it's a MIME handler.
72 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMIMEInfo
, mClass
== eMIMEInfo
)
73 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIHandlerInfo
)
76 // nsMIMEInfoImpl methods
78 // Constructors for a MIME handler.
79 nsMIMEInfoBase::nsMIMEInfoBase(const char* aMIMEType
)
80 : mSchemeOrType(aMIMEType
),
82 mPreferredAction(nsIMIMEInfo::saveToDisk
),
83 mAlwaysAskBeforeHandling(
84 !StaticPrefs::browser_download_improvements_to_download_panel()) {}
86 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString
& aMIMEType
)
87 : mSchemeOrType(aMIMEType
),
89 mPreferredAction(nsIMIMEInfo::saveToDisk
),
90 mAlwaysAskBeforeHandling(
91 !StaticPrefs::browser_download_improvements_to_download_panel()) {}
93 // Constructor for a handler that lets the caller specify whether this is a
94 // MIME handler or a protocol handler. In the long run, these will be distinct
95 // classes (f.e. nsMIMEInfo and nsProtocolInfo), but for now we reuse this class
96 // for both and distinguish between the two kinds of handlers via the aClass
97 // argument to this method, which can be either eMIMEInfo or eProtocolInfo.
98 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString
& aType
, HandlerClass aClass
)
99 : mSchemeOrType(aType
),
101 mPreferredAction(nsIMIMEInfo::saveToDisk
),
102 mAlwaysAskBeforeHandling(
103 !StaticPrefs::browser_download_improvements_to_download_panel() ||
104 aClass
!= eMIMEInfo
) {}
106 nsMIMEInfoBase::~nsMIMEInfoBase() {}
109 nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator
** aResult
) {
110 return NS_NewUTF8StringEnumerator(aResult
, &mExtensions
, this);
114 nsMIMEInfoBase::ExtensionExists(const nsACString
& aExtension
, bool* _retval
) {
115 MOZ_ASSERT(!aExtension
.IsEmpty(), "no extension");
116 *_retval
= mExtensions
.Contains(aExtension
,
117 nsCaseInsensitiveCStringArrayComparator());
122 nsMIMEInfoBase::GetPrimaryExtension(nsACString
& _retval
) {
123 if (!mExtensions
.Length()) {
125 return NS_ERROR_NOT_INITIALIZED
;
127 _retval
= mExtensions
[0];
132 nsMIMEInfoBase::SetPrimaryExtension(const nsACString
& aExtension
) {
133 if (MOZ_UNLIKELY(aExtension
.IsEmpty())) {
134 MOZ_ASSERT(false, "No extension");
135 return NS_ERROR_INVALID_ARG
;
137 int32_t i
= mExtensions
.IndexOf(aExtension
, 0,
138 nsCaseInsensitiveCStringArrayComparator());
140 mExtensions
.RemoveElementAt(i
);
142 mExtensions
.InsertElementAt(0, aExtension
);
146 void nsMIMEInfoBase::AddUniqueExtension(const nsACString
& aExtension
) {
147 if (!aExtension
.IsEmpty() &&
148 !mExtensions
.Contains(aExtension
,
149 nsCaseInsensitiveCStringArrayComparator())) {
150 mExtensions
.AppendElement(aExtension
);
155 nsMIMEInfoBase::AppendExtension(const nsACString
& aExtension
) {
156 MOZ_ASSERT(!aExtension
.IsEmpty(), "No extension");
157 AddUniqueExtension(aExtension
);
162 nsMIMEInfoBase::GetType(nsACString
& aType
) {
163 if (mSchemeOrType
.IsEmpty()) return NS_ERROR_NOT_INITIALIZED
;
165 aType
= mSchemeOrType
;
170 nsMIMEInfoBase::GetMIMEType(nsACString
& aMIMEType
) {
171 if (mSchemeOrType
.IsEmpty()) return NS_ERROR_NOT_INITIALIZED
;
173 aMIMEType
= mSchemeOrType
;
178 nsMIMEInfoBase::GetDescription(nsAString
& aDescription
) {
179 aDescription
= mDescription
;
184 nsMIMEInfoBase::SetDescription(const nsAString
& aDescription
) {
185 mDescription
= aDescription
;
190 nsMIMEInfoBase::Equals(nsIMIMEInfo
* aMIMEInfo
, bool* _retval
) {
191 if (!aMIMEInfo
) return NS_ERROR_NULL_POINTER
;
194 nsresult rv
= aMIMEInfo
->GetMIMEType(type
);
195 if (NS_FAILED(rv
)) return rv
;
197 *_retval
= mSchemeOrType
.Equals(type
);
203 nsMIMEInfoBase::SetFileExtensions(const nsACString
& aExtensions
) {
205 nsACString::const_iterator start
, end
;
206 aExtensions
.BeginReading(start
);
207 aExtensions
.EndReading(end
);
208 while (start
!= end
) {
209 nsACString::const_iterator cursor
= start
;
210 mozilla::Unused
<< FindCharInReadable(',', cursor
, end
);
211 AddUniqueExtension(Substring(start
, cursor
));
212 // If a comma was found, skip it for the next search.
213 start
= cursor
!= end
? ++cursor
: cursor
;
219 nsMIMEInfoBase::GetDefaultDescription(nsAString
& aDefaultDescription
) {
220 aDefaultDescription
= mDefaultAppDescription
;
225 nsMIMEInfoBase::GetPreferredApplicationHandler(
226 nsIHandlerApp
** aPreferredAppHandler
) {
227 *aPreferredAppHandler
= mPreferredApplication
;
228 NS_IF_ADDREF(*aPreferredAppHandler
);
233 nsMIMEInfoBase::SetPreferredApplicationHandler(
234 nsIHandlerApp
* aPreferredAppHandler
) {
235 mPreferredApplication
= aPreferredAppHandler
;
240 nsMIMEInfoBase::GetPossibleApplicationHandlers(
241 nsIMutableArray
** aPossibleAppHandlers
) {
242 if (!mPossibleApplications
)
243 mPossibleApplications
= do_CreateInstance(NS_ARRAY_CONTRACTID
);
245 if (!mPossibleApplications
) return NS_ERROR_OUT_OF_MEMORY
;
247 *aPossibleAppHandlers
= mPossibleApplications
;
248 NS_IF_ADDREF(*aPossibleAppHandlers
);
253 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction
* aPreferredAction
) {
254 *aPreferredAction
= mPreferredAction
;
259 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction
) {
260 mPreferredAction
= aPreferredAction
;
265 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk
) {
266 *aAlwaysAsk
= mAlwaysAskBeforeHandling
;
272 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk
) {
273 mAlwaysAskBeforeHandling
= aAlwaysAsk
;
278 nsresult
nsMIMEInfoBase::GetLocalFileFromURI(nsIURI
* aURI
, nsIFile
** aFile
) {
281 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
, &rv
);
286 nsCOMPtr
<nsIFile
> file
;
287 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
297 nsMIMEInfoBase::LaunchWithFile(nsIFile
* aFile
) {
300 // it doesn't make any sense to call this on protocol handlers
301 NS_ASSERTION(mClass
== eMIMEInfo
,
302 "nsMIMEInfoBase should have mClass == eMIMEInfo");
304 if (AutomationOnlyCheckIfLaunchStubbed(aFile
)) {
308 if (mPreferredAction
== useSystemDefault
) {
309 return LaunchDefaultWithFile(aFile
);
312 if (mPreferredAction
== useHelperApp
) {
313 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
315 // at the moment, we only know how to hand files off to local handlers
316 nsCOMPtr
<nsILocalHandlerApp
> localHandler
=
317 do_QueryInterface(mPreferredApplication
, &rv
);
318 NS_ENSURE_SUCCESS(rv
, rv
);
320 nsCOMPtr
<nsIFile
> executable
;
321 rv
= localHandler
->GetExecutable(getter_AddRefs(executable
));
322 NS_ENSURE_SUCCESS(rv
, rv
);
324 return LaunchWithIProcess(executable
, aFile
->NativePath());
327 return NS_ERROR_INVALID_ARG
;
330 bool nsMIMEInfoBase::AutomationOnlyCheckIfLaunchStubbed(nsIFile
* aFile
) {
331 // This is pretty gross and hacky, but otherwise we can't automatically
332 // test this, and we keep breaking edgecases around this, so...
333 if (!xpc::IsInAutomation()) {
337 aFile
->GetPath(path
);
338 nsCOMPtr
<nsISupportsPRBool
> canOpen
=
339 do_CreateInstance("@mozilla.org/supports-PRBool;1");
340 canOpen
->SetData(true);
341 nsCOMPtr
<nsIObserverService
> observerService
=
342 mozilla::services::GetObserverService();
343 observerService
->NotifyObservers(canOpen
, "test-only-opening-downloaded-file",
346 canOpen
->GetData(&data
);
351 nsMIMEInfoBase::LaunchWithURI(nsIURI
* aURI
, BrowsingContext
* aBrowsingContext
) {
352 // This is only being called with protocol handlers
353 NS_ASSERTION(mClass
== eProtocolInfo
,
354 "nsMIMEInfoBase should be a protocol handler");
356 if (mPreferredAction
== useSystemDefault
) {
357 // First, ensure we're not accidentally going to call ourselves.
358 // That'd lead to an infinite loop (see bug 215554).
359 nsCOMPtr
<nsIExternalProtocolService
> extProtService
=
360 do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID
);
361 if (!extProtService
) {
362 return NS_ERROR_FAILURE
;
364 nsAutoCString scheme
;
365 aURI
->GetScheme(scheme
);
366 bool isDefault
= false;
368 extProtService
->IsCurrentAppOSDefaultForProtocol(scheme
, &isDefault
);
369 if (NS_SUCCEEDED(rv
) && isDefault
) {
370 // Lie. This will trip the handler service into showing a dialog asking
371 // what the user wants.
372 return NS_ERROR_FILE_NOT_FOUND
;
374 return LoadUriInternal(aURI
);
377 if (mPreferredAction
== useHelperApp
) {
378 if (!mPreferredApplication
) return NS_ERROR_FILE_NOT_FOUND
;
380 EnsureAppDetailsAvailable();
381 nsCOMPtr
<nsILocalHandlerApp
> localPreferredHandler
=
382 do_QueryInterface(mPreferredApplication
);
383 if (localPreferredHandler
) {
384 nsCOMPtr
<nsIFile
> executable
;
385 localPreferredHandler
->GetExecutable(getter_AddRefs(executable
));
386 executable
= GetCanonicalExecutable(executable
);
387 bool isOurExecutable
= false;
389 NS_FAILED(executable
->Equals(sOurAppFile
, &isOurExecutable
)) ||
391 // Lie. This will trip the handler service into showing a dialog asking
392 // what the user wants.
393 return NS_ERROR_FILE_NOT_FOUND
;
396 return mPreferredApplication
->LaunchWithURI(aURI
, aBrowsingContext
);
399 return NS_ERROR_INVALID_ARG
;
402 void nsMIMEInfoBase::CopyBasicDataTo(nsMIMEInfoBase
* aOther
) {
403 aOther
->mSchemeOrType
= mSchemeOrType
;
404 aOther
->mDefaultAppDescription
= mDefaultAppDescription
;
405 aOther
->mExtensions
= mExtensions
.Clone();
409 already_AddRefed
<nsIProcess
> nsMIMEInfoBase::InitProcess(nsIFile
* aApp
,
411 NS_ASSERTION(aApp
, "Unexpected null pointer, fix caller");
413 nsCOMPtr
<nsIProcess
> process
=
414 do_CreateInstance(NS_PROCESS_CONTRACTID
, aResult
);
415 if (NS_FAILED(*aResult
)) return nullptr;
417 *aResult
= process
->Init(aApp
);
418 if (NS_FAILED(*aResult
)) return nullptr;
420 return process
.forget();
424 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
425 const nsCString
& aArg
) {
427 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
428 if (NS_FAILED(rv
)) return rv
;
430 const char* string
= aArg
.get();
432 return process
->Run(false, &string
, 1);
436 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
,
437 const nsString
& aArg
) {
439 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
440 if (NS_FAILED(rv
)) return rv
;
442 const char16_t
* string
= aArg
.get();
444 return process
->Runw(false, &string
, 1);
448 nsresult
nsMIMEInfoBase::LaunchWithIProcess(nsIFile
* aApp
, const int aArgc
,
449 const char16_t
** aArgv
) {
451 nsCOMPtr
<nsIProcess
> process
= InitProcess(aApp
, &rv
);
456 return process
->Runw(false, aArgv
, aArgc
);
459 // nsMIMEInfoImpl implementation
461 nsMIMEInfoImpl::GetDefaultDescription(nsAString
& aDefaultDescription
) {
462 if (mDefaultAppDescription
.IsEmpty() && mDefaultApplication
) {
463 // Don't want to cache this, just in case someone resets the app
464 // without changing the description....
465 mDefaultApplication
->GetLeafName(aDefaultDescription
);
467 aDefaultDescription
= mDefaultAppDescription
;
474 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval
) {
475 *_retval
= !mDefaultAppDescription
.IsEmpty();
476 if (mDefaultApplication
) {
478 *_retval
= NS_SUCCEEDED(mDefaultApplication
->Exists(&exists
)) && exists
;
484 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval
) {
486 if (mDefaultApplication
) {
487 // Determine if the default executable is our executable.
488 EnsureAppDetailsAvailable();
490 nsresult rv
= mDefaultApplication
->Equals(sOurAppFile
, &isSame
);
499 nsresult
nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile
* aFile
) {
500 if (!mDefaultApplication
) return NS_ERROR_FILE_NOT_FOUND
;
502 return LaunchWithIProcess(mDefaultApplication
, aFile
->NativePath());
506 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray
** _retval
) {
507 return NS_ERROR_NOT_IMPLEMENTED
;