Bug 1845017 - Disable the TestPHCExhaustion test r=glandium
[gecko.git] / uriloader / exthandler / nsMIMEInfoImpl.cpp
blob3616ab7fcf3bd46f70f19ea6a7ca9803862cc89f
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::GetPreferredApplicationHandler(
235 nsIHandlerApp** aPreferredAppHandler) {
236 *aPreferredAppHandler = mPreferredApplication;
237 NS_IF_ADDREF(*aPreferredAppHandler);
238 return NS_OK;
241 NS_IMETHODIMP
242 nsMIMEInfoBase::SetPreferredApplicationHandler(
243 nsIHandlerApp* aPreferredAppHandler) {
244 mPreferredApplication = aPreferredAppHandler;
245 return NS_OK;
248 NS_IMETHODIMP
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);
258 return NS_OK;
261 NS_IMETHODIMP
262 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction* aPreferredAction) {
263 *aPreferredAction = mPreferredAction;
264 return NS_OK;
267 NS_IMETHODIMP
268 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction) {
269 mPreferredAction = aPreferredAction;
270 return NS_OK;
273 NS_IMETHODIMP
274 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk) {
275 *aAlwaysAsk = mAlwaysAskBeforeHandling;
277 return NS_OK;
280 NS_IMETHODIMP
281 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk) {
282 mAlwaysAskBeforeHandling = aAlwaysAsk;
283 return NS_OK;
286 /* static */
287 nsresult nsMIMEInfoBase::GetLocalFileFromURI(nsIURI* aURI, nsIFile** aFile) {
288 nsresult rv;
290 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
291 if (NS_FAILED(rv)) {
292 return rv;
295 nsCOMPtr<nsIFile> file;
296 rv = fileUrl->GetFile(getter_AddRefs(file));
297 if (NS_FAILED(rv)) {
298 return rv;
301 file.forget(aFile);
302 return NS_OK;
305 NS_IMETHODIMP
306 nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile) {
307 nsresult rv;
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)) {
314 return NS_OK;
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()) {
343 return false;
345 nsAutoString path;
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",
353 path.get());
354 bool data = true;
355 canOpen->GetData(&data);
356 return !data;
359 NS_IMETHODIMP
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;
377 nsresult rv =
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;
398 if (!executable ||
399 NS_FAILED(executable->Equals(sOurAppFile, &isOurExecutable)) ||
400 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();
418 /* static */
419 already_AddRefed<nsIProcess> nsMIMEInfoBase::InitProcess(nsIFile* aApp,
420 nsresult* aResult) {
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();
433 /* static */
434 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
435 const nsCString& aArg) {
436 nsresult rv;
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);
445 /* static */
446 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
447 const nsString& aArg) {
448 nsresult rv;
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);
457 /* static */
458 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const int aArgc,
459 const char16_t** aArgv) {
460 nsresult rv;
461 nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
462 if (NS_FAILED(rv)) {
463 return rv;
466 return process->Runw(false, aArgv, aArgc);
469 // nsMIMEInfoImpl implementation
470 NS_IMETHODIMP
471 nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription) {
472 if (mDefaultAppDescription.IsEmpty()) {
473 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
474 if (defaultApp) {
475 // Don't want to cache this, just in case someone resets the app
476 // without changing the description....
477 defaultApp->GetLeafName(aDefaultDescription);
478 return NS_OK;
481 aDefaultDescription = mDefaultAppDescription;
483 return NS_OK;
486 NS_IMETHODIMP
487 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval) {
488 *_retval = !mDefaultAppDescription.IsEmpty();
489 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
490 if (defaultApp) {
491 bool exists;
492 *_retval = NS_SUCCEEDED(defaultApp->Exists(&exists)) && exists;
494 return NS_OK;
497 NS_IMETHODIMP
498 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval) {
499 *_retval = false;
500 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
501 if (defaultApp) {
502 // Determine if the default executable is our executable.
503 EnsureAppDetailsAvailable();
504 bool isSame = false;
505 nsresult rv = defaultApp->Equals(sOurAppFile, &isSame);
506 if (NS_FAILED(rv)) {
507 return rv;
509 *_retval = isSame;
511 return NS_OK;
514 nsresult nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile* aFile) {
515 nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication();
516 if (!defaultApp) {
517 return NS_ERROR_FILE_NOT_FOUND;
520 return LaunchWithIProcess(defaultApp, aFile->NativePath());
523 NS_IMETHODIMP
524 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray** _retval) {
525 return NS_ERROR_NOT_IMPLEMENTED;