no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / uriloader / exthandler / nsMIMEInfoImpl.cpp
blob378acb160bb4ba92418a356ff2504df94d889bac
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"
8 #include "nsString.h"
9 #include "nsReadableUtils.h"
10 #include "nsStringEnumerator.h"
11 #include "nsIFile.h"
12 #include "nsIFileURL.h"
13 #include "nsEscape.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;
29 /* static */
30 already_AddRefed<nsIFile> nsMIMEInfoBase::GetCanonicalExecutable(
31 nsIFile* aFile) {
32 nsCOMPtr<nsIFile> binary = aFile;
33 #ifdef XP_MACOSX
34 nsAutoString path;
35 if (binary) {
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;
44 if (binary) {
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);
51 if (binary) {
52 binary->GetLeafName(leafName);
55 #endif
56 return binary.forget();
59 static void EnsureAppDetailsAvailable() {
60 if (sInitializedOurData) {
61 return;
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)
79 NS_INTERFACE_MAP_END
81 // nsMIMEInfoImpl methods
83 // Constructors for a MIME handler.
84 nsMIMEInfoBase::nsMIMEInfoBase(const char* aMIMEType)
85 : mSchemeOrType(aMIMEType),
86 mClass(eMIMEInfo),
87 mAlwaysAskBeforeHandling(
88 mozilla::StaticPrefs::
89 browser_download_always_ask_before_handling_new_types()) {}
91 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aMIMEType)
92 : mSchemeOrType(aMIMEType),
93 mClass(eMIMEInfo),
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),
105 mClass(aClass),
106 mAlwaysAskBeforeHandling(
107 mozilla::StaticPrefs::
108 browser_download_always_ask_before_handling_new_types() ||
109 aClass != eMIMEInfo) {}
111 nsMIMEInfoBase::~nsMIMEInfoBase() {}
113 NS_IMETHODIMP
114 nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator** aResult) {
115 return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
118 NS_IMETHODIMP
119 nsMIMEInfoBase::ExtensionExists(const nsACString& aExtension, bool* _retval) {
120 MOZ_ASSERT(!aExtension.IsEmpty(), "no extension");
121 *_retval = mExtensions.Contains(aExtension,
122 nsCaseInsensitiveCStringArrayComparator());
123 return NS_OK;
126 NS_IMETHODIMP
127 nsMIMEInfoBase::GetPrimaryExtension(nsACString& _retval) {
128 if (!mExtensions.Length()) {
129 _retval.Truncate();
130 return NS_ERROR_NOT_INITIALIZED;
132 _retval = mExtensions[0];
133 return NS_OK;
136 NS_IMETHODIMP
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());
144 if (i != -1) {
145 mExtensions.RemoveElementAt(i);
147 mExtensions.InsertElementAt(0, aExtension);
148 mIsDefaultAppInfoFresh = false;
149 return NS_OK;
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);
163 NS_IMETHODIMP
164 nsMIMEInfoBase::AppendExtension(const nsACString& aExtension) {
165 MOZ_ASSERT(!aExtension.IsEmpty(), "No extension");
166 AddUniqueExtension(aExtension);
167 return NS_OK;
170 NS_IMETHODIMP
171 nsMIMEInfoBase::GetType(nsACString& aType) {
172 if (mSchemeOrType.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
174 aType = mSchemeOrType;
175 return NS_OK;
178 NS_IMETHODIMP
179 nsMIMEInfoBase::GetMIMEType(nsACString& aMIMEType) {
180 if (mSchemeOrType.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
182 aMIMEType = mSchemeOrType;
183 return NS_OK;
186 NS_IMETHODIMP
187 nsMIMEInfoBase::GetDescription(nsAString& aDescription) {
188 aDescription = mDescription;
189 return NS_OK;
192 NS_IMETHODIMP
193 nsMIMEInfoBase::SetDescription(const nsAString& aDescription) {
194 mDescription = aDescription;
195 return NS_OK;
198 NS_IMETHODIMP
199 nsMIMEInfoBase::Equals(nsIMIMEInfo* aMIMEInfo, bool* _retval) {
200 if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
202 nsAutoCString type;
203 nsresult rv = aMIMEInfo->GetMIMEType(type);
204 if (NS_FAILED(rv)) return rv;
206 *_retval = mSchemeOrType.Equals(type);
208 return NS_OK;
211 NS_IMETHODIMP
212 nsMIMEInfoBase::SetFileExtensions(const nsACString& aExtensions) {
213 mExtensions.Clear();
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;
224 return NS_OK;
227 NS_IMETHODIMP
228 nsMIMEInfoBase::GetDefaultDescription(nsAString& aDefaultDescription) {
229 aDefaultDescription = mDefaultAppDescription;
230 return NS_OK;
233 NS_IMETHODIMP
234 nsMIMEInfoBase::GetDefaultExecutable(nsIFile** aExecutable) {
235 return NS_ERROR_NOT_IMPLEMENTED;
238 NS_IMETHODIMP
239 nsMIMEInfoBase::GetPreferredApplicationHandler(
240 nsIHandlerApp** aPreferredAppHandler) {
241 *aPreferredAppHandler = mPreferredApplication;
242 NS_IF_ADDREF(*aPreferredAppHandler);
243 return NS_OK;
246 NS_IMETHODIMP
247 nsMIMEInfoBase::SetPreferredApplicationHandler(
248 nsIHandlerApp* aPreferredAppHandler) {
249 mPreferredApplication = aPreferredAppHandler;
250 return NS_OK;
253 NS_IMETHODIMP
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);
263 return NS_OK;
266 NS_IMETHODIMP
267 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction* aPreferredAction) {
268 *aPreferredAction = mPreferredAction;
269 return NS_OK;
272 NS_IMETHODIMP
273 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction) {
274 mPreferredAction = aPreferredAction;
275 return NS_OK;
278 NS_IMETHODIMP
279 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk) {
280 *aAlwaysAsk = mAlwaysAskBeforeHandling;
282 return NS_OK;
285 NS_IMETHODIMP
286 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk) {
287 mAlwaysAskBeforeHandling = aAlwaysAsk;
288 return NS_OK;
291 /* static */
292 nsresult nsMIMEInfoBase::GetLocalFileFromURI(nsIURI* aURI, nsIFile** aFile) {
293 nsresult rv;
295 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
296 if (NS_FAILED(rv)) {
297 return rv;
300 nsCOMPtr<nsIFile> file;
301 rv = fileUrl->GetFile(getter_AddRefs(file));
302 if (NS_FAILED(rv)) {
303 return rv;
306 file.forget(aFile);
307 return NS_OK;
310 NS_IMETHODIMP
311 nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile) {
312 nsresult rv;
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)) {
319 return NS_OK;
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()) {
348 return false;
350 nsAutoString path;
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",
358 path.get());
359 bool data = true;
360 canOpen->GetData(&data);
361 return !data;
364 NS_IMETHODIMP
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;
382 nsresult rv =
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;
403 if (!executable ||
404 NS_FAILED(executable->Equals(sOurAppFile, &isOurExecutable)) ||
405 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();
423 /* static */
424 already_AddRefed<nsIProcess> nsMIMEInfoBase::InitProcess(nsIFile* aApp,
425 nsresult* aResult) {
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();
438 /* static */
439 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
440 const nsCString& aArg) {
441 nsresult rv;
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);
450 /* static */
451 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
452 const nsString& aArg) {
453 nsresult rv;
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);
462 /* static */
463 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const int aArgc,
464 const char16_t** aArgv) {
465 nsresult rv;
466 nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
467 if (NS_FAILED(rv)) {
468 return rv;
471 return process->Runw(false, aArgv, aArgc);
474 // nsMIMEInfoImpl implementation
475 NS_IMETHODIMP
476 nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription) {
477 if (mDefaultAppDescription.IsEmpty()) {
478 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
479 if (defaultApp) {
480 // Don't want to cache this, just in case someone resets the app
481 // without changing the description....
482 defaultApp->GetLeafName(aDefaultDescription);
483 return NS_OK;
486 aDefaultDescription = mDefaultAppDescription;
488 return NS_OK;
491 NS_IMETHODIMP nsMIMEInfoImpl::GetDefaultExecutable(nsIFile** aExecutable) {
492 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
493 if (defaultApp) {
494 defaultApp.forget(aExecutable);
495 return NS_OK;
498 return NS_ERROR_FAILURE;
501 NS_IMETHODIMP
502 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval) {
503 *_retval = !mDefaultAppDescription.IsEmpty();
504 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
505 if (defaultApp) {
506 bool exists;
507 *_retval = NS_SUCCEEDED(defaultApp->Exists(&exists)) && exists;
509 return NS_OK;
512 NS_IMETHODIMP
513 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval) {
514 *_retval = false;
515 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
516 if (defaultApp) {
517 // Determine if the default executable is our executable.
518 EnsureAppDetailsAvailable();
519 bool isSame = false;
520 nsresult rv = defaultApp->Equals(sOurAppFile, &isSame);
521 if (NS_FAILED(rv)) {
522 return rv;
524 *_retval = isSame;
526 return NS_OK;
529 nsresult nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile* aFile) {
530 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
531 if (!defaultApp) {
532 return NS_ERROR_FILE_NOT_FOUND;
535 return LaunchWithIProcess(defaultApp, aFile->NativePath());
538 NS_IMETHODIMP
539 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray** _retval) {
540 return NS_ERROR_NOT_IMPLEMENTED;