Bug 1760193 [wpt PR 33188] - Update wpt metadata, a=testonly
[gecko.git] / uriloader / exthandler / nsMIMEInfoImpl.cpp
blobf0b3b48244e0424edcfb5ec08410ad169350a55c
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 "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;
24 /* static */
25 already_AddRefed<nsIFile> nsMIMEInfoBase::GetCanonicalExecutable(
26 nsIFile* aFile) {
27 nsCOMPtr<nsIFile> binary = aFile;
28 #ifdef XP_MACOSX
29 nsAutoString path;
30 if (binary) {
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;
39 if (binary) {
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);
46 if (binary) {
47 binary->GetLeafName(leafName);
50 #endif
51 return binary.forget();
54 static void EnsureAppDetailsAvailable() {
55 if (sInitializedOurData) {
56 return;
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)
74 NS_INTERFACE_MAP_END
76 // nsMIMEInfoImpl methods
78 // Constructors for a MIME handler.
79 nsMIMEInfoBase::nsMIMEInfoBase(const char* aMIMEType)
80 : mSchemeOrType(aMIMEType),
81 mClass(eMIMEInfo),
82 mPreferredAction(nsIMIMEInfo::saveToDisk),
83 mAlwaysAskBeforeHandling(
84 !StaticPrefs::browser_download_improvements_to_download_panel()) {}
86 nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aMIMEType)
87 : mSchemeOrType(aMIMEType),
88 mClass(eMIMEInfo),
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),
100 mClass(aClass),
101 mPreferredAction(nsIMIMEInfo::saveToDisk),
102 mAlwaysAskBeforeHandling(
103 !StaticPrefs::browser_download_improvements_to_download_panel() ||
104 aClass != eMIMEInfo) {}
106 nsMIMEInfoBase::~nsMIMEInfoBase() {}
108 NS_IMETHODIMP
109 nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator** aResult) {
110 return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
113 NS_IMETHODIMP
114 nsMIMEInfoBase::ExtensionExists(const nsACString& aExtension, bool* _retval) {
115 MOZ_ASSERT(!aExtension.IsEmpty(), "no extension");
116 *_retval = mExtensions.Contains(aExtension,
117 nsCaseInsensitiveCStringArrayComparator());
118 return NS_OK;
121 NS_IMETHODIMP
122 nsMIMEInfoBase::GetPrimaryExtension(nsACString& _retval) {
123 if (!mExtensions.Length()) {
124 _retval.Truncate();
125 return NS_ERROR_NOT_INITIALIZED;
127 _retval = mExtensions[0];
128 return NS_OK;
131 NS_IMETHODIMP
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());
139 if (i != -1) {
140 mExtensions.RemoveElementAt(i);
142 mExtensions.InsertElementAt(0, aExtension);
143 return NS_OK;
146 void nsMIMEInfoBase::AddUniqueExtension(const nsACString& aExtension) {
147 if (!aExtension.IsEmpty() &&
148 !mExtensions.Contains(aExtension,
149 nsCaseInsensitiveCStringArrayComparator())) {
150 mExtensions.AppendElement(aExtension);
154 NS_IMETHODIMP
155 nsMIMEInfoBase::AppendExtension(const nsACString& aExtension) {
156 MOZ_ASSERT(!aExtension.IsEmpty(), "No extension");
157 AddUniqueExtension(aExtension);
158 return NS_OK;
161 NS_IMETHODIMP
162 nsMIMEInfoBase::GetType(nsACString& aType) {
163 if (mSchemeOrType.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
165 aType = mSchemeOrType;
166 return NS_OK;
169 NS_IMETHODIMP
170 nsMIMEInfoBase::GetMIMEType(nsACString& aMIMEType) {
171 if (mSchemeOrType.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
173 aMIMEType = mSchemeOrType;
174 return NS_OK;
177 NS_IMETHODIMP
178 nsMIMEInfoBase::GetDescription(nsAString& aDescription) {
179 aDescription = mDescription;
180 return NS_OK;
183 NS_IMETHODIMP
184 nsMIMEInfoBase::SetDescription(const nsAString& aDescription) {
185 mDescription = aDescription;
186 return NS_OK;
189 NS_IMETHODIMP
190 nsMIMEInfoBase::Equals(nsIMIMEInfo* aMIMEInfo, bool* _retval) {
191 if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
193 nsAutoCString type;
194 nsresult rv = aMIMEInfo->GetMIMEType(type);
195 if (NS_FAILED(rv)) return rv;
197 *_retval = mSchemeOrType.Equals(type);
199 return NS_OK;
202 NS_IMETHODIMP
203 nsMIMEInfoBase::SetFileExtensions(const nsACString& aExtensions) {
204 mExtensions.Clear();
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;
215 return NS_OK;
218 NS_IMETHODIMP
219 nsMIMEInfoBase::GetDefaultDescription(nsAString& aDefaultDescription) {
220 aDefaultDescription = mDefaultAppDescription;
221 return NS_OK;
224 NS_IMETHODIMP
225 nsMIMEInfoBase::GetPreferredApplicationHandler(
226 nsIHandlerApp** aPreferredAppHandler) {
227 *aPreferredAppHandler = mPreferredApplication;
228 NS_IF_ADDREF(*aPreferredAppHandler);
229 return NS_OK;
232 NS_IMETHODIMP
233 nsMIMEInfoBase::SetPreferredApplicationHandler(
234 nsIHandlerApp* aPreferredAppHandler) {
235 mPreferredApplication = aPreferredAppHandler;
236 return NS_OK;
239 NS_IMETHODIMP
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);
249 return NS_OK;
252 NS_IMETHODIMP
253 nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction* aPreferredAction) {
254 *aPreferredAction = mPreferredAction;
255 return NS_OK;
258 NS_IMETHODIMP
259 nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction) {
260 mPreferredAction = aPreferredAction;
261 return NS_OK;
264 NS_IMETHODIMP
265 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk) {
266 *aAlwaysAsk = mAlwaysAskBeforeHandling;
268 return NS_OK;
271 NS_IMETHODIMP
272 nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk) {
273 mAlwaysAskBeforeHandling = aAlwaysAsk;
274 return NS_OK;
277 /* static */
278 nsresult nsMIMEInfoBase::GetLocalFileFromURI(nsIURI* aURI, nsIFile** aFile) {
279 nsresult rv;
281 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
282 if (NS_FAILED(rv)) {
283 return rv;
286 nsCOMPtr<nsIFile> file;
287 rv = fileUrl->GetFile(getter_AddRefs(file));
288 if (NS_FAILED(rv)) {
289 return rv;
292 file.forget(aFile);
293 return NS_OK;
296 NS_IMETHODIMP
297 nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile) {
298 nsresult rv;
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)) {
305 return NS_OK;
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()) {
334 return false;
336 nsAutoString path;
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",
344 path.get());
345 bool data = true;
346 canOpen->GetData(&data);
347 return !data;
350 NS_IMETHODIMP
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;
367 nsresult rv =
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;
388 if (!executable ||
389 NS_FAILED(executable->Equals(sOurAppFile, &isOurExecutable)) ||
390 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();
408 /* static */
409 already_AddRefed<nsIProcess> nsMIMEInfoBase::InitProcess(nsIFile* aApp,
410 nsresult* aResult) {
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();
423 /* static */
424 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
425 const nsCString& aArg) {
426 nsresult rv;
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);
435 /* static */
436 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp,
437 const nsString& aArg) {
438 nsresult rv;
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);
447 /* static */
448 nsresult nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const int aArgc,
449 const char16_t** aArgv) {
450 nsresult rv;
451 nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
452 if (NS_FAILED(rv)) {
453 return rv;
456 return process->Runw(false, aArgv, aArgc);
459 // nsMIMEInfoImpl implementation
460 NS_IMETHODIMP
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);
466 } else {
467 aDefaultDescription = mDefaultAppDescription;
470 return NS_OK;
473 NS_IMETHODIMP
474 nsMIMEInfoImpl::GetHasDefaultHandler(bool* _retval) {
475 *_retval = !mDefaultAppDescription.IsEmpty();
476 if (mDefaultApplication) {
477 bool exists;
478 *_retval = NS_SUCCEEDED(mDefaultApplication->Exists(&exists)) && exists;
480 return NS_OK;
483 NS_IMETHODIMP
484 nsMIMEInfoImpl::IsCurrentAppOSDefault(bool* _retval) {
485 *_retval = false;
486 if (mDefaultApplication) {
487 // Determine if the default executable is our executable.
488 EnsureAppDetailsAvailable();
489 bool isSame = false;
490 nsresult rv = mDefaultApplication->Equals(sOurAppFile, &isSame);
491 if (NS_FAILED(rv)) {
492 return rv;
494 *_retval = isSame;
496 return NS_OK;
499 nsresult nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile* aFile) {
500 if (!mDefaultApplication) return NS_ERROR_FILE_NOT_FOUND;
502 return LaunchWithIProcess(mDefaultApplication, aFile->NativePath());
505 NS_IMETHODIMP
506 nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray** _retval) {
507 return NS_ERROR_NOT_IMPLEMENTED;