summaryrefslogtreecommitdiffstats
path: root/uriloader/exthandler
diff options
context:
space:
mode:
Diffstat (limited to 'uriloader/exthandler')
-rw-r--r--uriloader/exthandler/ContentHandlerService.cpp169
-rw-r--r--uriloader/exthandler/ContentHandlerService.h52
-rw-r--r--uriloader/exthandler/ExternalHelperAppChild.cpp127
-rw-r--r--uriloader/exthandler/ExternalHelperAppChild.h45
-rw-r--r--uriloader/exthandler/ExternalHelperAppParent.cpp512
-rw-r--r--uriloader/exthandler/ExternalHelperAppParent.h119
-rw-r--r--uriloader/exthandler/HandlerServiceChild.h15
-rw-r--r--uriloader/exthandler/HandlerServiceParent.cpp270
-rw-r--r--uriloader/exthandler/HandlerServiceParent.h31
-rw-r--r--uriloader/exthandler/PExternalHelperApp.ipdl29
-rw-r--r--uriloader/exthandler/PHandlerService.ipdl42
-rw-r--r--uriloader/exthandler/android/nsAndroidHandlerApp.cpp91
-rw-r--r--uriloader/exthandler/android/nsAndroidHandlerApp.h33
-rw-r--r--uriloader/exthandler/android/nsExternalSharingAppService.cpp61
-rw-r--r--uriloader/exthandler/android/nsExternalSharingAppService.h28
-rw-r--r--uriloader/exthandler/android/nsExternalURLHandlerService.cpp27
-rw-r--r--uriloader/exthandler/android/nsExternalURLHandlerService.h27
-rw-r--r--uriloader/exthandler/android/nsMIMEInfoAndroid.cpp427
-rw-r--r--uriloader/exthandler/android/nsMIMEInfoAndroid.h60
-rw-r--r--uriloader/exthandler/android/nsOSHelperAppService.cpp67
-rw-r--r--uriloader/exthandler/android/nsOSHelperAppService.h40
-rw-r--r--uriloader/exthandler/gonk/nsOSHelperAppService.cpp56
-rw-r--r--uriloader/exthandler/gonk/nsOSHelperAppService.h39
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.cpp389
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.h118
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.h26
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.mm84
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.h34
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.mm114
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.h48
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.mm569
-rw-r--r--uriloader/exthandler/moz.build139
-rw-r--r--uriloader/exthandler/nsCExternalHandlerService.idl58
-rw-r--r--uriloader/exthandler/nsContentHandlerApp.cpp79
-rw-r--r--uriloader/exthandler/nsContentHandlerApp.h30
-rw-r--r--uriloader/exthandler/nsDBusHandlerApp.cpp177
-rw-r--r--uriloader/exthandler/nsDBusHandlerApp.h33
-rw-r--r--uriloader/exthandler/nsExternalHelperAppService.cpp2926
-rw-r--r--uriloader/exthandler/nsExternalHelperAppService.h498
-rw-r--r--uriloader/exthandler/nsExternalProtocolHandler.cpp561
-rw-r--r--uriloader/exthandler/nsExternalProtocolHandler.h38
-rw-r--r--uriloader/exthandler/nsHandlerService.js1429
-rw-r--r--uriloader/exthandler/nsHandlerService.manifest2
-rw-r--r--uriloader/exthandler/nsIContentDispatchChooser.idl45
-rw-r--r--uriloader/exthandler/nsIExternalHelperAppService.idl154
-rw-r--r--uriloader/exthandler/nsIExternalProtocolService.idl135
-rw-r--r--uriloader/exthandler/nsIExternalSharingAppService.idl28
-rw-r--r--uriloader/exthandler/nsIExternalURLHandlerService.idl26
-rw-r--r--uriloader/exthandler/nsIHandlerService.idl117
-rw-r--r--uriloader/exthandler/nsIHelperAppLauncherDialog.idl89
-rw-r--r--uriloader/exthandler/nsLocalHandlerApp.cpp179
-rw-r--r--uriloader/exthandler/nsLocalHandlerApp.h60
-rw-r--r--uriloader/exthandler/nsMIMEInfoImpl.cpp435
-rw-r--r--uriloader/exthandler/nsMIMEInfoImpl.h196
-rw-r--r--uriloader/exthandler/nsWebHandlerApp.js172
-rw-r--r--uriloader/exthandler/nsWebHandlerApp.manifest2
-rw-r--r--uriloader/exthandler/tests/Makefile.in11
-rw-r--r--uriloader/exthandler/tests/WriteArgument.cpp24
-rw-r--r--uriloader/exthandler/tests/mochitest/browser.ini8
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js19
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_remember_download_option.js49
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js76
-rw-r--r--uriloader/exthandler/tests/mochitest/handlerApp.xhtml30
-rw-r--r--uriloader/exthandler/tests/mochitest/handlerApps.js110
-rw-r--r--uriloader/exthandler/tests/mochitest/head.js105
-rw-r--r--uriloader/exthandler/tests/mochitest/mochitest.ini10
-rw-r--r--uriloader/exthandler/tests/mochitest/protocolHandler.html16
-rw-r--r--uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml12
-rw-r--r--uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml77
-rw-r--r--uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs14
-rw-r--r--uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js28
-rw-r--r--uriloader/exthandler/tests/moz.build24
-rw-r--r--uriloader/exthandler/tests/unit/head_handlerService.js163
-rw-r--r--uriloader/exthandler/tests/unit/mailcap2
-rw-r--r--uriloader/exthandler/tests/unit/tail_handlerService.js5
-rw-r--r--uriloader/exthandler/tests/unit/test_badMIMEType.js26
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js53
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js186
-rw-r--r--uriloader/exthandler/tests/unit/test_handlerService.js470
-rw-r--r--uriloader/exthandler/tests/unit/test_punycodeURIs.js126
-rw-r--r--uriloader/exthandler/tests/unit/xpcshell.ini15
-rw-r--r--uriloader/exthandler/tests/unit_ipc/test_encoding.js231
-rw-r--r--uriloader/exthandler/tests/unit_ipc/xpcshell.ini8
-rw-r--r--uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h32
-rw-r--r--uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm17
-rw-r--r--uriloader/exthandler/uikit/nsMIMEInfoUIKit.h37
-rw-r--r--uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm20
-rw-r--r--uriloader/exthandler/uikit/nsOSHelperAppService.h52
-rw-r--r--uriloader/exthandler/uikit/nsOSHelperAppService.mm64
-rw-r--r--uriloader/exthandler/unix/nsExternalSharingAppService.h31
-rw-r--r--uriloader/exthandler/unix/nsGNOMERegistry.cpp109
-rw-r--r--uriloader/exthandler/unix/nsGNOMERegistry.h28
-rw-r--r--uriloader/exthandler/unix/nsMIMEInfoUnix.cpp136
-rw-r--r--uriloader/exthandler/unix/nsMIMEInfoUnix.h32
-rw-r--r--uriloader/exthandler/unix/nsOSHelperAppService.cpp1537
-rw-r--r--uriloader/exthandler/unix/nsOSHelperAppService.h128
-rw-r--r--uriloader/exthandler/win/nsMIMEInfoWin.cpp883
-rw-r--r--uriloader/exthandler/win/nsMIMEInfoWin.h71
-rw-r--r--uriloader/exthandler/win/nsOSHelperAppService.cpp626
-rw-r--r--uriloader/exthandler/win/nsOSHelperAppService.h65
100 files changed, 17123 insertions, 0 deletions
diff --git a/uriloader/exthandler/ContentHandlerService.cpp b/uriloader/exthandler/ContentHandlerService.cpp
new file mode 100644
index 000000000..75575a730
--- /dev/null
+++ b/uriloader/exthandler/ContentHandlerService.cpp
@@ -0,0 +1,169 @@
+#include "ContentHandlerService.h"
+#include "HandlerServiceChild.h"
+#include "ContentChild.h"
+#include "nsIMutableArray.h"
+#include "nsIMIMEInfo.h"
+
+using mozilla::dom::ContentChild;
+using mozilla::dom::PHandlerServiceChild;
+using mozilla::dom::HandlerInfo;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ContentHandlerService, nsIHandlerService)
+
+ContentHandlerService::ContentHandlerService()
+{
+}
+
+nsresult
+ContentHandlerService::Init()
+{
+ if (!XRE_IsContentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+ ContentChild* cpc = ContentChild::GetSingleton();
+
+ mHandlerServiceChild = static_cast<HandlerServiceChild*>(cpc->SendPHandlerServiceConstructor());
+ return NS_OK;
+}
+
+void
+ContentHandlerService::nsIHandlerInfoToHandlerInfo(nsIHandlerInfo* aInfo,
+ HandlerInfo* aHandlerInfo)
+{
+ nsCString type;
+ aInfo->GetType(type);
+ nsCOMPtr<nsIMIMEInfo> mimeInfo = do_QueryInterface(aInfo);
+ bool isMIMEInfo = !!mimeInfo;
+ nsString description;
+ aInfo->GetDescription(description);
+ bool alwaysAskBeforeHandling;
+ aInfo->GetAlwaysAskBeforeHandling(&alwaysAskBeforeHandling);
+ nsCOMPtr<nsIHandlerApp> app;
+ aInfo->GetPreferredApplicationHandler(getter_AddRefs(app));
+ nsString name;
+ nsString detailedDescription;
+ if (app) {
+ app->GetName(name);
+ app->GetDetailedDescription(detailedDescription);
+ }
+ HandlerApp happ(name, detailedDescription);
+ nsTArray<HandlerApp> happs;
+ nsCOMPtr<nsIMutableArray> apps;
+ aInfo->GetPossibleApplicationHandlers(getter_AddRefs(apps));
+ if (apps) {
+ unsigned int length;
+ apps->GetLength(&length);
+ for (unsigned int i = 0; i < length; i++) {
+ apps->QueryElementAt(i, NS_GET_IID(nsIHandlerApp), getter_AddRefs(app));
+ app->GetName(name);
+ app->GetDetailedDescription(detailedDescription);
+ happs.AppendElement(HandlerApp(name, detailedDescription));
+ }
+ }
+ nsHandlerInfoAction action;
+ aInfo->GetPreferredAction(&action);
+ HandlerInfo info(type, isMIMEInfo, description, alwaysAskBeforeHandling, happ, happs, action);
+ *aHandlerInfo = info;
+}
+
+
+NS_IMETHODIMP RemoteHandlerApp::GetName(nsAString & aName)
+{
+ aName.Assign(mAppChild.name());
+ return NS_OK;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::SetName(const nsAString & aName)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::GetDetailedDescription(nsAString & aDetailedDescription)
+{
+ aDetailedDescription.Assign(mAppChild.detailedDescription());
+ return NS_OK;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::SetDetailedDescription(const nsAString & aDetailedDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(RemoteHandlerApp, nsIHandlerApp)
+
+static inline void CopyHanderInfoTonsIHandlerInfo(HandlerInfo info, nsIHandlerInfo* aHandlerInfo)
+{
+ HandlerApp preferredApplicationHandler = info.preferredApplicationHandler();
+ nsCOMPtr<nsIHandlerApp> preferredApp(new RemoteHandlerApp(preferredApplicationHandler));
+ aHandlerInfo->SetPreferredApplicationHandler(preferredApp);
+ nsCOMPtr<nsIMutableArray> possibleHandlers;
+ aHandlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
+ possibleHandlers->AppendElement(preferredApp, false);
+}
+ContentHandlerService::~ContentHandlerService()
+{
+}
+
+NS_IMETHODIMP ContentHandlerService::Enumerate(nsISimpleEnumerator * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ContentHandlerService::FillHandlerInfo(nsIHandlerInfo *aHandlerInfo, const nsACString & aOverrideType)
+{
+ HandlerInfo info;
+ nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
+ mHandlerServiceChild->SendFillHandlerInfo(info, nsCString(aOverrideType), &info);
+ CopyHanderInfoTonsIHandlerInfo(info, aHandlerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentHandlerService::Store(nsIHandlerInfo *aHandlerInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ContentHandlerService::Exists(nsIHandlerInfo *aHandlerInfo, bool *_retval)
+{
+ HandlerInfo info;
+ nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
+ mHandlerServiceChild->SendExists(info, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentHandlerService::Remove(nsIHandlerInfo *aHandlerInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP ContentHandlerService::GetTypeFromExtension(const nsACString & aFileExtension, nsACString & _retval)
+{
+ nsCString* cachedType = nullptr;
+ if (!!mExtToTypeMap.Get(aFileExtension, &cachedType) && !!cachedType) {
+ _retval.Assign(*cachedType);
+ return NS_OK;
+ }
+ nsCString type;
+ mHandlerServiceChild->SendGetTypeFromExtension(nsCString(aFileExtension), &type);
+ _retval.Assign(type);
+ mExtToTypeMap.Put(nsCString(aFileExtension), new nsCString(type));
+
+ return NS_OK;
+}
+
+}
+}
diff --git a/uriloader/exthandler/ContentHandlerService.h b/uriloader/exthandler/ContentHandlerService.h
new file mode 100644
index 000000000..e39e89bc9
--- /dev/null
+++ b/uriloader/exthandler/ContentHandlerService.h
@@ -0,0 +1,52 @@
+#ifndef ContentHandlerService_h
+#define ContentHandlerService_h
+
+#include "nsIHandlerService.h"
+#include "nsClassHashtable.h"
+#include "HandlerServiceChild.h"
+#include "nsIMIMEInfo.h"
+
+#define NS_CONTENTHANDLERSERVICE_CID \
+ {0xc4b6fb7c, 0xbfb1, 0x49dc, {0xa6, 0x5f, 0x03, 0x57, 0x96, 0x52, 0x4b, 0x53}}
+
+namespace mozilla {
+namespace dom {
+
+class PHandlerServiceChild;
+
+class ContentHandlerService : public nsIHandlerService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERSERVICE
+
+ ContentHandlerService();
+ MOZ_MUST_USE nsresult Init();
+ static void nsIHandlerInfoToHandlerInfo(nsIHandlerInfo* aInfo, HandlerInfo* aHandlerInfo);
+
+private:
+ virtual ~ContentHandlerService();
+ RefPtr<HandlerServiceChild> mHandlerServiceChild;
+ nsClassHashtable<nsCStringHashKey, nsCString> mExtToTypeMap;
+};
+
+class RemoteHandlerApp : public nsIHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+
+ explicit RemoteHandlerApp(HandlerApp aAppChild) : mAppChild(aAppChild)
+ {
+ }
+private:
+ virtual ~RemoteHandlerApp()
+ {
+ }
+ HandlerApp mAppChild;
+};
+
+
+}
+}
+#endif
diff --git a/uriloader/exthandler/ExternalHelperAppChild.cpp b/uriloader/exthandler/ExternalHelperAppChild.cpp
new file mode 100644
index 000000000..584e596b9
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppChild.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ExternalHelperAppChild.h"
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "nsIDivertableChannel.h"
+#include "nsIInputStream.h"
+#include "nsIFTPChannel.h"
+#include "nsIRequest.h"
+#include "nsIResumableChannel.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ExternalHelperAppChild,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+ExternalHelperAppChild::ExternalHelperAppChild()
+ : mStatus(NS_OK)
+{
+}
+
+ExternalHelperAppChild::~ExternalHelperAppChild()
+{
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+ExternalHelperAppChild::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(input, data, count);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!SendOnDataAvailable(data, offset, count))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIRequestObserver
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+ExternalHelperAppChild::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ return DivertToParent(divertable, request);
+ }
+
+ nsresult rv = mHandler->OnStartRequest(request, ctx);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ nsCString entityID;
+ nsCOMPtr<nsIResumableChannel> resumable(do_QueryInterface(request));
+ if (resumable) {
+ resumable->GetEntityID(entityID);
+ }
+ SendOnStartRequest(entityID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppChild::OnStopRequest(nsIRequest *request,
+ nsISupports *ctx,
+ nsresult status)
+{
+ // mHandler can be null if we diverted the request to the parent
+ if (mHandler) {
+ nsresult rv = mHandler->OnStopRequest(request, ctx, status);
+ SendOnStopRequest(status);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ExternalHelperAppChild::DivertToParent(nsIDivertableChannel *divertable,
+ nsIRequest *request)
+{
+ // nsIDivertable must know about content conversions before being diverted.
+ MOZ_ASSERT(mHandler);
+ mHandler->MaybeApplyDecodingForExtension(request);
+
+ mozilla::net::ChannelDiverterChild *diverter = nullptr;
+ nsresult rv = divertable->DivertToParent(&diverter);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(diverter);
+
+ if (SendDivertToParentUsing(diverter)) {
+ mHandler->DidDivertRequest(request);
+ mHandler = nullptr;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+bool
+ExternalHelperAppChild::RecvCancel(const nsresult& aStatus)
+{
+ mStatus = aStatus;
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/ExternalHelperAppChild.h b/uriloader/exthandler/ExternalHelperAppChild.h
new file mode 100644
index 000000000..eecca01e6
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppChild.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ExternalHelperAppChild_h
+#define mozilla_dom_ExternalHelperAppChild_h
+
+#include "mozilla/dom/PExternalHelperAppChild.h"
+#include "nsExternalHelperAppService.h"
+#include "nsIStreamListener.h"
+
+class nsIDivertableChannel;
+
+namespace mozilla {
+namespace dom {
+
+class ExternalHelperAppChild : public PExternalHelperAppChild
+ , public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ ExternalHelperAppChild();
+
+ // Give the listener a real nsExternalAppHandler to complete processing on
+ // the child.
+ void SetHandler(nsExternalAppHandler *handler) { mHandler = handler; }
+
+ virtual bool RecvCancel(const nsresult& aStatus) override;
+private:
+ virtual ~ExternalHelperAppChild();
+ MOZ_MUST_USE nsresult DivertToParent(nsIDivertableChannel *divertable, nsIRequest *request);
+
+ RefPtr<nsExternalAppHandler> mHandler;
+ nsresult mStatus;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ExternalHelperAppChild_h
diff --git a/uriloader/exthandler/ExternalHelperAppParent.cpp b/uriloader/exthandler/ExternalHelperAppParent.cpp
new file mode 100644
index 000000000..a8ddc54c8
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppParent.cpp
@@ -0,0 +1,512 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "ExternalHelperAppParent.h"
+#include "nsIContent.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalHelperAppService.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsStringStream.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsNetUtil.h"
+#include "nsIDocument.h"
+#include "mozilla/net/ChannelDiverterParent.h"
+
+#include "mozilla/Unused.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS_INHERITED(ExternalHelperAppParent,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIMultiPartChannel,
+ nsIPrivateBrowsingChannel,
+ nsIResumableChannel,
+ nsIStreamListener,
+ nsIExternalHelperAppParent)
+
+ExternalHelperAppParent::ExternalHelperAppParent(
+ const OptionalURIParams& uri,
+ const int64_t& aContentLength,
+ const bool& aWasFileChannel)
+ : mURI(DeserializeURI(uri))
+ , mPending(false)
+#ifdef DEBUG
+ , mDiverted(false)
+#endif
+ , mIPCClosed(false)
+ , mLoadFlags(0)
+ , mStatus(NS_OK)
+ , mContentLength(aContentLength)
+ , mWasFileChannel(aWasFileChannel)
+{
+}
+
+void
+ExternalHelperAppParent::Init(ContentParent *parent,
+ const nsCString& aMimeContentType,
+ const nsCString& aContentDispositionHeader,
+ const uint32_t& aContentDispositionHint,
+ const nsString& aContentDispositionFilename,
+ const bool& aForceSave,
+ const OptionalURIParams& aReferrer,
+ PBrowserParent* aBrowser)
+{
+ nsCOMPtr<nsIExternalHelperAppService> helperAppService =
+ do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);
+ NS_ASSERTION(helperAppService, "No Helper App Service!");
+
+ nsCOMPtr<nsIURI> referrer = DeserializeURI(aReferrer);
+ if (referrer)
+ SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), referrer);
+
+ mContentDispositionHeader = aContentDispositionHeader;
+ if (!mContentDispositionHeader.IsEmpty()) {
+ NS_GetFilenameFromDisposition(mContentDispositionFilename,
+ mContentDispositionHeader,
+ mURI);
+ mContentDisposition =
+ NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+ }
+ else {
+ mContentDisposition = aContentDispositionHint;
+ mContentDispositionFilename = aContentDispositionFilename;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> window;
+ if (aBrowser) {
+ TabParent* tabParent = TabParent::GetFrom(aBrowser);
+ if (tabParent->GetOwnerElement())
+ window = do_QueryInterface(tabParent->GetOwnerElement()->OwnerDoc()->GetWindow());
+
+ bool isPrivate = false;
+ nsCOMPtr<nsILoadContext> loadContext = tabParent->GetLoadContext();
+ loadContext->GetUsePrivateBrowsing(&isPrivate);
+ SetPrivate(isPrivate);
+ }
+
+ helperAppService->DoContent(aMimeContentType, this, window,
+ aForceSave, nullptr,
+ getter_AddRefs(mListener));
+}
+
+void
+ExternalHelperAppParent::ActorDestroy(ActorDestroyReason why)
+{
+ mIPCClosed = true;
+}
+
+void
+ExternalHelperAppParent::Delete()
+{
+ if (!mIPCClosed) {
+ Unused << Send__delete__(this);
+ }
+}
+
+bool
+ExternalHelperAppParent::RecvOnStartRequest(const nsCString& entityID)
+{
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+
+ mEntityID = entityID;
+ mPending = true;
+ mStatus = mListener->OnStartRequest(this, nullptr);
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (NS_FAILED(mStatus))
+ return true;
+
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+ MOZ_ASSERT(mPending, "must be pending!");
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ DebugOnly<nsresult> rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(), count, NS_ASSIGNMENT_DEPEND);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create dependent string!");
+ mStatus = mListener->OnDataAvailable(this, nullptr, stringStream, offset, count);
+
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvOnStopRequest(const nsresult& code)
+{
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+
+ mPending = false;
+ mListener->OnStopRequest(this, nullptr,
+ (NS_SUCCEEDED(code) && NS_FAILED(mStatus)) ? mStatus : code);
+ Delete();
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvDivertToParentUsing(PChannelDiverterParent* diverter)
+{
+ MOZ_ASSERT(diverter);
+ auto p = static_cast<mozilla::net::ChannelDiverterParent*>(diverter);
+ p->DivertTo(this);
+#ifdef DEBUG
+ mDiverted = true;
+#endif
+ Unused << p->Send__delete__(p);
+ return true;
+}
+
+//
+// nsIStreamListener
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ MOZ_ASSERT(mDiverted);
+ return mListener->OnDataAvailable(request, ctx, input, offset, count);
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ MOZ_ASSERT(mDiverted);
+ return mListener->OnStartRequest(request, ctx);
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnStopRequest(nsIRequest *request,
+ nsISupports *ctx,
+ nsresult status)
+{
+ MOZ_ASSERT(mDiverted);
+ nsresult rv = mListener->OnStopRequest(request, ctx, status);
+ Delete();
+ return rv;
+}
+
+ExternalHelperAppParent::~ExternalHelperAppParent()
+{
+}
+
+//
+// nsIRequest implementation...
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetName(nsACString& aResult)
+{
+ if (!mURI) {
+ aResult.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mURI->GetAsciiSpec(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::IsPending(bool *aResult)
+{
+ *aResult = mPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetStatus(nsresult *aResult)
+{
+ *aResult = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Cancel(nsresult aStatus)
+{
+ mStatus = aStatus;
+ Unused << SendCancel(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//
+// nsIChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetOriginalURI(nsIURI * *aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetOriginalURI(nsIURI *aURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Open(nsIInputStream **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Open2(nsIInputStream** aStream)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::AsyncOpen(nsIStreamListener *aListener,
+ nsISupports *aContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::AsyncOpen2(nsIStreamListener *aListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetOwner(nsISupports* *aOwner)
+{
+ *aOwner = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetOwner(nsISupports* aOwner)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadInfo(nsILoadInfo* *aLoadInfo)
+{
+ *aLoadInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ *aCallbacks = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentType(nsACString& aContentType)
+{
+ aContentType.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentType(const nsACString& aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentCharset(nsACString& aContentCharset)
+{
+ aContentCharset.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentCharset(const nsACString& aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ // NB: mContentDisposition may or may not be set to a non UINT32_MAX value in
+ // nsExternalHelperAppService::DoContentContentProcessHelper
+ if (mContentDispositionHeader.IsEmpty() && mContentDisposition == UINT32_MAX)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentDisposition(uint32_t aContentDisposition)
+{
+ mContentDisposition = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ if (mContentDispositionFilename.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionFilename = mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ mContentDispositionFilename = aContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionHeader = mContentDispositionHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentLength(int64_t *aContentLength)
+{
+ if (mContentLength < 0)
+ *aContentLength = -1;
+ else
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+//
+// nsIResumableChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::ResumeAt(uint64_t startPos, const nsACString& entityID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetEntityID(nsACString& aEntityID)
+{
+ aEntityID = mEntityID;
+ return NS_OK;
+}
+
+//
+// nsIMultiPartChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetBaseChannel(nsIChannel* *aChannel)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetPartID(uint32_t* aPartID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetIsLastPart(bool* aIsLastPart)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/ExternalHelperAppParent.h b/uriloader/exthandler/ExternalHelperAppParent.h
new file mode 100644
index 000000000..752da996d
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppParent.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PExternalHelperAppParent.h"
+#include "nsIChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIStreamListener.h"
+#include "nsHashPropertyBag.h"
+#include "PrivateBrowsingChannel.h"
+
+namespace IPC {
+class URI;
+} // namespace IPC
+
+namespace mozilla {
+
+namespace ipc {
+class OptionalURIParams;
+} // namespace ipc
+
+namespace net {
+class PChannelDiverterParent;
+} // namespace net
+
+namespace dom {
+
+#define NS_IEXTERNALHELPERAPPPARENT_IID \
+{ 0x127a01bc, 0x2a49, 0x46a8, \
+ { 0x8c, 0x63, 0x4b, 0x5d, 0x3c, 0xa4, 0x07, 0x9c } }
+
+class nsIExternalHelperAppParent : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IEXTERNALHELPERAPPPARENT_IID)
+
+ /**
+ * Returns true if this fake channel represented a file channel in the child.
+ */
+ virtual bool WasFileChannel() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIExternalHelperAppParent, NS_IEXTERNALHELPERAPPPARENT_IID)
+
+class ContentParent;
+class PBrowserParent;
+
+class ExternalHelperAppParent : public PExternalHelperAppParent
+ , public nsHashPropertyBag
+ , public nsIChannel
+ , public nsIMultiPartChannel
+ , public nsIResumableChannel
+ , public nsIStreamListener
+ , public net::PrivateBrowsingChannel<ExternalHelperAppParent>
+ , public nsIExternalHelperAppParent
+{
+ typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIMULTIPARTCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ bool RecvOnStartRequest(const nsCString& entityID) override;
+ bool RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ bool RecvOnStopRequest(const nsresult& code) override;
+
+ bool RecvDivertToParentUsing(PChannelDiverterParent* diverter) override;
+
+ bool WasFileChannel() override {
+ return mWasFileChannel;
+ }
+
+ ExternalHelperAppParent(const OptionalURIParams& uri, const int64_t& contentLength,
+ const bool& wasFileChannel);
+ void Init(ContentParent *parent,
+ const nsCString& aMimeContentType,
+ const nsCString& aContentDisposition,
+ const uint32_t& aContentDispositionHint,
+ const nsString& aContentDispositionFilename,
+ const bool& aForceSave,
+ const OptionalURIParams& aReferrer,
+ PBrowserParent* aBrowser);
+
+protected:
+ virtual ~ExternalHelperAppParent();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void Delete();
+
+private:
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mURI;
+ bool mPending;
+#ifdef DEBUG
+ bool mDiverted;
+#endif
+ bool mIPCClosed;
+ nsLoadFlags mLoadFlags;
+ nsresult mStatus;
+ int64_t mContentLength;
+ bool mWasFileChannel;
+ uint32_t mContentDisposition;
+ nsString mContentDispositionFilename;
+ nsCString mContentDispositionHeader;
+ nsCString mEntityID;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/HandlerServiceChild.h b/uriloader/exthandler/HandlerServiceChild.h
new file mode 100644
index 000000000..6ef6833a8
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceChild.h
@@ -0,0 +1,15 @@
+#ifndef handler_service_child_h
+#define handler_service_child_h
+
+#include "mozilla/dom/PHandlerServiceChild.h"
+
+class HandlerServiceChild final : public mozilla::dom::PHandlerServiceChild
+{
+ public:
+ NS_INLINE_DECL_REFCOUNTING(HandlerServiceChild)
+ HandlerServiceChild() {}
+ private:
+ virtual ~HandlerServiceChild() {}
+};
+
+#endif
diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp
new file mode 100644
index 000000000..3f9de04a8
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceParent.cpp
@@ -0,0 +1,270 @@
+#include "HandlerServiceParent.h"
+#include "nsIHandlerService.h"
+#include "nsIMIMEInfo.h"
+#include "ContentHandlerService.h"
+
+using mozilla::dom::HandlerInfo;
+using mozilla::dom::HandlerApp;
+using mozilla::dom::ContentHandlerService;
+using mozilla::dom::RemoteHandlerApp;
+
+namespace {
+
+class ProxyHandlerInfo final : public nsIHandlerInfo {
+public:
+ explicit ProxyHandlerInfo(const HandlerInfo& aHandlerInfo);
+ NS_DECL_ISUPPORTS;
+ NS_DECL_NSIHANDLERINFO;
+protected:
+ ~ProxyHandlerInfo() {}
+ HandlerInfo mHandlerInfo;
+ nsHandlerInfoAction mPrefAction;
+ nsCOMPtr<nsIMutableArray> mPossibleApps;
+};
+
+NS_IMPL_ISUPPORTS(ProxyHandlerInfo, nsIHandlerInfo)
+
+ProxyHandlerInfo::ProxyHandlerInfo(const HandlerInfo& aHandlerInfo) : mHandlerInfo(aHandlerInfo), mPossibleApps(do_CreateInstance(NS_ARRAY_CONTRACTID))
+{
+ for (auto& happ : aHandlerInfo.possibleApplicationHandlers()) {
+ mPossibleApps->AppendElement(new RemoteHandlerApp(happ), false);
+ }
+}
+
+/* readonly attribute ACString type; */
+NS_IMETHODIMP ProxyHandlerInfo::GetType(nsACString & aType)
+{
+ aType.Assign(mHandlerInfo.type());
+ return NS_OK;
+}
+
+/* attribute AString description; */
+NS_IMETHODIMP ProxyHandlerInfo::GetDescription(nsAString & aDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetDescription(const nsAString & aDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute nsIHandlerApp preferredApplicationHandler; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredApplicationHandler)
+{
+ *aPreferredApplicationHandler = new RemoteHandlerApp(mHandlerInfo.preferredApplicationHandler());
+ NS_IF_ADDREF(*aPreferredApplicationHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ProxyHandlerInfo::SetPreferredApplicationHandler(nsIHandlerApp *aApp)
+{
+ nsString name;
+ nsString detailedDescription;
+ if (aApp) {
+ aApp->GetName(name);
+ aApp->GetDetailedDescription(detailedDescription);
+ }
+ HandlerApp happ(name, detailedDescription);
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ mHandlerInfo.alwaysAskBeforeHandling(),
+ happ,
+ mHandlerInfo.possibleApplicationHandlers(),
+ mHandlerInfo.preferredAction());
+ return NS_OK;
+}
+
+/* readonly attribute nsIMutableArray possibleApplicationHandlers; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleApplicationHandlers)
+{
+ *aPossibleApplicationHandlers = mPossibleApps;
+ NS_IF_ADDREF(*aPossibleApplicationHandlers);
+ return NS_OK;
+}
+
+/* readonly attribute boolean hasDefaultHandler; */
+NS_IMETHODIMP ProxyHandlerInfo::GetHasDefaultHandler(bool *aHasDefaultHandler)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute AString defaultDescription; */
+NS_IMETHODIMP ProxyHandlerInfo::GetDefaultDescription(nsAString & aDefaultDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void launchWithURI (in nsIURI aURI, [optional] in nsIInterfaceRequestor aWindowContext); */
+NS_IMETHODIMP ProxyHandlerInfo::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute ProxyHandlerInfoAction preferredAction; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPreferredAction(nsHandlerInfoAction *aPreferredAction)
+{
+ *aPreferredAction = mPrefAction;
+ return NS_OK;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetPreferredAction(nsHandlerInfoAction aPreferredAction)
+{
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ mHandlerInfo.alwaysAskBeforeHandling(),
+ mHandlerInfo.preferredApplicationHandler(),
+ mHandlerInfo.possibleApplicationHandlers(),
+ aPreferredAction);
+ mPrefAction = aPreferredAction;
+ return NS_OK;
+}
+
+/* attribute boolean alwaysAskBeforeHandling; */
+NS_IMETHODIMP ProxyHandlerInfo::GetAlwaysAskBeforeHandling(bool *aAlwaysAskBeforeHandling)
+{
+ *aAlwaysAskBeforeHandling = mHandlerInfo.alwaysAskBeforeHandling();
+ return NS_OK;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetAlwaysAskBeforeHandling(bool aAlwaysAskBeforeHandling)
+{
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ aAlwaysAskBeforeHandling,
+ mHandlerInfo.preferredApplicationHandler(),
+ mHandlerInfo.possibleApplicationHandlers(),
+ mHandlerInfo.preferredAction());
+ return NS_OK;
+}
+
+
+class ProxyMIMEInfo : public nsIMIMEInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMIMEINFO
+ NS_FORWARD_NSIHANDLERINFO(mProxyHandlerInfo->);
+
+ explicit ProxyMIMEInfo(HandlerInfo aHandlerInfo) : mProxyHandlerInfo(new ProxyHandlerInfo(aHandlerInfo)) {}
+
+private:
+ virtual ~ProxyMIMEInfo() {}
+ nsCOMPtr<nsIHandlerInfo> mProxyHandlerInfo;
+
+protected:
+ /* additional members */
+};
+
+NS_IMPL_ISUPPORTS(ProxyMIMEInfo, nsIMIMEInfo, nsIHandlerInfo)
+
+/* nsIUTF8StringEnumerator getFileExtensions (); */
+NS_IMETHODIMP ProxyMIMEInfo::GetFileExtensions(nsIUTF8StringEnumerator * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void setFileExtensions (in AUTF8String aExtensions); */
+NS_IMETHODIMP ProxyMIMEInfo::SetFileExtensions(const nsACString & aExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean extensionExists (in AUTF8String aExtension); */
+NS_IMETHODIMP ProxyMIMEInfo::ExtensionExists(const nsACString & aExtension, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void appendExtension (in AUTF8String aExtension); */
+NS_IMETHODIMP ProxyMIMEInfo::AppendExtension(const nsACString & aExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute AUTF8String primaryExtension; */
+NS_IMETHODIMP ProxyMIMEInfo::GetPrimaryExtension(nsACString & aPrimaryExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ProxyMIMEInfo::SetPrimaryExtension(const nsACString & aPrimaryExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute ACString MIMEType; */
+NS_IMETHODIMP ProxyMIMEInfo::GetMIMEType(nsACString & aMIMEType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean equals (in nsIMIMEInfo aMIMEInfo); */
+NS_IMETHODIMP ProxyMIMEInfo::Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIArray possibleLocalHandlers; */
+NS_IMETHODIMP ProxyMIMEInfo::GetPossibleLocalHandlers(nsIArray * *aPossibleLocalHandlers)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void launchWithFile (in nsIFile aFile); */
+NS_IMETHODIMP ProxyMIMEInfo::LaunchWithFile(nsIFile *aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+static already_AddRefed<nsIHandlerInfo> WrapHandlerInfo(const HandlerInfo& aHandlerInfo) {
+ nsCOMPtr<nsIHandlerInfo> info;
+ if (aHandlerInfo.isMIMEInfo()) {
+ info = new ProxyMIMEInfo(aHandlerInfo);
+ } else {
+ info = new ProxyHandlerInfo(aHandlerInfo);
+ }
+ return info.forget();
+}
+
+} // anonymous namespace
+
+HandlerServiceParent::HandlerServiceParent()
+{
+}
+
+HandlerServiceParent::~HandlerServiceParent()
+{
+}
+
+bool HandlerServiceParent::RecvFillHandlerInfo(const HandlerInfo& aHandlerInfoData,
+ const nsCString& aOverrideType,
+ HandlerInfo* handlerInfoData)
+{
+ nsCOMPtr<nsIHandlerInfo> info(WrapHandlerInfo(aHandlerInfoData));
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->FillHandlerInfo(info, aOverrideType);
+ ContentHandlerService::nsIHandlerInfoToHandlerInfo(info, handlerInfoData);
+ return true;
+}
+
+bool HandlerServiceParent::RecvExists(const HandlerInfo& aHandlerInfo,
+ bool* exists)
+{
+ nsCOMPtr<nsIHandlerInfo> info(WrapHandlerInfo(aHandlerInfo));
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->Exists(info, exists);
+ return true;
+}
+
+bool HandlerServiceParent::RecvGetTypeFromExtension(const nsCString& aFileExtension,
+ nsCString* type)
+{
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->GetTypeFromExtension(aFileExtension, *type);
+ return true;
+}
+
+void HandlerServiceParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
diff --git a/uriloader/exthandler/HandlerServiceParent.h b/uriloader/exthandler/HandlerServiceParent.h
new file mode 100644
index 000000000..37f586018
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceParent.h
@@ -0,0 +1,31 @@
+#ifndef handler_service_parent_h
+#define handler_service_parent_h
+
+#include "mozilla/dom/PHandlerServiceParent.h"
+#include "nsIMIMEInfo.h"
+
+class nsIHandlerApp;
+
+class HandlerServiceParent final : public mozilla::dom::PHandlerServiceParent
+{
+ public:
+ HandlerServiceParent();
+ NS_INLINE_DECL_REFCOUNTING(HandlerServiceParent)
+
+ private:
+ virtual ~HandlerServiceParent();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+
+ virtual bool RecvFillHandlerInfo(const HandlerInfo& aHandlerInfoData,
+ const nsCString& aOverrideType,
+ HandlerInfo* handlerInfoData) override;
+ virtual bool RecvExists(const HandlerInfo& aHandlerInfo,
+ bool* exits) override;
+
+ virtual bool RecvGetTypeFromExtension(const nsCString& aFileExtension,
+ nsCString* type) override;
+
+};
+
+#endif
diff --git a/uriloader/exthandler/PExternalHelperApp.ipdl b/uriloader/exthandler/PExternalHelperApp.ipdl
new file mode 100644
index 000000000..c7d526c19
--- /dev/null
+++ b/uriloader/exthandler/PExternalHelperApp.ipdl
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+include protocol PChannelDiverter;
+
+namespace mozilla {
+namespace dom {
+
+protocol PExternalHelperApp
+{
+ manager PContent;
+
+parent:
+ async OnStartRequest(nsCString entityID);
+ async OnDataAvailable(nsCString data, uint64_t offset, uint32_t count);
+ async OnStopRequest(nsresult code);
+
+ async DivertToParentUsing(PChannelDiverter diverter);
+
+child:
+ async Cancel(nsresult aStatus);
+ async __delete__();
+};
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/PHandlerService.ipdl b/uriloader/exthandler/PHandlerService.ipdl
new file mode 100644
index 000000000..bcaab60ac
--- /dev/null
+++ b/uriloader/exthandler/PHandlerService.ipdl
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+
+struct HandlerApp {
+ nsString name;
+ nsString detailedDescription;
+};
+
+struct HandlerInfo {
+ nsCString type;
+ bool isMIMEInfo;
+ nsString description;
+ bool alwaysAskBeforeHandling;
+ HandlerApp preferredApplicationHandler;
+ HandlerApp[] possibleApplicationHandlers;
+ long preferredAction;
+};
+
+sync protocol PHandlerService
+{
+ manager PContent;
+
+parent:
+ sync FillHandlerInfo(HandlerInfo aHandlerInfoData,
+ nsCString aOverrideType)
+ returns (HandlerInfo handlerInfoData);
+ sync Exists(HandlerInfo aHandlerInfo)
+ returns (bool exists);
+ sync GetTypeFromExtension(nsCString aFileExtension)
+ returns (nsCString type);
+ async __delete__();
+};
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/android/nsAndroidHandlerApp.cpp b/uriloader/exthandler/android/nsAndroidHandlerApp.cpp
new file mode 100644
index 000000000..4c7ffff48
--- /dev/null
+++ b/uriloader/exthandler/android/nsAndroidHandlerApp.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAndroidHandlerApp.h"
+#include "GeneratedJNIWrappers.h"
+
+using namespace mozilla;
+
+
+NS_IMPL_ISUPPORTS(nsAndroidHandlerApp, nsIHandlerApp, nsISharingHandlerApp)
+
+nsAndroidHandlerApp::nsAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction) :
+mName(aName), mDescription(aDescription), mPackageName(aPackageName),
+ mClassName(aClassName), mMimeType(aMimeType), mAction(aAction)
+{
+}
+
+nsAndroidHandlerApp::~nsAndroidHandlerApp()
+{
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::GetName(nsAString & aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::GetDetailedDescription(nsAString & aDescription)
+{
+ aDescription.Assign(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+// XXX Workaround for bug 986975 to maintain the existing broken semantics
+template<>
+struct nsISharingHandlerApp::COMTypeInfo<nsAndroidHandlerApp, void> {
+ static const nsIID kIID;
+};
+const nsIID nsISharingHandlerApp::COMTypeInfo<nsAndroidHandlerApp, void>::kIID = NS_IHANDLERAPP_IID;
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *aRetval)
+{
+ nsCOMPtr<nsAndroidHandlerApp> aApp = do_QueryInterface(aHandlerApp);
+ *aRetval = aApp && aApp->mName.Equals(mName) &&
+ aApp->mDescription.Equals(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ nsCString uriSpec;
+ aURI->GetSpec(uriSpec);
+ return java::GeckoAppShell::OpenUriExternal(
+ uriSpec, mMimeType, mPackageName, mClassName,
+ mAction, EmptyString()) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::Share(const nsAString & data, const nsAString & title)
+{
+ return java::GeckoAppShell::OpenUriExternal(
+ data, mMimeType, mPackageName, mClassName,
+ mAction, EmptyString()) ? NS_OK : NS_ERROR_FAILURE;
+}
+
diff --git a/uriloader/exthandler/android/nsAndroidHandlerApp.h b/uriloader/exthandler/android/nsAndroidHandlerApp.h
new file mode 100644
index 000000000..bb3872463
--- /dev/null
+++ b/uriloader/exthandler/android/nsAndroidHandlerApp.h
@@ -0,0 +1,33 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAndroidHandlerApp_h
+#define nsAndroidHandlerApp_h
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIExternalSharingAppService.h"
+
+class nsAndroidHandlerApp : public nsISharingHandlerApp {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSISHARINGHANDLERAPP
+
+ nsAndroidHandlerApp(const nsAString& aName, const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType, const nsAString& aAction);
+
+private:
+ virtual ~nsAndroidHandlerApp();
+
+ nsString mName;
+ nsString mDescription;
+ nsString mPackageName;
+ nsString mClassName;
+ nsCString mMimeType;
+ nsString mAction;
+};
+#endif
diff --git a/uriloader/exthandler/android/nsExternalSharingAppService.cpp b/uriloader/exthandler/android/nsExternalSharingAppService.cpp
new file mode 100644
index 000000000..f4f8a7013
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalSharingAppService.cpp
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsExternalSharingAppService.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "AndroidBridge.h"
+#include "nsArrayUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsExternalSharingAppService, nsIExternalSharingAppService)
+
+nsExternalSharingAppService::nsExternalSharingAppService()
+{
+}
+
+nsExternalSharingAppService::~nsExternalSharingAppService()
+{
+}
+
+NS_IMETHODIMP
+nsExternalSharingAppService::ShareWithDefault(const nsAString & data,
+ const nsAString & mime,
+ const nsAString & title)
+{
+ NS_NAMED_LITERAL_STRING(sendAction, "android.intent.action.SEND");
+ const nsString emptyString = EmptyString();
+ return java::GeckoAppShell::OpenUriExternal(data,
+ mime, emptyString, emptyString, sendAction, title) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsExternalSharingAppService::GetSharingApps(const nsAString & aMIMEType,
+ uint32_t *aLen,
+ nsISharingHandlerApp ***aHandlers)
+{
+ nsresult rv;
+ NS_NAMED_LITERAL_STRING(sendAction, "android.intent.action.SEND");
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!AndroidBridge::Bridge())
+ return NS_OK;
+ AndroidBridge::Bridge()->GetHandlersForMimeType(aMIMEType, array,
+ nullptr, sendAction);
+ array->GetLength(aLen);
+ *aHandlers =
+ static_cast<nsISharingHandlerApp**>(moz_xmalloc(sizeof(nsISharingHandlerApp*)
+ * *aLen));
+ for (uint32_t i = 0; i < *aLen; i++) {
+ rv = array->QueryElementAt(i, NS_GET_IID(nsISharingHandlerApp),
+ (void**)(*aHandlers + i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/android/nsExternalSharingAppService.h b/uriloader/exthandler/android/nsExternalSharingAppService.h
new file mode 100644
index 000000000..a1e2e4363
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalSharingAppService.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_EXTERNAL_SHARING_APP_SERVICE_H
+#define NS_EXTERNAL_SHARING_APP_SERVICE_H
+#include "nsIExternalSharingAppService.h"
+
+
+#define NS_EXTERNALSHARINGAPPSERVICE_CID \
+ {0x93e2c46e, 0x0011, 0x434b, \
+ {0x81, 0x2e, 0xb6, 0xf3, 0xa8, 0x1e, 0x2a, 0x58}}
+
+class nsExternalSharingAppService final
+ : public nsIExternalSharingAppService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALSHARINGAPPSERVICE
+
+ nsExternalSharingAppService();
+
+private:
+ ~nsExternalSharingAppService();
+
+};
+
+#endif /*NS_EXTERNAL_SHARING_APP_SERVICE_H */
diff --git a/uriloader/exthandler/android/nsExternalURLHandlerService.cpp b/uriloader/exthandler/android/nsExternalURLHandlerService.cpp
new file mode 100644
index 000000000..f417b5b9f
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalURLHandlerService.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsExternalURLHandlerService.h"
+#include "nsMIMEInfoAndroid.h"
+
+NS_IMPL_ISUPPORTS(nsExternalURLHandlerService, nsIExternalURLHandlerService)
+
+nsExternalURLHandlerService::nsExternalURLHandlerService()
+{
+}
+
+nsExternalURLHandlerService::~nsExternalURLHandlerService()
+{
+}
+
+NS_IMETHODIMP
+nsExternalURLHandlerService::GetURLHandlerInfoFromOS(nsIURI *aURL,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ nsCString uriSpec;
+ aURL->GetSpec(uriSpec);
+ return nsMIMEInfoAndroid::GetMimeInfoForURL(uriSpec, found, info);
+}
diff --git a/uriloader/exthandler/android/nsExternalURLHandlerService.h b/uriloader/exthandler/android/nsExternalURLHandlerService.h
new file mode 100644
index 000000000..f2618c7e6
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalURLHandlerService.h
@@ -0,0 +1,27 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSEXTERNALURLHANDLERSERVICE_H
+#define NSEXTERNALURLHANDLERSERVICE_H
+
+#include "nsIExternalURLHandlerService.h"
+
+// {4BF1F8EF-D947-4BA3-9CD3-8C9A54A63A1C}
+#define NS_EXTERNALURLHANDLERSERVICE_CID \
+ {0x4bf1f8ef, 0xd947, 0x4ba3, {0x9c, 0xd3, 0x8c, 0x9a, 0x54, 0xa6, 0x3a, 0x1c}}
+
+class nsExternalURLHandlerService final
+ : public nsIExternalURLHandlerService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALURLHANDLERSERVICE
+
+ nsExternalURLHandlerService();
+private:
+ ~nsExternalURLHandlerService();
+};
+
+#endif // NSEXTERNALURLHANDLERSERVICE_H
diff --git a/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp b/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp
new file mode 100644
index 000000000..ee9bc8570
--- /dev/null
+++ b/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMIMEInfoAndroid.h"
+#include "AndroidBridge.h"
+#include "nsAndroidHandlerApp.h"
+#include "nsArrayUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid, nsIMIMEInfo, nsIHandlerInfo)
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ return LaunchWithFile(aFile);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LoadUriInternal(nsIURI * aURI)
+{
+ nsCString uriSpec;
+ aURI->GetSpec(uriSpec);
+
+ nsCString uriScheme;
+ aURI->GetScheme(uriScheme);
+
+ nsAutoString mimeType;
+ if (mType.Equals(uriScheme) || mType.Equals(uriSpec)) {
+ mimeType = EmptyString();
+ } else {
+ mimeType = NS_ConvertUTF8toUTF16(mType);
+ }
+
+ if (java::GeckoAppShell::OpenUriExternal(
+ NS_ConvertUTF8toUTF16(uriSpec), mimeType, EmptyString(),
+ EmptyString(), EmptyString(), EmptyString())) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool
+nsMIMEInfoAndroid::GetMimeInfoForMimeType(const nsACString& aMimeType,
+ nsMIMEInfoAndroid** aMimeInfo)
+{
+ RefPtr<nsMIMEInfoAndroid> info = new nsMIMEInfoAndroid(aMimeType);
+ mozilla::AndroidBridge* bridge = mozilla::AndroidBridge::Bridge();
+ // we don't have access to the bridge, so just assume we can handle
+ // the mime type for now and let the system deal with it
+ if (!bridge){
+ info.forget(aMimeInfo);
+ return false;
+ }
+
+ nsIHandlerApp* systemDefault = nullptr;
+
+ if (!IsUTF8(aMimeType, true))
+ return false;
+
+ NS_ConvertUTF8toUTF16 mimeType(aMimeType);
+
+ bridge->GetHandlersForMimeType(mimeType,
+ info->mHandlerApps, &systemDefault);
+
+ if (systemDefault)
+ info->mPrefApp = systemDefault;
+
+ nsAutoCString fileExt;
+ bridge->GetExtensionFromMimeType(aMimeType, fileExt);
+ info->SetPrimaryExtension(fileExt);
+
+ uint32_t len;
+ info->mHandlerApps->GetLength(&len);
+ if (len == 1) {
+ info.forget(aMimeInfo);
+ return false;
+ }
+
+ info.forget(aMimeInfo);
+ return true;
+}
+
+bool
+nsMIMEInfoAndroid::GetMimeInfoForFileExt(const nsACString& aFileExt,
+ nsMIMEInfoAndroid **aMimeInfo)
+{
+ nsCString mimeType;
+ if (mozilla::AndroidBridge::Bridge())
+ mozilla::AndroidBridge::Bridge()->
+ GetMimeTypeFromExtensions(aFileExt, mimeType);
+
+ // "*/*" means that the bridge didn't know.
+ if (mimeType.Equals(nsDependentCString("*/*"), nsCaseInsensitiveCStringComparator()))
+ return false;
+
+ bool found = GetMimeInfoForMimeType(mimeType, aMimeInfo);
+ (*aMimeInfo)->SetPrimaryExtension(aFileExt);
+ return found;
+}
+
+/**
+ * Returns MIME info for the aURL, which may contain the whole URL or only a protocol
+ */
+nsresult
+nsMIMEInfoAndroid::GetMimeInfoForURL(const nsACString &aURL,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ nsMIMEInfoAndroid *mimeinfo = new nsMIMEInfoAndroid(aURL);
+ NS_ADDREF(*info = mimeinfo);
+ *found = true;
+
+ mozilla::AndroidBridge* bridge = mozilla::AndroidBridge::Bridge();
+ if (!bridge) {
+ // we don't have access to the bridge, so just assume we can handle
+ // the protocol for now and let the system deal with it
+ return NS_OK;
+ }
+
+ nsIHandlerApp* systemDefault = nullptr;
+ bridge->GetHandlersForURL(NS_ConvertUTF8toUTF16(aURL),
+ mimeinfo->mHandlerApps, &systemDefault);
+
+ if (systemDefault)
+ mimeinfo->mPrefApp = systemDefault;
+
+
+ nsAutoCString fileExt;
+ nsAutoCString mimeType;
+ mimeinfo->GetType(mimeType);
+ bridge->GetExtensionFromMimeType(mimeType, fileExt);
+ mimeinfo->SetPrimaryExtension(fileExt);
+
+ uint32_t len;
+ mimeinfo->mHandlerApps->GetLength(&len);
+ if (len == 1) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ *found = false;
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetType(nsACString& aType)
+{
+ aType.Assign(mType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetDescription(nsAString& aDesc)
+{
+ aDesc.Assign(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetDescription(const nsAString& aDesc)
+{
+ mDescription.Assign(aDesc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPreferredApplicationHandler(nsIHandlerApp** aApp)
+{
+ *aApp = mPrefApp;
+ NS_IF_ADDREF(*aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPreferredApplicationHandler(nsIHandlerApp* aApp)
+{
+ mPrefApp = aApp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPossibleApplicationHandlers(nsIMutableArray **aHandlerApps)
+{
+ if (!mHandlerApps)
+ mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mHandlerApps)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *aHandlerApps = mHandlerApps;
+ NS_IF_ADDREF(*aHandlerApps);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetHasDefaultHandler(bool* aHasDefault)
+{
+ uint32_t len;
+ *aHasDefault = false;
+ if (!mHandlerApps)
+ return NS_OK;
+
+ if (NS_FAILED(mHandlerApps->GetLength(&len)))
+ return NS_OK;
+
+ if (len == 0)
+ return NS_OK;
+
+ *aHasDefault = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetDefaultDescription(nsAString& aDesc)
+{
+ aDesc.Assign(EmptyString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* req)
+{
+ return mPrefApp->LaunchWithURI(aURI, req);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPreferredAction(nsHandlerInfoAction* aPrefAction)
+{
+ *aPrefAction = mPrefAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPreferredAction(nsHandlerInfoAction aPrefAction)
+{
+ mPrefAction = aPrefAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk)
+{
+ *aAlwaysAsk = mAlwaysAsk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetAlwaysAskBeforeHandling(bool aAlwaysAsk)
+{
+ mAlwaysAsk = aAlwaysAsk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetFileExtensions(nsIUTF8StringEnumerator** aResult)
+{
+ return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetFileExtensions(const nsACString & aExtensions)
+{
+ mExtensions.Clear();
+ nsCString extList(aExtensions);
+
+ int32_t breakLocation = -1;
+ while ( (breakLocation = extList.FindChar(',')) != -1)
+ {
+ mExtensions.AppendElement(Substring(extList.get(), extList.get() + breakLocation));
+ extList.Cut(0, breakLocation + 1);
+ }
+ if (!extList.IsEmpty())
+ mExtensions.AppendElement(extList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::ExtensionExists(const nsACString & aExtension, bool *aRetVal)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+
+ nsCString mimeType;
+ if (mozilla::AndroidBridge::Bridge()) {
+ mozilla::AndroidBridge::Bridge()->
+ GetMimeTypeFromExtensions(aExtension, mimeType);
+ }
+
+ // "*/*" means the bridge didn't find anything (i.e., extension doesn't exist).
+ *aRetVal = !mimeType.Equals(nsDependentCString("*/*"), nsCaseInsensitiveCStringComparator());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::AppendExtension(const nsACString & aExtension)
+{
+ mExtensions.AppendElement(aExtension);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPrimaryExtension(nsACString & aPrimaryExtension)
+{
+ if (!mExtensions.Length())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aPrimaryExtension = mExtensions[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPrimaryExtension(const nsACString & aExtension)
+{
+ uint32_t extCount = mExtensions.Length();
+ uint8_t i;
+ bool found = false;
+ for (i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ mExtensions.RemoveElementAt(i);
+ }
+
+ mExtensions.InsertElementAt(0, aExtension);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetMIMEType(nsACString & aMIMEType)
+{
+ aMIMEType.Assign(mType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::Equals(nsIMIMEInfo *aMIMEInfo, bool *aRetVal)
+{
+ if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString type;
+ nsresult rv = aMIMEInfo->GetMIMEType(type);
+ if (NS_FAILED(rv)) return rv;
+
+ *aRetVal = mType.Equals(type);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPossibleLocalHandlers(nsIArray * *aPossibleLocalHandlers)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchWithFile(nsIFile *aFile)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewFileURI(getter_AddRefs(uri), aFile);
+ return LoadUriInternal(uri);
+}
+
+nsMIMEInfoAndroid::nsMIMEInfoAndroid(const nsACString& aMIMEType) :
+ mType(aMIMEType), mAlwaysAsk(true),
+ mPrefAction(nsIMIMEInfo::useHelperApp)
+{
+ mPrefApp = new nsMIMEInfoAndroid::SystemChooser(this);
+ nsresult rv;
+ mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ mHandlerApps->AppendElement(mPrefApp, false);
+}
+
+NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid::SystemChooser, nsIHandlerApp)
+
+
+nsresult nsMIMEInfoAndroid::SystemChooser::GetName(nsAString & aName) {
+ aName.AssignLiteral(u"Android chooser");
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::SetName(const nsAString&) {
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::GetDetailedDescription(nsAString & aDesc) {
+ aDesc.AssignLiteral(u"Android's default handler application chooser");
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::SetDetailedDescription(const nsAString&) {
+ return NS_OK;
+}
+
+// XXX Workaround for bug 986975 to maintain the existing broken semantics
+template<>
+struct nsIHandlerApp::COMTypeInfo<nsMIMEInfoAndroid::SystemChooser, void> {
+ static const nsIID kIID;
+};
+const nsIID nsIHandlerApp::COMTypeInfo<nsMIMEInfoAndroid::SystemChooser, void>::kIID = NS_IHANDLERAPP_IID;
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::Equals(nsIHandlerApp *aHandlerApp, bool *aRetVal) {
+ nsCOMPtr<nsMIMEInfoAndroid::SystemChooser> info = do_QueryInterface(aHandlerApp);
+ if (info)
+ return mOuter->Equals(info->mOuter, aRetVal);
+ *aRetVal = false;
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor*)
+{
+ return mOuter->LoadUriInternal(aURI);
+}
diff --git a/uriloader/exthandler/android/nsMIMEInfoAndroid.h b/uriloader/exthandler/android/nsMIMEInfoAndroid.h
new file mode 100644
index 000000000..569d715bd
--- /dev/null
+++ b/uriloader/exthandler/android/nsMIMEInfoAndroid.h
@@ -0,0 +1,60 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoAndroid_h
+#define nsMIMEInfoAndroid_h
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIMutableArray.h"
+#include "nsAndroidHandlerApp.h"
+
+class nsMIMEInfoAndroid final : public nsIMIMEInfo
+{
+public:
+ static MOZ_MUST_USE bool
+ GetMimeInfoForMimeType(const nsACString& aMimeType,
+ nsMIMEInfoAndroid** aMimeInfo);
+ static MOZ_MUST_USE bool
+ GetMimeInfoForFileExt(const nsACString& aFileExt,
+ nsMIMEInfoAndroid** aMimeInfo);
+
+ static MOZ_MUST_USE nsresult
+ GetMimeInfoForURL(const nsACString &aURL, bool *found,
+ nsIHandlerInfo **info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMIMEINFO
+ NS_DECL_NSIHANDLERINFO
+
+ nsMIMEInfoAndroid(const nsACString& aMIMEType);
+
+private:
+ ~nsMIMEInfoAndroid() {}
+
+ virtual MOZ_MUST_USE nsresult LaunchDefaultWithFile(nsIFile* aFile);
+ virtual MOZ_MUST_USE nsresult LoadUriInternal(nsIURI *aURI);
+ nsCOMPtr<nsIMutableArray> mHandlerApps;
+ nsCString mType;
+ nsTArray<nsCString> mExtensions;
+ bool mAlwaysAsk;
+ nsHandlerInfoAction mPrefAction;
+ nsString mDescription;
+ nsCOMPtr<nsIHandlerApp> mPrefApp;
+
+public:
+ class SystemChooser final : public nsIHandlerApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ SystemChooser(nsMIMEInfoAndroid* aOuter): mOuter(aOuter) {}
+
+ private:
+ ~SystemChooser() {}
+
+ nsMIMEInfoAndroid* mOuter;
+ };
+};
+
+#endif /* nsMIMEInfoAndroid_h */
diff --git a/uriloader/exthandler/android/nsOSHelperAppService.cpp b/uriloader/exthandler/android/nsOSHelperAppService.cpp
new file mode 100644
index 000000000..3a170dcf6
--- /dev/null
+++ b/uriloader/exthandler/android/nsOSHelperAppService.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoAndroid.h"
+#include "AndroidBridge.h"
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ RefPtr<nsMIMEInfoAndroid> mimeInfo;
+ *aFound = false;
+ if (!aMIMEType.IsEmpty())
+ *aFound =
+ nsMIMEInfoAndroid::GetMimeInfoForMimeType(aMIMEType,
+ getter_AddRefs(mimeInfo));
+ if (!*aFound)
+ *aFound =
+ nsMIMEInfoAndroid::GetMimeInfoForFileExt(aFileExt,
+ getter_AddRefs(mimeInfo));
+
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ if (!*aFound)
+ mimeInfo = new nsMIMEInfoAndroid(aMIMEType);
+
+ return mimeInfo.forget();
+}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists)
+{
+ *aExists = mozilla::AndroidBridge::Bridge()->GetHandlersForURL(NS_ConvertUTF8toUTF16(aScheme));
+ return NS_OK;
+}
+
+nsresult nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ return nsMIMEInfoAndroid::GetMimeInfoForURL(aScheme, found, info);
+}
+
+nsIHandlerApp*
+nsOSHelperAppService::CreateAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction)
+{
+ return new nsAndroidHandlerApp(aName, aDescription, aPackageName,
+ aClassName, aMimeType, aAction);
+}
diff --git a/uriloader/exthandler/android/nsOSHelperAppService.h b/uriloader/exthandler/android/nsOSHelperAppService.h
new file mode 100644
index 000000000..4f3623894
--- /dev/null
+++ b/uriloader/exthandler/android/nsOSHelperAppService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h
+#define nsOSHelperAppService_h
+
+#include "nsCExternalHandlerService.h"
+#include "nsExternalHelperAppService.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ virtual already_AddRefed<nsIMIMEInfo>
+ GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+
+ virtual MOZ_MUST_USE nsresult
+ OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists);
+
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ static nsIHandlerApp*
+ CreateAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction = EmptyString());
+};
+
+#endif /* nsOSHelperAppService_h */
diff --git a/uriloader/exthandler/gonk/nsOSHelperAppService.cpp b/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
new file mode 100644
index 000000000..d1342ec18
--- /dev/null
+++ b/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoImpl.h"
+
+class nsGonkMIMEInfo : public nsMIMEInfoImpl {
+public:
+ nsGonkMIMEInfo(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) { }
+
+protected:
+ virtual nsresult LoadUriInternal(nsIURI *aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ *aFound = false;
+ // Even if we return false for aFound, we need to return a working
+ // nsIMIMEInfo implementation that will be used by the caller.
+ RefPtr<nsGonkMIMEInfo> mimeInfo = new nsGonkMIMEInfo(aMIMEType);
+ return mimeInfo.forget();
+}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists)
+{
+ *aExists = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/gonk/nsOSHelperAppService.h b/uriloader/exthandler/gonk/nsOSHelperAppService.h
new file mode 100644
index 000000000..99a280bfc
--- /dev/null
+++ b/uriloader/exthandler/gonk/nsOSHelperAppService.h
@@ -0,0 +1,39 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsOSHelperAppService_h
+#define nsOSHelperAppService_h
+
+#include "nsCExternalHandlerService.h"
+#include "nsExternalHelperAppService.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ virtual already_AddRefed<nsIMIMEInfo>
+ GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+
+ virtual MOZ_MUST_USE nsresult
+ OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists);
+};
+
+#endif /* nsOSHelperAppService_h */
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.cpp b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
new file mode 100644
index 000000000..1a428a62d
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDecodeAppleFile.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+
+NS_IMPL_ADDREF(nsDecodeAppleFile)
+NS_IMPL_RELEASE(nsDecodeAppleFile)
+
+NS_INTERFACE_MAP_BEGIN(nsDecodeAppleFile)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsDecodeAppleFile::nsDecodeAppleFile()
+{
+ m_state = parseHeaders;
+ m_dataBufferLength = 0;
+ m_dataBuffer = (unsigned char*) PR_MALLOC(MAX_BUFFERSIZE);
+ m_entries = nullptr;
+ m_rfRefNum = -1;
+ m_totalDataForkWritten = 0;
+ m_totalResourceForkWritten = 0;
+ m_headerOk = false;
+
+ m_comment[0] = 0;
+ memset(&m_dates, 0, sizeof(m_dates));
+ memset(&m_finderInfo, 0, sizeof(m_dates));
+ memset(&m_finderExtraInfo, 0, sizeof(m_dates));
+}
+
+nsDecodeAppleFile::~nsDecodeAppleFile()
+{
+
+ PR_FREEIF(m_dataBuffer);
+ if (m_entries)
+ delete [] m_entries;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Initialize(nsIOutputStream *output, nsIFile *file)
+{
+ m_output = output;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(file);
+ macFile->GetTargetFSSpec(&m_fsFileSpec);
+
+ m_offset = 0;
+ m_dataForkOffset = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Close(void)
+{
+ nsresult rv;
+ rv = m_output->Close();
+
+ int32_t i;
+
+ if (m_rfRefNum != -1)
+ FSClose(m_rfRefNum);
+
+ /* Check if the file is complete and if it's the case, write file attributes */
+ if (m_headerOk)
+ {
+ bool dataOk = true; /* It's ok if the file doesn't have a datafork, therefore set it to true by default. */
+ if (m_headers.magic == APPLESINGLE_MAGIC)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_DFORK == m_entries[i].id)
+ {
+ dataOk = (bool)(m_totalDataForkWritten == m_entries[i].length);
+ break;
+ }
+ }
+
+ bool resourceOk = FALSE;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_RFORK == m_entries[i].id)
+ {
+ resourceOk = (bool)(m_totalResourceForkWritten == m_entries[i].length);
+ break;
+ }
+
+ if (dataOk && resourceOk)
+ {
+ HFileInfo *fpb;
+ CInfoPBRec cipbr;
+
+ fpb = (HFileInfo *) &cipbr;
+ fpb->ioVRefNum = m_fsFileSpec.vRefNum;
+ fpb->ioDirID = m_fsFileSpec.parID;
+ fpb->ioNamePtr = m_fsFileSpec.name;
+ fpb->ioFDirIndex = 0;
+ PBGetCatInfoSync(&cipbr);
+
+ /* set finder info */
+ memcpy(&fpb->ioFlFndrInfo, &m_finderInfo, sizeof (FInfo));
+ memcpy(&fpb->ioFlXFndrInfo, &m_finderExtraInfo, sizeof (FXInfo));
+ fpb->ioFlFndrInfo.fdFlags &= 0xfc00; /* clear flags maintained by finder */
+
+ /* set file dates */
+ fpb->ioFlCrDat = m_dates.create - CONVERT_TIME;
+ fpb->ioFlMdDat = m_dates.modify - CONVERT_TIME;
+ fpb->ioFlBkDat = m_dates.backup - CONVERT_TIME;
+
+ /* update file info */
+ fpb->ioDirID = fpb->ioFlParID;
+ PBSetCatInfoSync(&cipbr);
+
+ /* set comment */
+ IOParam vinfo;
+ GetVolParmsInfoBuffer vp;
+ DTPBRec dtp;
+
+ memset((void *) &vinfo, 0, sizeof (vinfo));
+ vinfo.ioVRefNum = fpb->ioVRefNum;
+ vinfo.ioBuffer = (Ptr) &vp;
+ vinfo.ioReqCount = sizeof (vp);
+ if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr && ((vp.vMAttrib >> bHasDesktopMgr) & 1))
+ {
+ memset((void *) &dtp, 0, sizeof (dtp));
+ dtp.ioVRefNum = fpb->ioVRefNum;
+ if (PBDTGetPath(&dtp) == noErr)
+ {
+ dtp.ioDTBuffer = (Ptr) &m_comment[1];
+ dtp.ioNamePtr = fpb->ioNamePtr;
+ dtp.ioDirID = fpb->ioDirID;
+ dtp.ioDTReqCount = m_comment[0];
+ if (PBDTSetCommentSync(&dtp) == noErr)
+ PBDTFlushSync(&dtp);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Flush(void)
+{
+ return m_output->Flush();
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteFrom(inStr, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteSegments(reader, closure, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::IsNonBlocking(bool *aNonBlocking)
+{
+ return m_output->IsNonBlocking(aNonBlocking);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Write(const char *buffer, uint32_t bufferSize, uint32_t* writeCount)
+{
+ /* WARNING: to simplify my life, I presume that I should get all appledouble headers in the first block,
+ else I would have to implement a buffer */
+
+ const char * buffPtr = buffer;
+ uint32_t dataCount;
+ int32_t i;
+ nsresult rv = NS_OK;
+
+ *writeCount = 0;
+
+ while (bufferSize > 0 && NS_SUCCEEDED(rv))
+ {
+ switch (m_state)
+ {
+ case parseHeaders :
+ dataCount = sizeof(ap_header) - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == sizeof(ap_header))
+ {
+ memcpy(&m_headers, m_dataBuffer, sizeof(ap_header));
+
+ /* Check header to be sure we are dealing with the right kind of data, else just write it to the data fork. */
+ if ((m_headers.magic == APPLEDOUBLE_MAGIC || m_headers.magic == APPLESINGLE_MAGIC) &&
+ m_headers.version == VERSION && m_headers.entriesCount)
+ {
+ /* Just to be sure, the filler must contains only 0 */
+ for (i = 0; i < 4 && m_headers.fill[i] == 0L; i ++)
+ ;
+ if (i == 4)
+ m_state = parseEntries;
+ }
+ m_dataBufferLength = 0;
+
+ if (m_state == parseHeaders)
+ {
+ dataCount = 0;
+ m_state = parseWriteThrough;
+ }
+ }
+ break;
+
+ case parseEntries :
+ if (!m_entries)
+ {
+ m_entries = new ap_entry[m_headers.entriesCount];
+ if (!m_entries)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint32_t entriesSize = sizeof(ap_entry) * m_headers.entriesCount;
+ dataCount = entriesSize - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == entriesSize)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ {
+ memcpy(&m_entries[i], &m_dataBuffer[i * sizeof(ap_entry)], sizeof(ap_entry));
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ {
+ uint32_t offset = m_entries[i].offset + m_entries[i].length;
+ if (offset > m_dataForkOffset)
+ m_dataForkOffset = offset;
+ }
+ }
+ m_headerOk = true;
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseLookupPart :
+ /* which part are we parsing? */
+ m_currentPartID = -1;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (m_offset == m_entries[i].offset && m_entries[i].length)
+ {
+ m_currentPartID = m_entries[i].id;
+ m_currentPartLength = m_entries[i].length;
+ m_currentPartCount = 0;
+
+ switch (m_currentPartID)
+ {
+ case ENT_DFORK : m_state = parseDataFork; break;
+ case ENT_RFORK : m_state = parseResourceFork; break;
+
+ case ENT_COMMENT :
+ case ENT_DATES :
+ case ENT_FINFO :
+ m_dataBufferLength = 0;
+ m_state = parsePart;
+ break;
+
+ default : m_state = parseSkipPart; break;
+ }
+ break;
+ }
+
+ if (m_currentPartID == -1)
+ {
+ /* maybe is the datafork of an appledouble file? */
+ if (m_offset == m_dataForkOffset)
+ {
+ m_currentPartID = ENT_DFORK;
+ m_currentPartLength = -1;
+ m_currentPartCount = 0;
+ m_state = parseDataFork;
+ }
+ else
+ dataCount = 1;
+ }
+ break;
+
+ case parsePart :
+ dataCount = m_currentPartLength - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == m_currentPartLength)
+ {
+ switch (m_currentPartID)
+ {
+ case ENT_COMMENT :
+ m_comment[0] = m_currentPartLength > 255 ? 255 : m_currentPartLength;
+ memcpy(&m_comment[1], buffPtr, m_comment[0]);
+ break;
+ case ENT_DATES :
+ if (m_currentPartLength == sizeof(m_dates))
+ memcpy(&m_dates, buffPtr, m_currentPartLength);
+ break;
+ case ENT_FINFO :
+ if (m_currentPartLength == (sizeof(m_finderInfo) + sizeof(m_finderExtraInfo)))
+ {
+ memcpy(&m_finderInfo, buffPtr, sizeof(m_finderInfo));
+ memcpy(&m_finderExtraInfo, buffPtr + sizeof(m_finderInfo), sizeof(m_finderExtraInfo));
+ }
+ break;
+ }
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseSkipPart :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ break;
+
+ case parseDataFork :
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ dataCount = bufferSize;
+ else
+ {
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ }
+
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ m_totalDataForkWritten += dataCount;
+ }
+
+ break;
+
+ case parseResourceFork :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+
+ if (m_rfRefNum == -1)
+ {
+ if (noErr != FSpOpenRF(&m_fsFileSpec, fsWrPerm, &m_rfRefNum))
+ return NS_ERROR_FAILURE;
+ }
+
+ long count = dataCount;
+ if (noErr != FSWrite(m_rfRefNum, &count, buffPtr) || count != dataCount)
+ return NS_ERROR_FAILURE;
+ m_totalResourceForkWritten += dataCount;
+ break;
+
+ case parseWriteThrough :
+ dataCount = bufferSize;
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+
+ if (dataCount)
+ {
+ *writeCount += dataCount;
+ bufferSize -= dataCount;
+ buffPtr += dataCount;
+ m_currentPartCount += dataCount;
+ m_offset += dataCount;
+ dataCount = 0;
+ }
+ }
+
+ return rv;
+}
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.h b/uriloader/exthandler/mac/nsDecodeAppleFile.h
new file mode 100644
index 000000000..cea2d701e
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDecodeAppleFile_h__
+#define nsDecodeAppleFile_h__
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIOutputStream.h"
+
+/*
+** applefile definitions used
+*/
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=mac68k
+#endif
+
+#define APPLESINGLE_MAGIC 0x00051600L
+#define APPLEDOUBLE_MAGIC 0x00051607L
+#define VERSION 0x00020000
+
+#define NUM_ENTRIES 6
+
+#define ENT_DFORK 1L
+#define ENT_RFORK 2L
+#define ENT_NAME 3L
+#define ENT_COMMENT 4L
+#define ENT_DATES 8L
+#define ENT_FINFO 9L
+
+#define CONVERT_TIME 1265437696L
+
+/*
+** data type used in the header decoder.
+*/
+typedef struct ap_header
+{
+ int32_t magic;
+ int32_t version;
+ int32_t fill[4];
+ int16_t entriesCount;
+
+} ap_header;
+
+typedef struct ap_entry
+{
+ int32_t id;
+ int32_t offset;
+ int32_t length;
+
+} ap_entry;
+
+typedef struct ap_dates
+{
+ int32_t create, modify, backup, access;
+
+} ap_dates;
+
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=reset
+#endif
+
+/*
+**Error codes
+*/
+enum {
+ errADNotEnoughData = -12099,
+ errADNotSupported,
+ errADBadVersion
+};
+
+
+class nsDecodeAppleFile : public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsDecodeAppleFile();
+ virtual ~nsDecodeAppleFile();
+
+ MOZ_MUST_USE nsresult Initialize(nsIOutputStream *output, nsIFile *file);
+
+private:
+ #define MAX_BUFFERSIZE 1024
+ enum ParserState {parseHeaders, parseEntries, parseLookupPart, parsePart, parseSkipPart,
+ parseDataFork, parseResourceFork, parseWriteThrough};
+
+ nsCOMPtr<nsIOutputStream> m_output;
+ FSSpec m_fsFileSpec;
+ SInt16 m_rfRefNum;
+
+ unsigned char * m_dataBuffer;
+ int32_t m_dataBufferLength;
+ ParserState m_state;
+ ap_header m_headers;
+ ap_entry * m_entries;
+ int32_t m_offset;
+ int32_t m_dataForkOffset;
+ int32_t m_totalDataForkWritten;
+ int32_t m_totalResourceForkWritten;
+ bool m_headerOk;
+
+ int32_t m_currentPartID;
+ int32_t m_currentPartLength;
+ int32_t m_currentPartCount;
+
+ Str255 m_comment;
+ ap_dates m_dates;
+ FInfo m_finderInfo;
+ FXInfo m_finderExtraInfo;
+};
+
+#endif
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.h b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
new file mode 100644
index 000000000..402ec5295
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSLOCALHANDLERAPPMAC_H_
+#define NSLOCALHANDLERAPPMAC_H_
+
+#include "nsLocalHandlerApp.h"
+
+class nsLocalHandlerAppMac : public nsLocalHandlerApp {
+
+ public:
+ nsLocalHandlerAppMac() { }
+
+ nsLocalHandlerAppMac(const char16_t *aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+
+ nsLocalHandlerAppMac(const nsAString & aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+ virtual ~nsLocalHandlerAppMac() { }
+
+ NS_IMETHOD LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* aWindowContext);
+ NS_IMETHOD GetName(nsAString& aName);
+};
+
+#endif /*NSLOCALHANDLERAPPMAC_H_*/
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
new file mode 100644
index 000000000..fd633ab72
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsLocalHandlerAppMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIURI.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsLocalHandlerAppMac::GetName(nsAString& aName)
+{
+ if (mExecutable) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mExecutable);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aName);
+ }
+ }
+
+ return nsLocalHandlerApp::GetName(aName);
+}
+
+/**
+ * mostly copy/pasted from nsMacShellService.cpp (which is in browser/,
+ * so we can't depend on it here). This code probably really wants to live
+ * somewhere more central (see bug 389922).
+ */
+NS_IMETHODIMP
+nsLocalHandlerAppMac::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(mExecutable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CFURLRef appURL;
+ rv = lfm->GetCFURL(&appURL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString uriSpec;
+ aURI->GetAsciiSpec(uriSpec);
+
+ const UInt8* uriString = reinterpret_cast<const UInt8*>(uriSpec.get());
+ CFURLRef uri = ::CFURLCreateWithBytes(NULL, uriString, uriSpec.Length(),
+ kCFStringEncodingUTF8, NULL);
+ if (!uri) {
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CFArrayRef uris = ::CFArrayCreate(NULL, reinterpret_cast<const void**>(&uri),
+ 1, NULL);
+ if (!uris) {
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LSLaunchURLSpec launchSpec;
+ launchSpec.appURL = appURL;
+ launchSpec.itemURLs = uris;
+ launchSpec.passThruParams = NULL;
+ launchSpec.launchFlags = kLSLaunchDefaults;
+ launchSpec.asyncRefCon = NULL;
+
+ OSErr err = ::LSOpenFromURLSpec(&launchSpec, NULL);
+
+ ::CFRelease(uris);
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+
+ return err != noErr ? NS_ERROR_FAILURE : NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.h b/uriloader/exthandler/mac/nsMIMEInfoMac.h
new file mode 100644
index 000000000..298357f75
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoMac_h_
+#define nsMIMEInfoMac_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoMac : public nsMIMEInfoImpl {
+ public:
+ explicit nsMIMEInfoMac(const char* aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
+ explicit nsMIMEInfoMac(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
+ nsMIMEInfoMac(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoImpl(aType, aClass) {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+ protected:
+ virtual MOZ_MUST_USE nsresult LoadUriInternal(nsIURI *aURI);
+#ifdef DEBUG
+ virtual MOZ_MUST_USE nsresult LaunchDefaultWithFile(nsIFile* aFile) {
+ NS_NOTREACHED("do not call this method, use LaunchWithFile");
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+ static MOZ_MUST_USE nsresult OpenApplicationWithURI(nsIFile *aApplication,
+ const nsCString& aURI);
+
+ NS_IMETHOD GetDefaultDescription(nsAString& aDefaultDescription);
+
+};
+
+
+#endif
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.mm b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
new file mode 100644
index 000000000..64d3a82de
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsMIMEInfoMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsMIMEInfoMac::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ if (mDefaultApplication) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mDefaultApplication);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aDefaultDescription);
+ }
+ }
+
+ return nsMIMEInfoImpl::GetDefaultDescription(aDefaultDescription);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoMac::LaunchWithFile(nsIFile *aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<nsIFile> application;
+ nsresult rv;
+
+ NS_ASSERTION(mClass == eMIMEInfo, "only MIME infos are currently allowed"
+ "to pass content by value");
+
+ if (mPreferredAction == useHelperApp) {
+
+ // we don't yet support passing content by value (rather than reference)
+ // to web apps. at some point, we will probably want to.
+ nsCOMPtr<nsILocalHandlerApp> localHandlerApp =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localHandlerApp->GetExecutable(getter_AddRefs(application));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ } else if (mPreferredAction == useSystemDefault) {
+ application = mDefaultApplication;
+ }
+ else
+ return NS_ERROR_INVALID_ARG;
+
+ // if we've already got an app, just QI so we have the launchWithDoc method
+ nsCOMPtr<nsILocalFileMac> app;
+ if (application) {
+ app = do_QueryInterface(application, &rv);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // otherwise ask LaunchServices for an app directly
+ nsCOMPtr<nsILocalFileMac> tempFile = do_QueryInterface(aFile, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ FSRef tempFileRef;
+ tempFile->GetFSRef(&tempFileRef);
+
+ FSRef appFSRef;
+ if (::LSGetApplicationForItem(&tempFileRef, kLSRolesAll, &appFSRef, nullptr) == noErr)
+ {
+ app = (do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!app) return NS_ERROR_FAILURE;
+ app->InitWithFSRef(&appFSRef);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return app->LaunchWithDoc(aFile, false);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsMIMEInfoMac::LoadUriInternal(nsIURI *aURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString uri;
+ aURI->GetSpec(uri);
+ if (!uri.IsEmpty()) {
+ CFURLRef myURLRef = ::CFURLCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)uri.get(),
+ strlen(uri.get()),
+ kCFStringEncodingUTF8,
+ NULL);
+ if (myURLRef) {
+ OSStatus status = ::LSOpenCFURLRef(myURLRef, NULL);
+ if (status == noErr)
+ rv = NS_OK;
+ ::CFRelease(myURLRef);
+ }
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.h b/uriloader/exthandler/mac/nsOSHelperAppService.h
new file mode 100644
index 000000000..7371e1f42
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications. This is the Mac version.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // method overrides --> used to hook the mime service into internet config....
+ NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo);
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool * aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got out of the
+ // rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual MOZ_MUST_USE nsresult GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile);
+
+ MOZ_MUST_USE nsresult OSProtocolHandlerExists(const char * aScheme,
+ bool * aHandlerExists);
+
+private:
+ uint32_t mPermissions;
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.mm b/uriloader/exthandler/mac/nsOSHelperAppService.mm
new file mode 100644
index 000000000..00f12f544
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.mm
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "nsOSHelperAppService.h"
+#include "nsObjCExceptions.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsMimeTypes.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "nsMemory.h"
+#include "nsCRT.h"
+#include "nsMIMEInfoMac.h"
+#include "nsEmbedCID.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+// chrome URL's
+#define HELPERAPPLAUNCHER_BUNDLE_URL "chrome://global/locale/helperAppLauncher.properties"
+#define BRAND_BUNDLE_URL "chrome://branding/locale/brand.properties"
+
+using mozilla::LogLevel;
+
+/* This is an undocumented interface (in the Foundation framework) that has
+ * been stable since at least 10.2.8 and is still present on SnowLeopard.
+ * Furthermore WebKit has three public methods (in WebKitSystemInterface.h)
+ * that are thin wrappers around this interface's last three methods. So
+ * it's unlikely to change anytime soon. Now that we're no longer using
+ * Internet Config Services, this is the only way to look up a MIME type
+ * from an extension, or vice versa.
+ */
+@class NSURLFileTypeMappingsInternal;
+
+@interface NSURLFileTypeMappings : NSObject
+{
+ NSURLFileTypeMappingsInternal *_internal;
+}
+
++ (NSURLFileTypeMappings*)sharedMappings;
+- (NSString*)MIMETypeForExtension:(NSString*)aString;
+- (NSString*)preferredExtensionForMIMEType:(NSString*)aString;
+- (NSArray*)extensionsForMIMEType:(NSString*)aString;
+@end
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+ mode_t mask = umask(0777);
+ umask(mask);
+ mPermissions = 0666 & ~mask;
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ // CFStringCreateWithBytes() can fail even if we're not out of memory --
+ // for example if the 'bytes' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef schemeString = ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)aProtocolScheme,
+ strlen(aProtocolScheme),
+ kCFStringEncodingUTF8,
+ false);
+ if (schemeString) {
+ // LSCopyDefaultHandlerForURLScheme() can fail to find the default handler
+ // for aProtocolScheme when it's never been explicitly set (using
+ // LSSetDefaultHandlerForURLScheme()). For example, Safari is the default
+ // handler for the "http" scheme on a newly installed copy of OS X. But
+ // this (presumably) wasn't done using LSSetDefaultHandlerForURLScheme(),
+ // so LSCopyDefaultHandlerForURLScheme() will fail to find Safari. To get
+ // around this we use LSCopyAllHandlersForURLScheme() instead -- which seems
+ // never to fail.
+ // http://lists.apple.com/archives/Carbon-dev/2007/May/msg00349.html
+ // http://www.realsoftware.com/listarchives/realbasic-nug/2008-02/msg00119.html
+ CFArrayRef handlerArray = ::LSCopyAllHandlersForURLScheme(schemeString);
+ *aHandlerExists = !!handlerArray;
+ if (handlerArray)
+ ::CFRelease(handlerArray);
+ ::CFRelease(schemeString);
+ } else {
+ *aHandlerExists = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ CFStringRef schemeCFString =
+ ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8 *)PromiseFlatCString(aScheme).get(),
+ aScheme.Length(),
+ kCFStringEncodingUTF8,
+ false);
+
+ if (schemeCFString) {
+ CFStringRef lookupCFString = ::CFStringCreateWithFormat(NULL, NULL, CFSTR("%@:"), schemeCFString);
+
+ if (lookupCFString) {
+ CFURLRef lookupCFURL = ::CFURLCreateWithString(NULL, lookupCFString, NULL);
+
+ if (lookupCFURL) {
+ CFURLRef appCFURL = NULL;
+ OSStatus theErr = ::LSGetApplicationForURL(lookupCFURL, kLSRolesAll, NULL, &appCFURL);
+
+ if (theErr == noErr) {
+ CFBundleRef handlerBundle = ::CFBundleCreate(NULL, appCFURL);
+
+ if (handlerBundle) {
+ // Get the human-readable name of the default handler bundle
+ CFStringRef bundleName =
+ (CFStringRef)::CFBundleGetValueForInfoDictionaryKey(handlerBundle,
+ kCFBundleNameKey);
+
+ if (bundleName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex bundleNameLength = ::CFStringGetLength(bundleName);
+ buffer.SetLength(bundleNameLength);
+ ::CFStringGetCharacters(bundleName, CFRangeMake(0, bundleNameLength),
+ buffer.Elements());
+ _retval.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), bundleNameLength);
+ rv = NS_OK;
+ }
+
+ ::CFRelease(handlerBundle);
+ }
+
+ ::CFRelease(appCFURL);
+ }
+
+ ::CFRelease(lookupCFURL);
+ }
+
+ ::CFRelease(lookupCFString);
+ }
+
+ ::CFRelease(schemeCFString);
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath, nsIFile ** aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ CFURLRef pathAsCFURL;
+ CFStringRef pathAsCFString = ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(aPlatformAppPath),
+ NS_strlen(aPlatformAppPath));
+ if (!pathAsCFString)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (::CFStringGetCharacterAtIndex(pathAsCFString, 0) == '/') {
+ // we have a Posix path
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLPOSIXPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ // if it doesn't start with a / it's not an absolute Posix path
+ // let's check if it's a HFS path left over from old preferences
+
+ // If it starts with a ':' char, it's not an absolute HFS path
+ // so bail for that, and also if it's empty
+ if (::CFStringGetLength(pathAsCFString) == 0 ||
+ ::CFStringGetCharacterAtIndex(pathAsCFString, 0) == ':')
+ {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLHFSPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ rv = localFile->InitWithCFURL(pathAsCFURL);
+ ::CFRelease(pathAsCFString);
+ ::CFRelease(pathAsCFURL);
+ if (NS_FAILED(rv))
+ return rv;
+ *aFile = localFile;
+ NS_IF_ADDREF(*aFile);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo)
+{
+ return nsExternalHelperAppService::GetFromTypeAndExtension(aType, aFileExt, aMIMEInfo);
+}
+
+// Returns the MIME types an application bundle explicitly claims to handle.
+// Returns NULL if aAppRef doesn't explicitly claim to handle any MIME types.
+// If the return value is non-NULL, the caller is responsible for freeing it.
+// This isn't necessarily the same as the MIME types the application bundle
+// is registered to handle in the Launch Services database. (For example
+// the Preview application is normally registered to handle the application/pdf
+// MIME type, even though it doesn't explicitly claim to handle *any* MIME
+// types in its Info.plist. This is probably because Preview does explicitly
+// claim to handle the com.adobe.pdf UTI, and Launch Services somehow
+// translates this into a claim to support the application/pdf MIME type.
+// Launch Services doesn't provide any APIs (documented or undocumented) to
+// query which MIME types a given application is registered to handle. So any
+// app that wants this information (e.g. the Default Apps pref pane) needs to
+// iterate through the entire Launch Services database -- a process which can
+// take several seconds.)
+static CFArrayRef GetMIMETypesHandledByApp(FSRef *aAppRef)
+{
+ CFURLRef appURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aAppRef);
+ if (!appURL) {
+ return NULL;
+ }
+ CFDictionaryRef infoDict = ::CFBundleCopyInfoDictionaryForURL(appURL);
+ ::CFRelease(appURL);
+ if (!infoDict) {
+ return NULL;
+ }
+ CFTypeRef cfObject = ::CFDictionaryGetValue(infoDict, CFSTR("CFBundleDocumentTypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFArrayRef docTypes = static_cast<CFArrayRef>(cfObject);
+ CFIndex docTypesCount = ::CFArrayGetCount(docTypes);
+ if (docTypesCount == 0) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFMutableArrayRef mimeTypes =
+ ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ for (CFIndex i = 0; i < docTypesCount; ++i) {
+ cfObject = ::CFArrayGetValueAtIndex(docTypes, i);
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFDictionaryGetTypeID())) {
+ continue;
+ }
+ CFDictionaryRef typeDict = static_cast<CFDictionaryRef>(cfObject);
+
+ // When this key is present (on OS X 10.5 and later), its contents
+ // take precedence over CFBundleTypeMIMETypes (and CFBundleTypeExtensions
+ // and CFBundleTypeOSTypes).
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("LSItemContentTypes"));
+ if (cfObject && (::CFGetTypeID(cfObject) == ::CFArrayGetTypeID())) {
+ continue;
+ }
+
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("CFBundleTypeMIMETypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ continue;
+ }
+ CFArrayRef mimeTypeHolder = static_cast<CFArrayRef>(cfObject);
+ CFArrayAppendArray(mimeTypes, mimeTypeHolder,
+ ::CFRangeMake(0, ::CFArrayGetCount(mimeTypeHolder)));
+ }
+
+ ::CFRelease(infoDict);
+ if (!::CFArrayGetCount(mimeTypes)) {
+ ::CFRelease(mimeTypes);
+ mimeTypes = NULL;
+ }
+ return mimeTypes;
+}
+
+// aMIMEType and aFileExt might not match, If they don't we set *aFound to
+// false and return a minimal nsIMIMEInfo structure.
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool * aFound)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ *aFound = false;
+
+ const nsCString& flatType = PromiseFlatCString(aMIMEType);
+ const nsCString& flatExt = PromiseFlatCString(aFileExt);
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("Mac: HelperAppService lookup for type '%s' ext '%s'\n",
+ flatType.get(), flatExt.get()));
+
+ // Create a Mac-specific MIME info so we can use Mac-specific members.
+ RefPtr<nsMIMEInfoMac> mimeInfoMac = new nsMIMEInfoMac(aMIMEType);
+
+ NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
+
+ OSStatus err;
+ bool haveAppForType = false;
+ bool haveAppForExt = false;
+ bool typeAppIsDefault = false;
+ bool extAppIsDefault = false;
+ FSRef typeAppFSRef;
+ FSRef extAppFSRef;
+
+ CFStringRef cfMIMEType = NULL;
+
+ if (!aMIMEType.IsEmpty()) {
+ CFURLRef appURL = NULL;
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very weird (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ cfMIMEType = ::CFStringCreateWithCString(NULL, flatType.get(),
+ kCFStringEncodingUTF8);
+ if (cfMIMEType) {
+ err = ::LSCopyApplicationForMIMEType(cfMIMEType, kLSRolesAll, &appURL);
+ if ((err == noErr) && appURL && ::CFURLGetFSRef(appURL, &typeAppFSRef)) {
+ haveAppForType = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSCopyApplicationForMIMEType found a default application\n"));
+ }
+ if (appURL) {
+ ::CFRelease(appURL);
+ }
+ }
+ }
+ if (!aFileExt.IsEmpty()) {
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef cfExt = ::CFStringCreateWithCString(NULL, flatExt.get(), kCFStringEncodingUTF8);
+ if (cfExt) {
+ err = ::LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, cfExt,
+ kLSRolesAll, &extAppFSRef, nullptr);
+ if (err == noErr) {
+ haveAppForExt = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSGetApplicationForInfo found a default application\n"));
+ }
+ ::CFRelease(cfExt);
+ }
+ }
+
+ if (haveAppForType && haveAppForExt) {
+ // Do aMIMEType and aFileExt match?
+ if (::FSCompareFSRefs((const FSRef *) &typeAppFSRef, (const FSRef *) &extAppFSRef) == noErr) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForType) {
+ // If aFileExt isn't empty, it doesn't match aMIMEType.
+ if (aFileExt.IsEmpty()) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForExt) {
+ // If aMIMEType isn't empty, it doesn't match aFileExt, which should mean
+ // that we haven't found a matching app. But make an exception for an app
+ // that also explicitly claims to handle aMIMEType, or which doesn't claim
+ // to handle any MIME types. This helps work around the following Apple
+ // design flaw:
+ //
+ // Launch Services is somewhat unreliable about registering Apple apps to
+ // handle MIME types. Probably this is because Apple has officially
+ // deprecated support for MIME types (in favor of UTIs). As a result,
+ // most of Apple's own apps don't explicitly claim to handle any MIME
+ // types (instead they claim to handle one or more UTIs). So Launch
+ // Services must contain logic to translate support for a given UTI into
+ // support for one or more MIME types, and it doesn't always do this
+ // correctly. For example DiskImageMounter isn't (by default) registered
+ // to handle the application/x-apple-diskimage MIME type. See bug 675356.
+ //
+ // Apple has also deprecated support for file extensions, and Apple apps
+ // also don't register to handle them. But for some reason Launch Services
+ // is (apparently) better about translating support for a given UTI into
+ // support for one or more file extensions. It's not at all clear why.
+ if (aMIMEType.IsEmpty()) {
+ extAppIsDefault = true;
+ *aFound = true;
+ } else {
+ CFArrayRef extAppMIMETypes = GetMIMETypesHandledByApp(&extAppFSRef);
+ if (extAppMIMETypes) {
+ if (cfMIMEType) {
+ if (::CFArrayContainsValue(extAppMIMETypes,
+ ::CFRangeMake(0, ::CFArrayGetCount(extAppMIMETypes)),
+ cfMIMEType)) {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ ::CFRelease(extAppMIMETypes);
+ } else {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ }
+
+ if (cfMIMEType) {
+ ::CFRelease(cfMIMEType);
+ }
+
+ if (aMIMEType.IsEmpty()) {
+ if (haveAppForExt) {
+ // If aMIMEType is empty and we've found a default app for aFileExt, try
+ // to get the MIME type from aFileExt. (It might also be worth doing
+ // this when aMIMEType isn't empty but haveAppForType is false -- but
+ // the doc for this method says that if we have a MIME type (in
+ // aMIMEType), we need to give it preference.)
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *extStr = [NSString stringWithCString:flatExt.get() encoding:NSASCIIStringEncoding];
+ NSString *typeStr = map ? [map MIMETypeForExtension:extStr] : NULL;
+ if (typeStr) {
+ nsAutoCString mimeType;
+ mimeType.Assign((char *)[typeStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->SetMIMEType(mimeType);
+ haveAppForType = true;
+ } else {
+ // Sometimes the OS won't give us a MIME type for an extension that's
+ // registered with Launch Services and has a default app: For example
+ // Real Player registers itself for the "ogg" extension and for the
+ // audio/x-ogg and application/x-ogg MIME types, but
+ // MIMETypeForExtension returns nil for the "ogg" extension even on
+ // systems where Real Player is installed. This is probably an Apple
+ // bug. But bad things happen if we return an nsIMIMEInfo structure
+ // with an empty MIME type and set *aFound to true. So in this
+ // case we need to set it to false here.
+ haveAppForExt = false;
+ extAppIsDefault = false;
+ *aFound = false;
+ }
+ } else {
+ // Otherwise set the MIME type to a reasonable fallback.
+ mimeInfoMac->SetMIMEType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ }
+ }
+
+ if (typeAppIsDefault || extAppIsDefault) {
+ if (haveAppForExt)
+ mimeInfoMac->AppendExtension(aFileExt);
+
+ nsCOMPtr<nsILocalFileMac> app(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (!app) {
+ [localPool release];
+ return nullptr;
+ }
+
+ CFStringRef cfAppName = NULL;
+ if (typeAppIsDefault) {
+ app->InitWithFSRef(&typeAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &typeAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ } else {
+ app->InitWithFSRef(&extAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &extAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ }
+ if (cfAppName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex appNameLength = ::CFStringGetLength(cfAppName);
+ buffer.SetLength(appNameLength);
+ ::CFStringGetCharacters(cfAppName, CFRangeMake(0, appNameLength),
+ buffer.Elements());
+ nsAutoString appName;
+ appName.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), appNameLength);
+ mimeInfoMac->SetDefaultDescription(appName);
+ ::CFRelease(cfAppName);
+ }
+
+ mimeInfoMac->SetDefaultApplication(app);
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ } else {
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ nsAutoCString mimeType;
+ mimeInfoMac->GetMIMEType(mimeType);
+ if (*aFound && !mimeType.IsEmpty()) {
+ // If we have a MIME type, make sure its preferred extension is included
+ // in our list.
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *typeStr = [NSString stringWithCString:mimeType.get() encoding:NSASCIIStringEncoding];
+ NSString *extStr = map ? [map preferredExtensionForMIMEType:typeStr] : NULL;
+ if (extStr) {
+ nsAutoCString preferredExt;
+ preferredExt.Assign((char *)[extStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->AppendExtension(preferredExt);
+ }
+
+ CFStringRef cfType = ::CFStringCreateWithCString(NULL, mimeType.get(), kCFStringEncodingUTF8);
+ if (cfType) {
+ CFStringRef cfTypeDesc = NULL;
+ if (::LSCopyKindStringForMIMEType(cfType, &cfTypeDesc) == noErr) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex typeDescLength = ::CFStringGetLength(cfTypeDesc);
+ buffer.SetLength(typeDescLength);
+ ::CFStringGetCharacters(cfTypeDesc, CFRangeMake(0, typeDescLength),
+ buffer.Elements());
+ nsAutoString typeDesc;
+ typeDesc.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), typeDescLength);
+ mimeInfoMac->SetDescription(typeDesc);
+ }
+ if (cfTypeDesc) {
+ ::CFRelease(cfTypeDesc);
+ }
+ ::CFRelease(cfType);
+ }
+ }
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("OS gave us: type '%s' found '%i'\n", mimeType.get(), *aFound));
+
+ [localPool release];
+ return mimeInfoMac.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoMac *handlerInfo =
+ new nsMIMEInfoMac(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ rv = GetApplicationDescription(aScheme, desc);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "GetApplicationDescription failed");
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
+
diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
new file mode 100644
index 000000000..6a3ca08af
--- /dev/null
+++ b/uriloader/exthandler/moz.build
@@ -0,0 +1,139 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['tests']
+
+XPIDL_SOURCES += [
+ 'nsCExternalHandlerService.idl',
+ 'nsIContentDispatchChooser.idl',
+ 'nsIExternalHelperAppService.idl',
+ 'nsIExternalProtocolService.idl',
+ 'nsIExternalSharingAppService.idl',
+ 'nsIExternalURLHandlerService.idl',
+ 'nsIHandlerService.idl',
+ 'nsIHelperAppLauncherDialog.idl',
+]
+
+XPIDL_MODULE = 'exthandler'
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ osdir = 'win'
+ LOCAL_INCLUDES += ['win']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ osdir = 'mac'
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk', 'uikit'):
+ osdir = CONFIG['MOZ_WIDGET_TOOLKIT']
+else:
+ osdir = 'unix'
+
+EXPORTS += [
+ osdir + '/nsOSHelperAppService.h'
+]
+
+EXPORTS += [
+ 'ContentHandlerService.h',
+ 'nsExternalHelperAppService.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ EXPORTS += [ '%s/%s' % (osdir, f) for f in [
+ 'nsExternalSharingAppService.h',
+ 'nsExternalURLHandlerService.h',
+ ]]
+
+EXPORTS.mozilla.dom += [
+ 'ExternalHelperAppChild.h',
+ 'ExternalHelperAppParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ContentHandlerService.cpp',
+ 'ExternalHelperAppChild.cpp',
+ 'ExternalHelperAppParent.cpp',
+ 'HandlerServiceParent.cpp',
+ 'nsExternalHelperAppService.cpp',
+ 'nsExternalProtocolHandler.cpp',
+ 'nsLocalHandlerApp.cpp',
+ 'nsMIMEInfoImpl.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'mac/nsLocalHandlerAppMac.mm',
+ 'mac/nsMIMEInfoMac.mm',
+ 'mac/nsOSHelperAppService.mm',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
+ UNIFIED_SOURCES += [
+ 'uikit/nsLocalHandlerAppUIKit.mm',
+ 'uikit/nsMIMEInfoUIKit.mm',
+ 'uikit/nsOSHelperAppService.mm',
+ ]
+else:
+ # These files can't be built in unified mode because they redefine LOG.
+ SOURCES += [
+ osdir + '/nsOSHelperAppService.cpp',
+ ]
+ if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ UNIFIED_SOURCES += [
+ 'unix/nsGNOMERegistry.cpp',
+ 'unix/nsMIMEInfoUnix.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ UNIFIED_SOURCES += [
+ 'android/nsAndroidHandlerApp.cpp',
+ 'android/nsExternalSharingAppService.cpp',
+ 'android/nsExternalURLHandlerService.cpp',
+ 'android/nsMIMEInfoAndroid.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ UNIFIED_SOURCES += [
+ 'win/nsMIMEInfoWin.cpp',
+ ]
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ UNIFIED_SOURCES += [
+ 'nsDBusHandlerApp.cpp',
+ ]
+
+if CONFIG['MOZ_ENABLE_CONTENTACTION']:
+ UNIFIED_SOURCES += [
+ 'nsContentHandlerApp.cpp',
+ ]
+
+EXTRA_COMPONENTS += [
+ 'nsHandlerService.js',
+ 'nsHandlerService.manifest',
+ 'nsWebHandlerApp.js',
+ 'nsWebHandlerApp.manifest',
+]
+
+IPDL_SOURCES += [
+ 'PExternalHelperApp.ipdl',
+ 'PHandlerService.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/ipc',
+ '/netwerk/base',
+ '/netwerk/protocol/http',
+]
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
diff --git a/uriloader/exthandler/nsCExternalHandlerService.idl b/uriloader/exthandler/nsCExternalHandlerService.idl
new file mode 100644
index 000000000..6aa25a01d
--- /dev/null
+++ b/uriloader/exthandler/nsCExternalHandlerService.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: IDL; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIExternalHelperAppService.idl"
+
+/*
+nsCExternalHelperApp implements:
+-------------------------
+nsIExternalHelperAppService
+*/
+
+%{ C++
+
+/* A7F800E0-4306-11d4-98D0-001083010E9B */
+#define NS_EXTERNALHELPERAPPSERVICE_CID \
+ { 0xa7f800e0, 0x4306, 0x11d4, { 0x98, 0xd0, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+#define NS_EXTERNALHELPERAPPSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/external-helper-app-service;1"
+
+#define NS_HANDLERSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/handler-service;1"
+
+#define NS_EXTERNALPROTOCOLSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/external-protocol-service;1"
+
+#define NS_MIMESERVICE_CONTRACTID \
+"@mozilla.org/mime;1"
+
+#define NS_EXTERNALPROTOCOLHANDLER_CID \
+{ 0xbd6390c8, 0xfbea, 0x11d4, {0x98, 0xf6, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+/* 9fa83ce7-d0ab-4ed3-938e-afafee435670 */
+#define NS_BLOCKEDEXTERNALPROTOCOLHANDLER_CID \
+{ 0x9fa83ce7, 0xd0ab, 0x4ed3, {0x93, 0x8e, 0xaf, 0xaf, 0xee, 0x43, 0x56, 0x70 } }
+
+/* bc0017e3-2438-47be-a567-41db58f17627 */
+#define NS_LOCALHANDLERAPP_CID \
+{ 0xbc0017e3, 0x2438, 0x47be, {0xa5, 0x67, 0x41, 0xdb, 0x58, 0xf1, 0x76, 0x27 } }
+
+/*6c3c274b-4cbf-4bb5-a635-05ad2cbb6535*/
+#define NS_DBUSHANDLERAPP_CID \
+{ 0x6c3c274b, 0x4cbf, 0x4bb5, {0xa6, 0x35, 0x05, 0xad, 0x2c, 0xbb, 0x65, 0x35 } }
+
+#define NS_DBUSHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/dbus-handler-app;1"
+
+#define NS_LOCALHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/local-handler-app;1"
+
+#define NS_WEBHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/web-handler-app;1"
+
+%}
+
diff --git a/uriloader/exthandler/nsContentHandlerApp.cpp b/uriloader/exthandler/nsContentHandlerApp.cpp
new file mode 100644
index 000000000..ce3f7b90f
--- /dev/null
+++ b/uriloader/exthandler/nsContentHandlerApp.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define NS_CONTENTHANDLER_CID \
+{ 0x43ec2c82, 0xb9db, 0x4835, {0x80, 0x3f, 0x64, 0xc9, 0x72, 0x5a, 0x70, 0x28 } }
+
+NS_IMPL_CLASSINFO(nsContentHandlerApp, nullptr, 0, NS_CONTENTHANDLER_CID)
+NS_IMPL_ISUPPORTS_CI(nsContentHandlerApp, nsIHandlerApp)
+
+nsContentHandlerApp::nsContentHandlerApp(nsString aName, nsCString aType,
+ ContentAction::Action& aAction) :
+ mName(aName),
+ mType(aType),
+ mAction(aAction)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerInfo
+
+NS_IMETHODIMP nsContentHandlerApp::GetName(nsAString& aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::SetName(const nsAString& aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription)
+{
+ aDetailedDescription.Assign(mDetailedDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::SetDetailedDescription(const nsAString& aDetailedDescription)
+{
+ mDetailedDescription.Assign(aDetailedDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ const char* url = spec.get();
+
+ QList<ContentAction::Action> actions =
+ ContentAction::Action::actionsForFile(QUrl(url), QString(mType.get()));
+ for (int i = 0; i < actions.size(); ++i) {
+ if (actions[i].name() == QString((QChar*)mName.get(), mName.Length())) {
+ actions[i].trigger();
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
diff --git a/uriloader/exthandler/nsContentHandlerApp.h b/uriloader/exthandler/nsContentHandlerApp.h
new file mode 100644
index 000000000..8ace9a2cd
--- /dev/null
+++ b/uriloader/exthandler/nsContentHandlerApp.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsContentHandlerAppImpl_h__
+#define __nsContentHandlerAppImpl_h__
+
+#include <contentaction/contentaction.h>
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+
+class nsContentHandlerApp : public nsIHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+
+ nsContentHandlerApp(nsString aName, nsCString aType, ContentAction::Action& aAction);
+ virtual ~nsContentHandlerApp() { }
+
+protected:
+ nsString mName;
+ nsCString mType;
+ nsString mDetailedDescription;
+
+ ContentAction::Action mAction;
+};
+#endif
diff --git a/uriloader/exthandler/nsDBusHandlerApp.cpp b/uriloader/exthandler/nsDBusHandlerApp.cpp
new file mode 100644
index 000000000..bed2ecd98
--- /dev/null
+++ b/uriloader/exthandler/nsDBusHandlerApp.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dbus/dbus.h>
+#include "mozilla/ipc/DBusConnectionDelete.h"
+#include "mozilla/ipc/DBusMessageRefPtr.h"
+#include "nsDBusHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsCOMPtr.h"
+#include "nsCExternalHandlerService.h"
+
+// XXX why does nsMIMEInfoImpl have a threadsafe nsISupports? do we need one
+// here too?
+NS_IMPL_CLASSINFO(nsDBusHandlerApp, nullptr, 0, NS_DBUSHANDLERAPP_CID)
+NS_IMPL_ISUPPORTS_CI(nsDBusHandlerApp, nsIDBusHandlerApp, nsIHandlerApp)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerApp
+
+NS_IMETHODIMP nsDBusHandlerApp::GetName(nsAString& aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDetailedDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetDetailedDescription(nsAString& aDescription)
+{
+ aDescription.Assign(mDetailedDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBusHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aHandlerApp);
+
+ // If the handler app isn't a dbus handler app, then it's not the same app.
+ nsCOMPtr<nsIDBusHandlerApp> dbusHandlerApp = do_QueryInterface(aHandlerApp);
+ if (!dbusHandlerApp) {
+ *_retval = false;
+ return NS_OK;
+ }
+ nsAutoCString service;
+ nsAutoCString method;
+
+ nsresult rv = dbusHandlerApp->GetService(service);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+ rv = dbusHandlerApp->GetMethod(method);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = service.Equals(mService) && method.Equals(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBusHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ const char* uri = spec.get();
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ mozilla::UniquePtr<DBusConnection, mozilla::DBusConnectionDelete>
+ connection(dbus_bus_get_private(DBUS_BUS_SESSION, &err));
+
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+ if (nullptr == connection) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_connection_set_exit_on_disconnect(connection.get(),false);
+
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(mService.get(),
+ mObjpath.get(),
+ mInterface.get(),
+ mMethod.get()));
+
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_set_no_reply(msg, true);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &uri);
+
+ if (dbus_connection_send(connection.get(), msg, nullptr)) {
+ dbus_connection_flush(connection.get());
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDBusHandlerApp
+
+NS_IMETHODIMP nsDBusHandlerApp::GetService(nsACString & aService)
+{
+ aService.Assign(mService);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetService(const nsACString & aService)
+{
+ mService.Assign(aService);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetMethod(nsACString & aMethod)
+{
+ aMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetMethod(const nsACString & aMethod)
+{
+ mMethod.Assign(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetDBusInterface(nsACString & aInterface)
+{
+ aInterface.Assign(mInterface);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetDBusInterface(const nsACString & aInterface)
+{
+ mInterface.Assign(aInterface);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetObjectPath(nsACString & aObjpath)
+{
+ aObjpath.Assign(mObjpath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetObjectPath(const nsACString & aObjpath)
+{
+ mObjpath.Assign(aObjpath);
+ return NS_OK;
+}
+
+
diff --git a/uriloader/exthandler/nsDBusHandlerApp.h b/uriloader/exthandler/nsDBusHandlerApp.h
new file mode 100644
index 000000000..a2835d98f
--- /dev/null
+++ b/uriloader/exthandler/nsDBusHandlerApp.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsDBusHandlerAppImpl_h__
+#define __nsDBusHandlerAppImpl_h__
+
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+
+class nsDBusHandlerApp : public nsIDBusHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSIDBUSHANDLERAPP
+
+ nsDBusHandlerApp() { }
+
+protected:
+ virtual ~nsDBusHandlerApp() { }
+
+ nsString mName;
+ nsString mDetailedDescription;
+ nsCString mService;
+ nsCString mMethod;
+ nsCString mInterface;
+ nsCString mObjpath;
+
+};
+#endif
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
new file mode 100644
index 000000000..51a7ee0f6
--- /dev/null
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -0,0 +1,2926 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Base64.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsXULAppAPI.h"
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIChannel.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICategoryManager.h"
+#include "nsDependentSubstring.h"
+#include "nsXPIDLString.h"
+#include "nsUnicharUtils.h"
+#include "nsIStringEnumerator.h"
+#include "nsMemory.h"
+#include "nsIStreamListener.h"
+#include "nsIMIMEService.h"
+#include "nsILoadGroup.h"
+#include "nsIWebProgressListener.h"
+#include "nsITransfer.h"
+#include "nsReadableUtils.h"
+#include "nsIRequest.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsIMutableArray.h"
+
+// used to access our datastore of user-configured helper applications
+#include "nsIHandlerService.h"
+#include "nsIMIMEInfo.h"
+#include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
+#include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
+#include "nsIHelperAppLauncherDialog.h"
+#include "nsIContentDispatchChooser.h"
+#include "nsNetUtil.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+
+#include "nsMimeTypes.h"
+// used for header disposition information.
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIEncodedChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIObserverService.h" // so we can be a profile change observer
+#include "nsIPropertyBag2.h" // for the 64-bit content length
+
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+#endif
+
+#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
+#include "nsPluginHost.h"
+#include "nsEscape.h"
+
+#include "nsIStringBundle.h" // XXX needed to localize error msgs
+#include "nsIPrompt.h"
+
+#include "nsITextToSubURI.h" // to unescape the filename
+#include "nsIMIMEHeaderParam.h"
+
+#include "nsIWindowWatcher.h"
+
+#include "nsIDownloadHistory.h" // to mark downloads as visited
+#include "nsDocShellCID.h"
+
+#include "nsCRT.h"
+#include "nsLocalHandlerApp.h"
+
+#include "nsIRandomGenerator.h"
+
+#include "ContentChild.h"
+#include "nsXULAppAPI.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocShellTreeItem.h"
+#include "ExternalHelperAppChild.h"
+
+#ifdef XP_WIN
+#include "nsWindowsHelpers.h"
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
+
+#include "mozilla/Preferences.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+// Download Folder location constants
+#define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
+#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
+enum {
+ NS_FOLDER_VALUE_DESKTOP = 0
+, NS_FOLDER_VALUE_DOWNLOADS = 1
+, NS_FOLDER_VALUE_CUSTOM = 2
+};
+
+LazyLogModule nsExternalHelperAppService::mLog("HelperAppService");
+
+// Using level 3 here because the OSHelperAppServices use a log level
+// of LogLevel::Debug (4), and we want less detailed output here
+// Using 3 instead of LogLevel::Warning because we don't output warnings
+#undef LOG
+#define LOG(args) MOZ_LOG(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info)
+
+static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
+ "browser.helperApps.neverAsk.saveToDisk";
+static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
+ "browser.helperApps.neverAsk.openFile";
+
+// Helper functions for Content-Disposition headers
+
+/**
+ * Given a URI fragment, unescape it
+ * @param aFragment The string to unescape
+ * @param aURI The URI from which this fragment is taken. Only its character set
+ * will be used.
+ * @param aResult [out] Unescaped string.
+ */
+static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
+ nsAString& aResult)
+{
+ // First, we need a charset
+ nsAutoCString originCharset;
+ nsresult rv = aURI->GetOriginCharset(originCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, we need the unescaper
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
+}
+
+/**
+ * UTF-8 version of UnescapeFragment.
+ * @param aFragment The string to unescape
+ * @param aURI The URI from which this fragment is taken. Only its character set
+ * will be used.
+ * @param aResult [out] Unescaped string, UTF-8 encoded.
+ * @note It is safe to pass the same string for aFragment and aResult.
+ * @note When this function fails, aResult will not be modified.
+ */
+static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
+ nsACString& aResult)
+{
+ nsAutoString result;
+ nsresult rv = UnescapeFragment(aFragment, aURI, result);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF16toUTF8(result, aResult);
+ return rv;
+}
+
+/**
+ * Given a channel, returns the filename and extension the channel has.
+ * This uses the URL and other sources (nsIMultiPartChannel).
+ * Also gives back whether the channel requested external handling (i.e.
+ * whether Content-Disposition: attachment was sent)
+ * @param aChannel The channel to extract the filename/extension from
+ * @param aFileName [out] Reference to the string where the filename should be
+ * stored. Empty if it could not be retrieved.
+ * WARNING - this filename may contain characters which the OS does not
+ * allow as part of filenames!
+ * @param aExtension [out] Reference to the string where the extension should
+ * be stored. Empty if it could not be retrieved. Stored in UTF-8.
+ * @param aAllowURLExtension (optional) Get the extension from the URL if no
+ * Content-Disposition header is present. Default is true.
+ * @retval true The server sent Content-Disposition:attachment or equivalent
+ * @retval false Content-Disposition: inline or no content-disposition header
+ * was sent.
+ */
+static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
+ nsString& aFileName,
+ nsCString& aExtension,
+ bool aAllowURLExtension = true)
+{
+ aExtension.Truncate();
+ /*
+ * If the channel is an http or part of a multipart channel and we
+ * have a content disposition header set, then use the file name
+ * suggested there as the preferred file name to SUGGEST to the
+ * user. we shouldn't actually use that without their
+ * permission... otherwise just use our temp file
+ */
+ bool handleExternally = false;
+ uint32_t disp;
+ nsresult rv = aChannel->GetContentDisposition(&disp);
+ if (NS_SUCCEEDED(rv))
+ {
+ aChannel->GetContentDispositionFilename(aFileName);
+ if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
+ handleExternally = true;
+ }
+
+ // If the disposition header didn't work, try the filename from nsIURL
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (url && aFileName.IsEmpty())
+ {
+ if (aAllowURLExtension) {
+ url->GetFileExtension(aExtension);
+ UnescapeFragment(aExtension, url, aExtension);
+
+ // Windows ignores terminating dots. So we have to as well, so
+ // that our security checks do "the right thing"
+ // In case the aExtension consisted only of the dot, the code below will
+ // extract an aExtension from the filename
+ aExtension.Trim(".", false);
+ }
+
+ // try to extract the file name from the url and use that as a first pass as the
+ // leaf name of our temp file...
+ nsAutoCString leafName;
+ url->GetFileName(leafName);
+ if (!leafName.IsEmpty())
+ {
+ rv = UnescapeFragment(leafName, url, aFileName);
+ if (NS_FAILED(rv))
+ {
+ CopyUTF8toUTF16(leafName, aFileName); // use escaped name
+ }
+ }
+ }
+
+ // Extract Extension, if we have a filename; otherwise,
+ // truncate the string
+ if (aExtension.IsEmpty()) {
+ if (!aFileName.IsEmpty())
+ {
+ // Windows ignores terminating dots. So we have to as well, so
+ // that our security checks do "the right thing"
+ aFileName.Trim(".", false);
+
+ // XXX RFindCharInReadable!!
+ nsAutoString fileNameStr(aFileName);
+ int32_t idx = fileNameStr.RFindChar(char16_t('.'));
+ if (idx != kNotFound)
+ CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
+ }
+ }
+
+
+ return handleExternally;
+}
+
+/**
+ * Obtains the directory to use. This tends to vary per platform, and
+ * needs to be consistent throughout our codepaths. For platforms where
+ * helper apps use the downloads directory, this should be kept in
+ * sync with nsDownloadManager.cpp
+ *
+ * Optionally skip availability of the directory and storage.
+ */
+static nsresult GetDownloadDirectory(nsIFile **_directory,
+ bool aSkipChecks = false)
+{
+ nsCOMPtr<nsIFile> dir;
+#ifdef XP_MACOSX
+ // On OS X, we first try to get the users download location, if it's set.
+ switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
+ case NS_FOLDER_VALUE_DESKTOP:
+ (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
+ break;
+ case NS_FOLDER_VALUE_CUSTOM:
+ {
+ Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(dir));
+ if (!dir) break;
+
+ // If we're not checking for availability we're done.
+ if (aSkipChecks) {
+ dir.forget(_directory);
+ return NS_OK;
+ }
+
+ // We have the directory, and now we need to make sure it exists
+ bool dirExists = false;
+ (void) dir->Exists(&dirExists);
+ if (dirExists) break;
+
+ nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv)) {
+ dir = nullptr;
+ break;
+ }
+ }
+ break;
+ case NS_FOLDER_VALUE_DOWNLOADS:
+ // This is just the OS default location, so fall out
+ break;
+ }
+
+ if (!dir) {
+ // If not, we default to the OS X default download location.
+ nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
+ getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#elif defined(ANDROID)
+ // We ask Java for the temporary download directory. The directory will be
+ // different depending on whether we have the permission to write to the
+ // public download directory or not.
+ // In the case where we do not have the permission we will start the
+ // download to the app cache directory and later move it to the final
+ // destination after prompting for the permission.
+ jni::String::LocalRef downloadDir;
+ if (jni::IsFennec()) {
+ downloadDir = java::DownloadsIntegration::GetTemporaryDownloadDirectory();
+ }
+
+ nsresult rv;
+ if (downloadDir) {
+ nsCOMPtr<nsIFile> ldir;
+ rv = NS_NewNativeLocalFile(downloadDir->ToCString(),
+ true, getter_AddRefs(ldir));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ dir = do_QueryInterface(ldir);
+
+ // If we're not checking for availability we're done.
+ if (aSkipChecks) {
+ dir.forget(_directory);
+ return NS_OK;
+ }
+ }
+ else {
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // On all other platforms, we default to the systems temporary directory.
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if defined(XP_UNIX)
+ // Ensuring that only the current user can read the file names we end up
+ // creating. Note that Creating directories with specified permission only
+ // supported on Unix platform right now. That's why above if exists.
+
+ uint32_t permissions;
+ rv = dir->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions != PR_IRWXU) {
+ const char* userName = PR_GetEnv("USERNAME");
+ if (!userName || !*userName) {
+ userName = PR_GetEnv("USER");
+ }
+ if (!userName || !*userName) {
+ userName = PR_GetEnv("LOGNAME");
+ }
+ if (!userName || !*userName) {
+ userName = "mozillaUser";
+ }
+
+ nsAutoString userDir;
+ userDir.AssignLiteral("mozilla_");
+ userDir.AppendASCII(userName);
+ userDir.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
+
+ int counter = 0;
+ bool pathExists;
+ nsCOMPtr<nsIFile> finalPath;
+
+ while (true) {
+ nsAutoString countedUserDir(userDir);
+ countedUserDir.AppendInt(counter, 10);
+ dir->Clone(getter_AddRefs(finalPath));
+ finalPath->Append(countedUserDir);
+
+ rv = finalPath->Exists(&pathExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pathExists) {
+ // If this path has the right permissions, use it.
+ rv = finalPath->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensuring the path is writable by the current user.
+ bool isWritable;
+ rv = finalPath->IsWritable(&isWritable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions == PR_IRWXU && isWritable) {
+ dir = finalPath;
+ break;
+ }
+ }
+
+ rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
+ if (NS_SUCCEEDED(rv)) {
+ dir = finalPath;
+ break;
+ }
+ else if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ // Unexpected error.
+ return rv;
+ }
+
+ counter++;
+ }
+ }
+
+#endif
+#endif
+
+ NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
+ dir.forget(_directory);
+ return NS_OK;
+}
+
+/**
+ * Structure for storing extension->type mappings.
+ * @see defaultMimeEntries
+ */
+struct nsDefaultMimeTypeEntry {
+ const char* mMimeType;
+ const char* mFileExtension;
+};
+
+/**
+ * Default extension->mimetype mappings. These are not overridable.
+ * If you add types here, make sure they are lowercase, or you'll regret it.
+ */
+static const nsDefaultMimeTypeEntry defaultMimeEntries[] =
+{
+ // The following are those extensions that we're asked about during startup,
+ // sorted by order used
+ { IMAGE_GIF, "gif" },
+ { TEXT_XML, "xml" },
+ { APPLICATION_RDF, "rdf" },
+ { TEXT_XUL, "xul" },
+ { IMAGE_PNG, "png" },
+ // -- end extensions used during startup
+ { TEXT_CSS, "css" },
+ { IMAGE_JPEG, "jpeg" },
+ { IMAGE_JPEG, "jpg" },
+ { IMAGE_SVG_XML, "svg" },
+ { TEXT_HTML, "html" },
+ { TEXT_HTML, "htm" },
+ { APPLICATION_XPINSTALL, "xpi" },
+ { "application/xhtml+xml", "xhtml" },
+ { "application/xhtml+xml", "xht" },
+ { TEXT_PLAIN, "txt" },
+ { VIDEO_OGG, "ogv" },
+ { VIDEO_OGG, "ogg" },
+ { APPLICATION_OGG, "ogg" },
+ { AUDIO_OGG, "oga" },
+ { AUDIO_OGG, "opus" },
+ { APPLICATION_PDF, "pdf" },
+ { VIDEO_WEBM, "webm" },
+ { AUDIO_WEBM, "webm" },
+#if defined(MOZ_WMF)
+ { VIDEO_MP4, "mp4" },
+ { AUDIO_MP4, "m4a" },
+ { AUDIO_MP3, "mp3" },
+#endif
+#ifdef MOZ_RAW
+ { VIDEO_RAW, "yuv" }
+#endif
+};
+
+/**
+ * This is a small private struct used to help us initialize some
+ * default mime types.
+ */
+struct nsExtraMimeTypeEntry {
+ const char* mMimeType;
+ const char* mFileExtensions;
+ const char* mDescription;
+};
+
+#ifdef XP_MACOSX
+#define MAC_TYPE(x) x
+#else
+#define MAC_TYPE(x) 0
+#endif
+
+/**
+ * This table lists all of the 'extra' content types that we can deduce from particular
+ * file extensions. These entries also ensure that we provide a good descriptive name
+ * when we encounter files with these content types and/or extensions. These can be
+ * overridden by user helper app prefs.
+ * If you add types here, make sure they are lowercase, or you'll regret it.
+ */
+static const nsExtraMimeTypeEntry extraMimeEntries[] =
+{
+#if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
+ { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
+#else
+ { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
+#endif
+ { APPLICATION_GZIP2, "gz", "gzip" },
+ { "application/x-arj", "arj", "ARJ file" },
+ { "application/rtf", "rtf", "Rich Text Format File" },
+ { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
+ { APPLICATION_PDF, "pdf", "Portable Document Format" },
+ { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
+ { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
+ { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
+#ifdef MOZ_WIDGET_ANDROID
+ { "application/vnd.android.package-archive", "apk", "Android Package" },
+#endif
+ { IMAGE_ART, "art", "ART Image" },
+ { IMAGE_BMP, "bmp", "BMP Image" },
+ { IMAGE_GIF, "gif", "GIF Image" },
+ { IMAGE_ICO, "ico,cur", "ICO Image" },
+ { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
+ { IMAGE_PNG, "png", "PNG Image" },
+ { IMAGE_APNG, "apng", "APNG Image" },
+ { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
+ { IMAGE_XBM, "xbm", "XBM Image" },
+ { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
+ { MESSAGE_RFC822, "eml", "RFC-822 data" },
+ { TEXT_PLAIN, "txt,text", "Text File" },
+ { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
+ { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
+ { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
+ { APPLICATION_RDF, "rdf", "Resource Description Framework" },
+ { TEXT_XUL, "xul", "XML-Based User Interface Language" },
+ { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
+ { TEXT_CSS, "css", "Style Sheet" },
+ { TEXT_VCARD, "vcf,vcard", "Contact Information" },
+ { VIDEO_OGG, "ogv", "Ogg Video" },
+ { VIDEO_OGG, "ogg", "Ogg Video" },
+ { APPLICATION_OGG, "ogg", "Ogg Video"},
+ { AUDIO_OGG, "oga", "Ogg Audio" },
+ { AUDIO_OGG, "opus", "Opus Audio" },
+#ifdef MOZ_WIDGET_GONK
+ { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
+ { AUDIO_FLAC, "flac", "FLAC Audio" },
+ { VIDEO_AVI, "avi", "Audio Video Interleave" },
+ { VIDEO_AVI, "divx", "Audio Video Interleave" },
+ { VIDEO_MPEG_TS, "ts", "MPEG Transport Stream" },
+ { VIDEO_MPEG_TS, "m2ts", "MPEG-2 Transport Stream" },
+ { VIDEO_MATROSKA, "mkv", "MATROSKA VIDEO" },
+ { AUDIO_MATROSKA, "mka", "MATROSKA AUDIO" },
+#endif
+ { VIDEO_WEBM, "webm", "Web Media Video" },
+ { AUDIO_WEBM, "webm", "Web Media Audio" },
+ { AUDIO_MP3, "mp3", "MPEG Audio" },
+ { VIDEO_MP4, "mp4", "MPEG-4 Video" },
+ { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
+ { VIDEO_RAW, "yuv", "Raw YUV Video" },
+ { AUDIO_WAV, "wav", "Waveform Audio" },
+ { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
+ { VIDEO_3GPP2,"3g2", "3GPP2 Video" },
+#ifdef MOZ_WIDGET_GONK
+ // The AUDIO_3GPP has to come after the VIDEO_3GPP entry because the Gallery
+ // app on Firefox OS depends on the "3gp" extension mapping to the
+ // "video/3gpp" MIME type.
+ { AUDIO_3GPP, "3gpp,3gp", "3GPP Audio" },
+ { AUDIO_3GPP2, "3g2", "3GPP2 Audio" },
+#endif
+ { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
+};
+
+#undef MAC_TYPE
+
+/**
+ * File extensions for which decoding should be disabled.
+ * NOTE: These MUST be lower-case and ASCII.
+ */
+static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
+ { APPLICATION_GZIP, "gz" },
+ { APPLICATION_GZIP, "tgz" },
+ { APPLICATION_ZIP, "zip" },
+ { APPLICATION_COMPRESS, "z" },
+ { APPLICATION_GZIP, "svgz" }
+};
+
+NS_IMPL_ISUPPORTS(
+ nsExternalHelperAppService,
+ nsIExternalHelperAppService,
+ nsPIExternalAppLauncher,
+ nsIExternalProtocolService,
+ nsIMIMEService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsExternalHelperAppService::nsExternalHelperAppService()
+{
+}
+nsresult nsExternalHelperAppService::Init()
+{
+ // Add an observer for profile change
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = obs->AddObserver(this, "profile-before-change", true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return obs->AddObserver(this, "last-pb-context-exited", true);
+}
+
+nsExternalHelperAppService::~nsExternalHelperAppService()
+{
+}
+
+
+nsresult
+nsExternalHelperAppService::DoContentContentProcessHelper(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(aContentContext);
+ NS_ENSURE_STATE(window);
+
+ // We need to get a hold of a ContentChild so that we can begin forwarding
+ // this data to the parent. In the HTTP case, this is unfortunate, since
+ // we're actually passing data from parent->child->parent wastefully, but
+ // the Right Fix will eventually be to short-circuit those channels on the
+ // parent side based on some sort of subscription concept.
+ using mozilla::dom::ContentChild;
+ using mozilla::dom::ExternalHelperAppChild;
+ ContentChild *child = ContentChild::GetSingleton();
+ if (!child) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString disp;
+ nsCOMPtr<nsIURI> uri;
+ int64_t contentLength = -1;
+ bool wasFileChannel = false;
+ uint32_t contentDisposition = -1;
+ nsAutoString fileName;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ channel->GetContentLength(&contentLength);
+ channel->GetContentDisposition(&contentDisposition);
+ channel->GetContentDispositionFilename(fileName);
+ channel->GetContentDispositionHeader(disp);
+
+ nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aRequest));
+ wasFileChannel = fileChan != nullptr;
+ }
+
+
+ nsCOMPtr<nsIURI> referrer;
+ NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
+
+ OptionalURIParams uriParams, referrerParams;
+ SerializeURI(uri, uriParams);
+ SerializeURI(referrer, referrerParams);
+
+ // Now we build a protocol for forwarding our data to the parent. The
+ // protocol will act as a listener on the child-side and create a "real"
+ // helperAppService listener on the parent-side, via another call to
+ // DoContent.
+ mozilla::dom::PExternalHelperAppChild *pc =
+ child->SendPExternalHelperAppConstructor(uriParams,
+ nsCString(aMimeContentType),
+ disp, contentDisposition,
+ fileName, aForceSave,
+ contentLength, wasFileChannel,
+ referrerParams,
+ mozilla::dom::TabChild::GetFrom(window));
+ ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
+
+ NS_ADDREF(*aStreamListener = childListener);
+
+ uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
+
+ RefPtr<nsExternalAppHandler> handler =
+ new nsExternalAppHandler(nullptr, EmptyCString(), aContentContext, aWindowContext, this,
+ fileName, reason, aForceSave);
+ if (!handler) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ childListener->SetHandler(handler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener)
+{
+ if (XRE_IsContentProcess()) {
+ return DoContentContentProcessHelper(aMimeContentType, aRequest, aContentContext,
+ aForceSave, aWindowContext, aStreamListener);
+ }
+
+ nsAutoString fileName;
+ nsAutoCString fileExtension;
+ uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
+ uint32_t contentDisposition = -1;
+
+ // Get the file extension and name that we will need later
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> uri;
+ int64_t contentLength = -1;
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ channel->GetContentLength(&contentLength);
+ channel->GetContentDisposition(&contentDisposition);
+ channel->GetContentDispositionFilename(fileName);
+
+ // Check if we have a POST request, in which case we don't want to use
+ // the url's extension
+ bool allowURLExt = true;
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
+ if (httpChan) {
+ nsAutoCString requestMethod;
+ httpChan->GetRequestMethod(requestMethod);
+ allowURLExt = !requestMethod.EqualsLiteral("POST");
+ }
+
+ // Check if we had a query string - we don't want to check the URL
+ // extension if a query is present in the URI
+ // If we already know we don't want to check the URL extension, don't
+ // bother checking the query
+ if (uri && allowURLExt) {
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+
+ if (url) {
+ nsAutoCString query;
+
+ // We only care about the query for HTTP and HTTPS URLs
+ nsresult rv;
+ bool isHTTP, isHTTPS;
+ rv = uri->SchemeIs("http", &isHTTP);
+ if (NS_FAILED(rv)) {
+ isHTTP = false;
+ }
+ rv = uri->SchemeIs("https", &isHTTPS);
+ if (NS_FAILED(rv)) {
+ isHTTPS = false;
+ }
+ if (isHTTP || isHTTPS) {
+ url->GetQuery(query);
+ }
+
+ // Only get the extension if the query is empty; if it isn't, then the
+ // extension likely belongs to a cgi script and isn't helpful
+ allowURLExt = query.IsEmpty();
+ }
+ }
+ // Extract name & extension
+ bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
+ fileExtension,
+ allowURLExt);
+ LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
+ fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
+ isAttachment));
+ if (isAttachment) {
+ reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
+ }
+ }
+
+ LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
+ PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
+
+ // We get the mime service here even though we're the default implementation
+ // of it, so it's possible to override only the mime service and not need to
+ // reimplement the whole external helper app service itself.
+ nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
+
+ // Try to find a mime object by looking at the mime type/extension
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
+ nsAutoCString mimeType;
+ if (!fileExtension.IsEmpty()) {
+ mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
+ if (mimeInfo) {
+ mimeInfo->GetMIMEType(mimeType);
+
+ LOG(("OS-Provided mime type '%s' for extension '%s'\n",
+ mimeType.get(), fileExtension.get()));
+ }
+ }
+
+ if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
+ // Extension lookup gave us no useful match
+ mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
+ getter_AddRefs(mimeInfo));
+ mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
+ }
+
+ if (channel) {
+ channel->SetContentType(mimeType);
+ }
+
+ // Don't overwrite SERVERREQUEST
+ if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
+ reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
+ }
+ } else {
+ mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
+ getter_AddRefs(mimeInfo));
+ }
+ LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
+
+ // No mimeinfo -> we can't continue. probably OOM.
+ if (!mimeInfo) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aStreamListener = nullptr;
+ // We want the mimeInfo's primary extension to pass it to
+ // nsExternalAppHandler
+ nsAutoCString buf;
+ mimeInfo->GetPrimaryExtension(buf);
+
+ nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
+ buf,
+ aContentContext,
+ aWindowContext,
+ this,
+ fileName,
+ reason,
+ aForceSave);
+ if (!handler) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aStreamListener = handler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
+ const nsACString& aEncodingType,
+ bool *aApplyDecoding)
+{
+ *aApplyDecoding = true;
+ uint32_t i;
+ for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
+ if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
+ aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
+ *aApplyDecoding = false;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
+ nsIFile ** aFile)
+{
+ nsDependentString platformAppPath(aPlatformAppPath);
+ // First, check if we have an absolute path
+ nsIFile* localFile = nullptr;
+ nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
+ if (NS_SUCCEEDED(rv)) {
+ *aFile = localFile;
+ bool exists;
+ if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
+ NS_RELEASE(*aFile);
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ return NS_OK;
+ }
+
+
+ // Second, check if file exists in mozilla program directory
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (*aFile)->Append(platformAppPath);
+ if (NS_SUCCEEDED(rv)) {
+ bool exists = false;
+ rv = (*aFile)->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ return NS_OK;
+ }
+ NS_RELEASE(*aFile);
+ }
+
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin external protocol service default implementation...
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
+ bool * aHandlerExists)
+{
+ nsCOMPtr<nsIHandlerInfo> handlerInfo;
+ nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
+ getter_AddRefs(handlerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if we have any known possible handler apps for this
+ nsCOMPtr<nsIMutableArray> possibleHandlers;
+ handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
+
+ uint32_t length;
+ possibleHandlers->GetLength(&length);
+ if (length) {
+ *aHandlerExists = true;
+ return NS_OK;
+ }
+
+ // if not, fall back on an os-based handler
+ return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
+{
+ // check the per protocol setting first. it always takes precedence.
+ // if not set, then use the global setting.
+
+ nsAutoCString prefName("network.protocol-handler.expose.");
+ prefName += aProtocolScheme;
+ bool val;
+ if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
+ *aResult = val;
+ return NS_OK;
+ }
+
+ // by default, no protocol is exposed. i.e., by default all link clicks must
+ // go through the external protocol service. most applications override this
+ // default behavior.
+ *aResult =
+ Preferences::GetBool("network.protocol-handler.expose-all", false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
+{
+ return LoadURI(aURL, nullptr);
+}
+
+static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external.";
+static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
+
+NS_IMETHODIMP
+nsExternalHelperAppService::LoadURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (XRE_IsContentProcess()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+
+ nsCOMPtr<nsITabChild> tabChild(do_GetInterface(aWindowContext));
+ mozilla::dom::ContentChild::GetSingleton()->
+ SendLoadURIExternal(uri, static_cast<dom::TabChild*>(tabChild.get()));
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ aURI->GetSpec(spec);
+
+ if (spec.Find("%00") != -1)
+ return NS_ERROR_MALFORMED_URI;
+
+ spec.ReplaceSubstring("\"", "%22");
+ spec.ReplaceSubstring("`", "%60");
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ if (scheme.IsEmpty())
+ return NS_OK; // must have a scheme
+
+ // Deny load if the prefs say to do so
+ nsAutoCString externalPref(kExternalProtocolPrefPrefix);
+ externalPref += scheme;
+ bool allowLoad = false;
+ if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
+ // no scheme-specific value, check the default
+ if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
+ &allowLoad))) {
+ return NS_OK; // missing default pref
+ }
+ }
+
+ if (!allowLoad) {
+ return NS_OK; // explicitly denied
+ }
+
+ nsCOMPtr<nsIHandlerInfo> handler;
+ rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHandlerInfoAction preferredAction;
+ handler->GetPreferredAction(&preferredAction);
+ bool alwaysAsk = true;
+ handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
+
+ // if we are not supposed to ask, and the preferred action is to use
+ // a helper app or the system default, we just launch the URI.
+ if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
+ preferredAction == nsIHandlerInfo::useSystemDefault))
+ return handler->LaunchWithURI(uri, aWindowContext);
+
+ nsCOMPtr<nsIContentDispatchChooser> chooser =
+ do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return chooser->Ask(handler, aWindowContext, uri,
+ nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ // this method should only be implemented by each OS specific implementation of this service.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// Methods related to deleting temporary files on exit
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/* static */
+nsresult
+nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
+ nsCOMArray<nsIFile> &aFileList)
+{
+ bool isFile = false;
+
+ // as a safety measure, make sure the nsIFile is really a file and not a directory object.
+ aTemporaryFile->IsFile(&isFile);
+ if (!isFile) return NS_OK;
+
+ aFileList.AppendObject(aTemporaryFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
+{
+ return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
+{
+ return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
+{
+ int32_t numEntries = fileList.Count();
+ nsIFile* localFile;
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ localFile = fileList[index];
+ if (localFile) {
+ // First make the file writable, since the temp file is probably readonly.
+ localFile->SetPermissions(0600);
+ localFile->Remove(false);
+ }
+ }
+
+ fileList.Clear();
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryFiles()
+{
+ ExpungeTemporaryFilesHelper(mTemporaryFilesList);
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
+{
+ ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
+}
+
+static const char kExternalWarningPrefPrefix[] =
+ "network.protocol-handler.warn-external.";
+static const char kExternalWarningDefaultPref[] =
+ "network.protocol-handler.warn-external-default";
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
+ nsIHandlerInfo **aHandlerInfo)
+{
+ // XXX enterprise customers should be able to turn this support off with a
+ // single master pref (maybe use one of the "exposed" prefs here?)
+
+ bool exists;
+ nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
+ if (NS_FAILED(rv)) {
+ // Either it knows nothing, or we ran out of memory
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ bool hasHandler = false;
+ (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
+ if (hasHandler) {
+ rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ }
+
+ return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **aHandlerInfo)
+{
+ // intended to be implemented by the subclass
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
+ bool aOSHandlerExists)
+{
+ // this type isn't in our database, so we've only got an OS default handler,
+ // if one exists
+
+ if (aOSHandlerExists) {
+ // we've got a default, so use it
+ aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
+
+ // whether or not to ask the user depends on the warning preference
+ nsAutoCString scheme;
+ aHandlerInfo->GetType(scheme);
+
+ nsAutoCString warningPref(kExternalWarningPrefPrefix);
+ warningPref += scheme;
+ bool warn;
+ if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
+ // no scheme-specific value, check the default
+ warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
+ }
+ aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
+ } else {
+ // If no OS default existed, we set the preferred action to alwaysAsk.
+ // This really means not initialized (i.e. there's no available handler)
+ // to all the code...
+ aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
+ }
+
+ return NS_OK;
+}
+
+// XPCOM profile change observer
+NS_IMETHODIMP
+nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
+{
+ if (!strcmp(aTopic, "profile-before-change")) {
+ ExpungeTemporaryFiles();
+ } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+ ExpungeTemporaryPrivateFiles();
+ }
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin external app handler implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ADDREF(nsExternalAppHandler)
+NS_IMPL_RELEASE(nsExternalAppHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsICancelable)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
+ const nsCSubstring& aTempFileExtension,
+ nsIInterfaceRequestor* aContentContext,
+ nsIInterfaceRequestor* aWindowContext,
+ nsExternalHelperAppService *aExtProtSvc,
+ const nsAString& aSuggestedFilename,
+ uint32_t aReason, bool aForceSave)
+: mMimeInfo(aMIMEInfo)
+, mContentContext(aContentContext)
+, mWindowContext(aWindowContext)
+, mWindowToClose(nullptr)
+, mSuggestedFileName(aSuggestedFilename)
+, mForceSave(aForceSave)
+, mCanceled(false)
+, mShouldCloseWindow(false)
+, mStopRequestIssued(false)
+, mReason(aReason)
+, mContentLength(-1)
+, mProgress(0)
+, mSaver(nullptr)
+, mDialogProgressListener(nullptr)
+, mTransfer(nullptr)
+, mRequest(nullptr)
+, mExtProtSvc(aExtProtSvc)
+{
+
+ // make sure the extention includes the '.'
+ if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
+ mTempFileExtension = char16_t('.');
+ AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
+
+ // replace platform specific path separator and illegal characters to avoid any confusion
+ mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+ mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+
+ // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
+ const char16_t unsafeBidiCharacters[] = {
+ char16_t(0x061c), // Arabic Letter Mark
+ char16_t(0x200e), // Left-to-Right Mark
+ char16_t(0x200f), // Right-to-Left Mark
+ char16_t(0x202a), // Left-to-Right Embedding
+ char16_t(0x202b), // Right-to-Left Embedding
+ char16_t(0x202c), // Pop Directional Formatting
+ char16_t(0x202d), // Left-to-Right Override
+ char16_t(0x202e), // Right-to-Left Override
+ char16_t(0x2066), // Left-to-Right Isolate
+ char16_t(0x2067), // Right-to-Left Isolate
+ char16_t(0x2068), // First Strong Isolate
+ char16_t(0x2069), // Pop Directional Isolate
+ char16_t(0)
+ };
+ mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
+ mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
+
+ // Make sure extension is correct.
+ EnsureSuggestedFileName();
+
+ mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
+}
+
+nsExternalAppHandler::~nsExternalAppHandler()
+{
+ MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
+}
+
+void
+nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
+{
+ MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
+ // Remove our request from the child loadGroup
+ RetargetLoadNotifications(request);
+ MaybeCloseWindow();
+}
+
+NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
+{
+ // This is always called by nsHelperDlg.js. Go ahead and register the
+ // progress listener. At this point, we don't have mTransfer.
+ mDialogProgressListener = aWebProgressListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
+{
+ if (mFinalFileDestination)
+ *aTarget = mFinalFileDestination;
+ else
+ *aTarget = mTempFile;
+
+ NS_IF_ADDREF(*aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
+{
+ // Use the real target if it's been set
+ if (mFinalFileDestination)
+ return mFinalFileDestination->IsExecutable(aExec);
+
+ // Otherwise, use the stored executable-ness of the temporary
+ *aExec = mTempFileIsExecutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
+{
+ *aTime = mTimeDownloadStarted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
+{
+ // we are going to run the downloading of the helper app in our own little docloader / load group context.
+ // so go ahead and force the creation of a load group and doc loader for us to use...
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel)
+ return;
+
+ // we need to store off the original (pre redirect!) channel that initiated the load. We do
+ // this so later on, we can pass any refresh urls associated with the original channel back to the
+ // window context which started the whole process. More comments about that are listed below....
+ // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
+ // ideally we should be able to just use mChannel (the channel we are extracting content from) or
+ // the default load channel associated with the original load group. Unfortunately because
+ // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
+ // which is what we really want....
+
+ // Note that we need to do this before removing aChannel from the loadgroup,
+ // since that would mess with the original channel on the loader.
+ nsCOMPtr<nsIDocumentLoader> origContextLoader =
+ do_GetInterface(mContentContext);
+ if (origContextLoader) {
+ origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
+ }
+
+ bool isPrivate = NS_UsePrivateBrowsing(aChannel);
+
+ nsCOMPtr<nsILoadGroup> oldLoadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
+
+ if(oldLoadGroup) {
+ oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
+ }
+
+ aChannel->SetLoadGroup(nullptr);
+ aChannel->SetNotificationCallbacks(nullptr);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(isPrivate);
+ }
+}
+
+/**
+ * Make mTempFileExtension contain an extension exactly when its previous value
+ * is different from mSuggestedFileName's extension, so that it can be appended
+ * to mSuggestedFileName and form a valid, useful leaf name.
+ * This is required so that the (renamed) temporary file has the correct extension
+ * after downloading to make sure the OS will launch the application corresponding
+ * to the MIME type (which was used to calculate mTempFileExtension). This prevents
+ * a cgi-script named foobar.exe that returns application/zip from being named
+ * foobar.exe and executed as an executable file. It also blocks content that
+ * a web site might provide with a content-disposition header indicating
+ * filename="foobar.exe" from being downloaded to a file with extension .exe
+ * and executed.
+ */
+void nsExternalAppHandler::EnsureSuggestedFileName()
+{
+ // Make sure there is a mTempFileExtension (not "" or ".").
+ // Remember that mTempFileExtension will always have the leading "."
+ // (the check for empty is just to be safe).
+ if (mTempFileExtension.Length() > 1)
+ {
+ // Get mSuggestedFileName's current extension.
+ nsAutoString fileExt;
+ int32_t pos = mSuggestedFileName.RFindChar('.');
+ if (pos != kNotFound)
+ mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
+
+ // Now, compare fileExt to mTempFileExtension.
+ if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
+ {
+ // Matches -> mTempFileExtension can be empty
+ mTempFileExtension.Truncate();
+ }
+ }
+}
+
+nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
+{
+ // First we need to try to get the destination directory for the temporary
+ // file.
+ nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this point, we do not have a filename for the temp file. For security
+ // purposes, this cannot be predictable, so we must use a cryptographic
+ // quality PRNG to generate one.
+ // We will request raw random bytes, and transform that to a base64 string,
+ // as all characters from the base64 set are acceptable for filenames. For
+ // each three bytes of random data, we will get four bytes of ASCII. Request
+ // a bit more, to be safe, and truncate to the length we want in the end.
+
+ const uint32_t wantedFileNameLength = 8;
+ const uint32_t requiredBytesLength =
+ static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
+
+ nsCOMPtr<nsIRandomGenerator> rg =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint8_t *buffer;
+ rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tempLeafName;
+ nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
+ rv = Base64Encode(randomData, tempLeafName);
+ free(buffer);
+ buffer = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tempLeafName.Truncate(wantedFileNameLength);
+
+ // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
+ // to replace illegal characters -- notably '/'
+ tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+
+ // now append our extension.
+ nsAutoCString ext;
+ mMimeInfo->GetPrimaryExtension(ext);
+ if (!ext.IsEmpty()) {
+ ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+ if (ext.First() != '.')
+ tempLeafName.Append('.');
+ tempLeafName.Append(ext);
+ }
+
+ // We need to temporarily create a dummy file with the correct
+ // file extension to determine the executable-ness, so do this before adding
+ // the extra .part extension.
+ nsCOMPtr<nsIFile> dummyFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the file name without .part
+ rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Store executable-ness then delete
+ dummyFile->IsExecutable(&mTempFileIsExecutable);
+ dummyFile->Remove(false);
+
+ // Add an additional .part to prevent the OS from running this file in the
+ // default application.
+ tempLeafName.AppendLiteral(".part");
+
+ rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
+ // make this file unique!!!
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
+ // This is a bit broken in the case when createUnique actually had to append
+ // some numbers, because then we now have a filename like foo.bar-1.part and
+ // we'll end up with foo.bar-1.bar as our final filename if we end up using
+ // this. But the other options are all bad too.... Ideally we'd have a way
+ // to tell createUnique to put its unique marker before the extension that
+ // comes before ".part" or something.
+ rv = mTempFile->GetLeafName(mTempLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
+ NS_ERROR_UNEXPECTED);
+
+ // Strip off the ".part" from mTempLeafName
+ mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
+
+ MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
+ mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSaver->SetObserver(this);
+ if (NS_FAILED(rv)) {
+ mSaver = nullptr;
+ return rv;
+ }
+
+ rv = mSaver->EnableSha256();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSaver->EnableSignatureInfo();
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("Enabled hashing and signature verification"));
+
+ rv = mSaver->SetTarget(mTempFile, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+void
+nsExternalAppHandler::MaybeApplyDecodingForExtension(nsIRequest *aRequest)
+{
+ MOZ_ASSERT(aRequest);
+
+ nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
+ if (!encChannel) {
+ return;
+ }
+
+ // Turn off content encoding conversions if needed
+ bool applyConversion = true;
+
+ // First, check to see if conversion is already disabled. If so, we
+ // have nothing to do here.
+ encChannel->GetApplyConversion(&applyConversion);
+ if (!applyConversion) {
+ return;
+ }
+
+ nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
+ if (sourceURL)
+ {
+ nsAutoCString extension;
+ sourceURL->GetFileExtension(extension);
+ if (!extension.IsEmpty())
+ {
+ nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
+ encChannel->GetContentEncodings(getter_AddRefs(encEnum));
+ if (encEnum)
+ {
+ bool hasMore;
+ nsresult rv = encEnum->HasMore(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ {
+ nsAutoCString encType;
+ rv = encEnum->GetNext(encType);
+ if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
+ {
+ MOZ_ASSERT(mExtProtSvc);
+ mExtProtSvc->ApplyDecodingForExtension(extension, encType,
+ &applyConversion);
+ }
+ }
+ }
+ }
+ }
+
+ encChannel->SetApplyConversion( applyConversion );
+ return;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ NS_PRECONDITION(request, "OnStartRequest without request?");
+
+ // Set mTimeDownloadStarted here as the download has already started and
+ // we want to record the start time before showing the filepicker.
+ mTimeDownloadStarted = PR_Now();
+
+ mRequest = request;
+
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
+ mIsFileChannel = fileChan != nullptr;
+ if (!mIsFileChannel) {
+ // It's possible that this request came from the child process and the
+ // file channel actually lives there. If this returns true, then our
+ // mSourceUrl will be an nsIFileURL anyway.
+ nsCOMPtr<dom::nsIExternalHelperAppParent> parent(do_QueryInterface(request));
+ mIsFileChannel = parent && parent->WasFileChannel();
+ }
+
+ // Get content length
+ if (aChannel) {
+ aChannel->GetContentLength(&mContentLength);
+ }
+
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
+ // Determine whether a new window was opened specifically for this request
+ if (props) {
+ bool tmp = false;
+ props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
+ &tmp);
+ mShouldCloseWindow = tmp;
+ }
+
+ // Now get the URI
+ if (aChannel) {
+ aChannel->GetURI(getter_AddRefs(mSourceUrl));
+ }
+
+ // retarget all load notifications to our docloader instead of the original window's docloader...
+ RetargetLoadNotifications(request);
+
+ // Check to see if there is a refresh header on the original channel.
+ if (mOriginalChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
+ if (httpChannel) {
+ nsAutoCString refreshHeader;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
+ refreshHeader);
+ if (!refreshHeader.IsEmpty()) {
+ mShouldCloseWindow = false;
+ }
+ }
+ }
+
+ // Close the underlying DOMWindow if there is no refresh header
+ // and it was opened specifically for the download
+ MaybeCloseWindow();
+
+ // In an IPC setting, we're allowing the child process, here, to make
+ // decisions about decoding the channel (e.g. decompression). It will
+ // still forward the decoded (uncompressed) data back to the parent.
+ // Con: Uncompressed data means more IPC overhead.
+ // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
+ // Parent process doesn't need to expect CPU time on decompression.
+ MaybeApplyDecodingForExtension(aChannel);
+
+ // At this point, the child process has done everything it can usefully do
+ // for OnStartRequest.
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+
+ rv = SetUpTempFile(aChannel);
+ if (NS_FAILED(rv)) {
+ nsresult transferError = rv;
+
+ rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to create transfer to report failure."
+ "Will fallback to prompter!"));
+ }
+
+ mCanceled = true;
+ request->Cancel(transferError);
+
+ nsAutoString path;
+ if (mTempFile)
+ mTempFile->GetPath(path);
+
+ SendStatusChange(kWriteError, transferError, request, path);
+
+ return NS_OK;
+ }
+
+ // Inform channel it is open on behalf of a download to prevent caching.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
+ if (httpInternal) {
+ httpInternal->SetChannelIsForDownload(true);
+ }
+
+ // now that the temp file is set up, find out if we need to invoke a dialog
+ // asking the user what they want us to do with this content...
+
+ // We can get here for three reasons: "can't handle", "sniffed type", or
+ // "server sent content-disposition:attachment". In the first case we want
+ // to honor the user's "always ask" pref; in the other two cases we want to
+ // honor it only if the default action is "save". Opening attachments in
+ // helper apps by default breaks some websites (especially if the attachment
+ // is one part of a multipart document). Opening sniffed content in helper
+ // apps by default introduces security holes that we'd rather not have.
+
+ // So let's find out whether the user wants to be prompted. If he does not,
+ // check mReason and the preferred action to see what we should do.
+
+ bool alwaysAsk = true;
+ mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
+ if (alwaysAsk) {
+ // But we *don't* ask if this mimeInfo didn't come from
+ // our user configuration datastore and the user has said
+ // at some point in the distant past that they don't
+ // want to be asked. The latter fact would have been
+ // stored in pref strings back in the old days.
+
+ bool mimeTypeIsInDatastore = false;
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
+ }
+ if (!handlerSvc || !mimeTypeIsInDatastore) {
+ nsAutoCString MIMEType;
+ mMimeInfo->GetMIMEType(MIMEType);
+ if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get())) {
+ // Don't need to ask after all.
+ alwaysAsk = false;
+ // Make sure action matches pref (save to disk).
+ mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get())) {
+ // Don't need to ask after all.
+ alwaysAsk = false;
+ }
+ }
+ }
+
+ int32_t action = nsIMIMEInfo::saveToDisk;
+ mMimeInfo->GetPreferredAction( &action );
+
+ // OK, now check why we're here
+ if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
+ // Force asking if we're not saving. See comment back when we fetched the
+ // alwaysAsk boolean for details.
+ alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
+ }
+
+ // if we were told that we _must_ save to disk without asking, all the stuff
+ // before this is irrelevant; override it
+ if (mForceSave) {
+ alwaysAsk = false;
+ action = nsIMIMEInfo::saveToDisk;
+ }
+
+ if (alwaysAsk)
+ {
+ // Display the dialog
+ mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this will create a reference cycle (the dialog holds a reference to us as
+ // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
+ rv = mDialog->Show(this, GetDialogParent(), mReason);
+
+ // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
+ }
+ else
+ {
+
+ // We need to do the save/open immediately, then.
+#ifdef XP_WIN
+ /* We need to see whether the file we've got here could be
+ * executable. If it could, we had better not try to open it!
+ * We can skip this check, though, if we have a setting to open in a
+ * helper app.
+ * This code mirrors the code in
+ * nsExternalAppHandler::LaunchWithApplication so that what we
+ * test here is as close as possible to what will really be
+ * happening if we decide to execute
+ */
+ nsCOMPtr<nsIHandlerApp> prefApp;
+ mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
+ if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
+ nsCOMPtr<nsIFile> fileToTest;
+ GetTargetFile(getter_AddRefs(fileToTest));
+ if (fileToTest) {
+ bool isExecutable;
+ rv = fileToTest->IsExecutable(&isExecutable);
+ if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good
+ action = nsIMIMEInfo::saveToDisk;
+ }
+ } else { // Paranoia is good here too, though this really should not happen
+ NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
+ action = nsIMIMEInfo::saveToDisk;
+ }
+ }
+
+#endif
+ if (action == nsIMIMEInfo::useHelperApp ||
+ action == nsIMIMEInfo::useSystemDefault) {
+ rv = LaunchWithApplication(nullptr, false);
+ } else {
+ rv = SaveToDisk(nullptr, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+// Convert error info into proper message text and send OnStatusChange
+// notification to the dialog progress listener or nsITransfer implementation.
+void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
+{
+ nsAutoString msgId;
+ switch (rv) {
+ case NS_ERROR_OUT_OF_MEMORY:
+ // No memory
+ msgId.AssignLiteral("noMemory");
+ break;
+
+ case NS_ERROR_FILE_DISK_FULL:
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ // Out of space on target volume.
+ msgId.AssignLiteral("diskFull");
+ break;
+
+ case NS_ERROR_FILE_READ_ONLY:
+ // Attempt to write to read/only file.
+ msgId.AssignLiteral("readOnly");
+ break;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ if (type == kWriteError) {
+ // Attempt to write without sufficient permissions.
+#if defined(ANDROID)
+ // On Android (and Gonk), this means the SD card is present but
+ // unavailable (read-only).
+ msgId.AssignLiteral("SDAccessErrorCardReadOnly");
+#else
+ msgId.AssignLiteral("accessError");
+#endif
+ } else {
+ msgId.AssignLiteral("launchError");
+ }
+ break;
+
+ case NS_ERROR_FILE_NOT_FOUND:
+ case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
+ case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+ // Helper app not found, let's verify this happened on launch
+ if (type == kLaunchError) {
+ msgId.AssignLiteral("helperAppNotFound");
+ break;
+ }
+#if defined(ANDROID)
+ else if (type == kWriteError) {
+ // On Android (and Gonk), this means the SD card is missing (not in
+ // SD slot).
+ msgId.AssignLiteral("SDAccessErrorCardMissing");
+ break;
+ }
+#endif
+ MOZ_FALLTHROUGH;
+
+ default:
+ // Generic read/write/launch error message.
+ switch (type) {
+ case kReadError:
+ msgId.AssignLiteral("readError");
+ break;
+ case kWriteError:
+ msgId.AssignLiteral("writeError");
+ break;
+ case kLaunchError:
+ msgId.AssignLiteral("launchError");
+ break;
+ }
+ break;
+ }
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
+ NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
+
+ // Get properties file bundle and extract status string.
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (stringService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties",
+ getter_AddRefs(bundle)))) {
+ nsXPIDLString msgText;
+ const char16_t *strings[] = { path.get() };
+ if (NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1,
+ getter_Copies(msgText)))) {
+ if (mDialogProgressListener) {
+ // We have a listener, let it handle the error.
+ mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+ } else if (mTransfer) {
+ mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+ } else if (XRE_IsParentProcess()) {
+ // We don't have a listener. Simply show the alert ourselves.
+ nsresult qiRv;
+ nsCOMPtr<nsIPrompt> prompter(do_GetInterface(GetDialogParent(), &qiRv));
+ nsXPIDLString title;
+ bundle->FormatStringFromName(u"title",
+ strings,
+ 1,
+ getter_Copies(title));
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
+ ("mContentContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
+ mContentContext.get(),
+ prompter.get(),
+ qiRv,
+ NS_ConvertUTF16toUTF8(title).get(),
+ NS_ConvertUTF16toUTF8(msgText).get()));
+
+ // If we didn't have a prompter we will try and get a window
+ // instead, get it's docshell and use it to alert the user.
+ if (!prompter) {
+ nsCOMPtr<nsPIDOMWindowOuter> window(do_GetInterface(GetDialogParent()));
+ if (!window || !window->GetDocShell()) {
+ return;
+ }
+
+ prompter = do_GetInterface(window->GetDocShell(), &qiRv);
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
+ ("No prompter from mContentContext, using DocShell, " \
+ "window=0x%p, docShell=0x%p, " \
+ "prompter=0x%p, qi rv=0x%08X",
+ window.get(),
+ window->GetDocShell(),
+ prompter.get(),
+ qiRv));
+
+ // If we still don't have a prompter, there's nothing else we
+ // can do so just return.
+ if (!prompter) {
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ ("No prompter from DocShell, no way to alert user"));
+ return;
+ }
+ }
+
+ // We should always have a prompter at this point.
+ prompter->Alert(title, msgText);
+ }
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
+ nsIInputStream * inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ nsresult rv = NS_OK;
+ // first, check to see if we've been canceled....
+ if (mCanceled || !mSaver) {
+ // then go cancel our underlying channel too
+ return request->Cancel(NS_BINDING_ABORTED);
+ }
+
+ // read the data out of the stream and write it to the temp file.
+ if (count > 0) {
+ mProgress += count;
+
+ nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
+ rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
+ if (NS_SUCCEEDED(rv)) {
+ // Send progress notification.
+ if (mTransfer) {
+ mTransfer->OnProgressChange64(nullptr, request, mProgress,
+ mContentLength, mProgress,
+ mContentLength);
+ }
+ } else {
+ // An error occurred, notify listener.
+ nsAutoString tempFilePath;
+ if (mTempFile) {
+ mTempFile->GetPath(tempFilePath);
+ }
+ SendStatusChange(kReadError, rv, request, tempFilePath);
+
+ // Cancel the download.
+ Cancel(rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ LOG(("nsExternalAppHandler::OnStopRequest\n"
+ " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
+ mCanceled, mTransfer.get(), aStatus));
+
+ mStopRequestIssued = true;
+
+ // Cancel if the request did not complete successfully.
+ if (!mCanceled && NS_FAILED(aStatus)) {
+ // Send error notification.
+ nsAutoString tempFilePath;
+ if (mTempFile)
+ mTempFile->GetPath(tempFilePath);
+ SendStatusChange( kReadError, aStatus, request, tempFilePath );
+
+ Cancel(aStatus);
+ }
+
+ // first, check to see if we've been canceled....
+ if (mCanceled || !mSaver) {
+ return NS_OK;
+ }
+
+ return mSaver->Finish(NS_OK);
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
+ nsIFile *aTarget)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
+ nsresult aStatus)
+{
+ LOG(("nsExternalAppHandler::OnSaveComplete\n"
+ " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
+ aSaver, aStatus, mCanceled, mTransfer.get()));
+
+ if (!mCanceled) {
+ // Save the hash and signature information
+ (void)mSaver->GetSha256Hash(mHash);
+ (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
+
+ // Free the reference that the saver keeps on us, even if we couldn't get
+ // the hash.
+ mSaver = nullptr;
+
+ // Save the redirect information.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> redirectChain =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("nsExternalAppHandler: Got %u redirects\n", loadInfo->RedirectChain().Length()));
+ for (nsIPrincipal* principal : loadInfo->RedirectChain()) {
+ redirectChain->AppendElement(principal, false);
+ }
+ mRedirects = redirectChain;
+ }
+ }
+
+ if (NS_FAILED(aStatus)) {
+ nsAutoString path;
+ mTempFile->GetPath(path);
+
+ // It may happen when e10s is enabled that there will be no transfer
+ // object available to communicate status as expected by the system.
+ // Let's try and create a temporary transfer object to take care of this
+ // for us, we'll fall back to using the prompt service if we absolutely
+ // have to.
+ if (!mTransfer) {
+ // We don't care if this fails.
+ CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
+ }
+
+ SendStatusChange(kWriteError, aStatus, nullptr, path);
+ if (!mCanceled)
+ Cancel(aStatus);
+ return NS_OK;
+ }
+ }
+
+ // Notify the transfer object that we are done if the user has chosen an
+ // action. If the user hasn't chosen an action, the progress listener
+ // (nsITransfer) will be notified in CreateTransfer.
+ if (mTransfer) {
+ NotifyTransfer(aStatus);
+ }
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
+ MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
+
+ LOG(("Notifying progress listener"));
+
+ if (NS_SUCCEEDED(aStatus)) {
+ (void)mTransfer->SetSha256Hash(mHash);
+ (void)mTransfer->SetSignatureInfo(mSignatureInfo);
+ (void)mTransfer->SetRedirects(mRedirects);
+ (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
+ mContentLength, mProgress, mContentLength);
+ }
+
+ (void)mTransfer->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
+
+ // This nsITransfer object holds a reference to us (we are its observer), so
+ // we need to release the reference to break a reference cycle (and therefore
+ // to prevent leaking). We do this even if the previous calls failed.
+ mTransfer = nullptr;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
+{
+ *aMIMEInfo = mMimeInfo;
+ NS_ADDREF(*aMIMEInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
+{
+ NS_ENSURE_ARG(aSourceURI);
+ *aSourceURI = mSourceUrl;
+ NS_IF_ADDREF(*aSourceURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
+{
+ aSuggestedFileName = mSuggestedFileName;
+ return NS_OK;
+}
+
+nsresult nsExternalAppHandler::CreateTransfer()
+{
+ LOG(("nsExternalAppHandler::CreateTransfer"));
+
+ MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
+ // We are back from the helper app dialog (where the user chooses to save or
+ // open), but we aren't done processing the load. in this case, throw up a
+ // progress dialog so the user can see what's going on.
+ // Also, release our reference to mDialog. We don't need it anymore, and we
+ // need to break the reference cycle.
+ mDialog = nullptr;
+ if (!mDialogProgressListener) {
+ NS_WARNING("The dialog should nullify the dialog progress listener");
+ }
+ nsresult rv;
+
+ // We must be able to create an nsITransfer object. If not, it doesn't matter
+ // much that we can't launch the helper application or save to disk. Work on
+ // a local copy rather than mTransfer until we know we succeeded, to make it
+ // clearer that this function is re-entrant.
+ nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
+ NS_TRANSFER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the download
+ nsCOMPtr<nsIURI> target;
+ rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
+
+ rv = transfer->Init(mSourceUrl, target, EmptyString(),
+ mMimeInfo, mTimeDownloadStarted, mTempFile, this,
+ channel && NS_UsePrivateBrowsing(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now let's add the download to history
+ nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
+ if (dh) {
+ if (channel && !NS_UsePrivateBrowsing(channel)) {
+ nsCOMPtr<nsIURI> referrer;
+ NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
+
+ dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
+ }
+ }
+
+ // If we were cancelled since creating the transfer, just return. It is
+ // always ok to return NS_OK if we are cancelled. Callers of this function
+ // must call Cancel if CreateTransfer fails, but there's no need to cancel
+ // twice.
+ if (mCanceled) {
+ return NS_OK;
+ }
+ rv = transfer->OnStateChange(nullptr, mRequest,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mRequest = nullptr;
+ // Finally, save the transfer to mTransfer.
+ mTransfer = transfer;
+ transfer = nullptr;
+
+ // While we were bringing up the progress dialog, we actually finished
+ // processing the url. If that's the case then mStopRequestIssued will be
+ // true and OnSaveComplete has been called.
+ if (mStopRequestIssued && !mSaver && mTransfer) {
+ NotifyTransfer(NS_OK);
+ }
+
+ return rv;
+}
+
+nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
+{
+ nsresult rv;
+ nsCOMPtr<nsITransfer> transfer =
+ do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have a download directory we're kinda screwed but it's OK
+ // we'll still report the error via the prompter.
+ nsCOMPtr<nsIFile> pseudoFile;
+ rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the default suggested filename. If the user restarts the transfer
+ // we will re-trigger a filename check anyway to ensure that it is unique.
+ rv = pseudoFile->Append(mSuggestedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> pseudoTarget;
+ rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
+ mMimeInfo, mTimeDownloadStarted, nullptr, this,
+ aIsPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Our failed transfer is ready.
+ mTransfer = transfer.forget();
+
+ return NS_OK;
+}
+
+nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
+{
+ if (aFile)
+ ContinueSave(aFile);
+ else
+ Cancel(NS_BINDING_ABORTED);
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
+{
+ // Display the dialog
+ // XXX Convert to use file picker? No, then embeddors could not do any sort of
+ // "AutoDownload" w/o showing a prompt
+ nsresult rv = NS_OK;
+ if (!mDialog) {
+ // Get helper app launcher dialog.
+ mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
+ if (rv != NS_OK) {
+ Cancel(NS_BINDING_ABORTED);
+ return;
+ }
+ }
+
+ // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
+ // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
+
+ // Now, be sure to keep |this| alive, and the dialog
+ // If we don't do this, users that close the helper app dialog while the file
+ // picker is up would cause Cancel() to be called, and the dialog would be
+ // released, which would release this object too, which would crash.
+ // See Bug 249143
+ RefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
+ nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
+
+ rv = dlg->PromptForSaveToFileAsync(this,
+ GetDialogParent(),
+ aDefaultFile.get(),
+ aFileExtension.get(),
+ mForceSave);
+ if (NS_FAILED(rv)) {
+ Cancel(NS_BINDING_ABORTED);
+ }
+}
+
+// SaveToDisk should only be called by the helper app dialog which allows
+// the user to say launch with application or save to disk. It doesn't actually
+// perform the save, it just prompts for the destination file name.
+NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+
+ if (!aNewFileLocation) {
+ if (mSuggestedFileName.IsEmpty())
+ RequestSaveDestination(mTempLeafName, mTempFileExtension);
+ else
+ {
+ nsAutoString fileExt;
+ int32_t pos = mSuggestedFileName.RFindChar('.');
+ if (pos >= 0)
+ mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
+ if (fileExt.IsEmpty())
+ fileExt = mTempFileExtension;
+
+ RequestSaveDestination(mSuggestedFileName, fileExt);
+ }
+ } else {
+ ContinueSave(aNewFileLocation);
+ }
+
+ return NS_OK;
+}
+nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
+ mFinalFileDestination = do_QueryInterface(fileToUse);
+
+ // Move what we have in the final directory, but append .part
+ // to it, to indicate that it's unfinished. Do not call SetTarget on the
+ // saver if we are done (Finish has been called) but OnSaverComplete has not
+ // been called.
+ if (mFinalFileDestination && mSaver && !mStopRequestIssued)
+ {
+ nsCOMPtr<nsIFile> movedFile;
+ mFinalFileDestination->Clone(getter_AddRefs(movedFile));
+ if (movedFile) {
+ // Get the old leaf name and append .part to it
+ nsAutoString name;
+ mFinalFileDestination->GetLeafName(name);
+ name.AppendLiteral(".part");
+ movedFile->SetLeafName(name);
+
+ rv = mSaver->SetTarget(movedFile, true);
+ if (NS_FAILED(rv)) {
+ nsAutoString path;
+ mTempFile->GetPath(path);
+ SendStatusChange(kWriteError, rv, nullptr, path);
+ Cancel(rv);
+ return NS_OK;
+ }
+
+ mTempFile = movedFile;
+ }
+ }
+
+ // The helper app dialog has told us what to do and we have a final file
+ // destination.
+ rv = CreateTransfer();
+ // If we fail to create the transfer, Cancel.
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return rv;
+ }
+
+ // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
+ // if there is one. We don't want to do this before the save as dialog goes away because this dialog
+ // is modal and we do bad things if you try to load a web page in the underlying window while a modal
+ // dialog is still up.
+ ProcessAnyRefreshTags();
+
+ return NS_OK;
+}
+
+
+// LaunchWithApplication should only be called by the helper app dialog which
+// allows the user to say launch with application or save to disk. It doesn't
+// actually perform launch with application.
+NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ // user has chosen to launch using an application, fire any refresh tags now...
+ ProcessAnyRefreshTags();
+
+ if (mMimeInfo && aApplication) {
+ PlatformLocalHandlerApp_t *handlerApp =
+ new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
+ mMimeInfo->SetPreferredApplicationHandler(handlerApp);
+ }
+
+ // Now check if the file is local, in which case we won't bother with saving
+ // it to a temporary directory and just launch it from where it is
+ nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
+ if (fileUrl && mIsFileChannel) {
+ Cancel(NS_BINDING_ABORTED);
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mMimeInfo->LaunchWithFile(file);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ nsAutoString path;
+ if (file)
+ file->GetPath(path);
+ // If we get here, an error happened
+ SendStatusChange(kLaunchError, rv, nullptr, path);
+ return rv;
+ }
+
+ // Now that the user has elected to launch the downloaded file with a helper
+ // app, we're justified in removing the 'salted' name. We'll rename to what
+ // was specified in mSuggestedFileName after the download is done prior to
+ // launching the helper app. So that any existing file of that name won't be
+ // overwritten we call CreateUnique(). Also note that we use the same
+ // directory as originally downloaded so nsDownload can rename in place
+ // later.
+ nsCOMPtr<nsIFile> fileToUse;
+ (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
+
+ if (mSuggestedFileName.IsEmpty()) {
+ // Keep using the leafname of the temp file, since we're just starting a helper
+ mSuggestedFileName = mTempLeafName;
+ }
+
+#ifdef XP_WIN
+ fileToUse->Append(mSuggestedFileName + mTempFileExtension);
+#else
+ fileToUse->Append(mSuggestedFileName);
+#endif
+
+ nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if(NS_SUCCEEDED(rv)) {
+ mFinalFileDestination = do_QueryInterface(fileToUse);
+ // launch the progress window now that the user has picked the desired action.
+ rv = CreateTransfer();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ } else {
+ // Cancel the download and report an error. We do not want to end up in
+ // a state where it appears that we have a normal download that is
+ // pointing to a file that we did not actually create.
+ nsAutoString path;
+ mTempFile->GetPath(path);
+ SendStatusChange(kWriteError, rv, nullptr, path);
+ Cancel(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
+{
+ NS_ENSURE_ARG(NS_FAILED(aReason));
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+ mCanceled = true;
+
+ if (mSaver) {
+ // We are still writing to the target file. Give the saver a chance to
+ // close the target file, then notify the transfer object if necessary in
+ // the OnSaveComplete callback.
+ mSaver->Finish(aReason);
+ mSaver = nullptr;
+ } else {
+ if (mStopRequestIssued && mTempFile) {
+ // This branch can only happen when the user cancels the helper app dialog
+ // when the request has completed. The temp file has to be removed here,
+ // because mSaver has been released at that time with the temp file left.
+ (void)mTempFile->Remove(false);
+ }
+
+ // Notify the transfer object that the download has been canceled, if the
+ // user has already chosen an action and we didn't notify already.
+ if (mTransfer) {
+ NotifyTransfer(aReason);
+ }
+ }
+
+ // Break our reference cycle with the helper app dialog (set up in
+ // OnStartRequest)
+ mDialog = nullptr;
+
+ mRequest = nullptr;
+
+ // Release the listener, to break the reference cycle with it (we are the
+ // observer of the listener).
+ mDialogProgressListener = nullptr;
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::ProcessAnyRefreshTags()
+{
+ // one last thing, try to see if the original window context supports a refresh interface...
+ // Sometimes, when you download content that requires an external handler, there is
+ // a refresh header associated with the download. This refresh header points to a page
+ // the content provider wants the user to see after they download the content. How do we
+ // pass this refresh information back to the caller? For now, try to get the refresh URI
+ // interface. If the window context where the request originated came from supports this
+ // then we can force it to process the refresh information (if there is any) from this channel.
+ if (mContentContext && mOriginalChannel) {
+ nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mContentContext));
+ if (refreshHandler) {
+ refreshHandler->SetupRefreshURI(mOriginalChannel);
+ }
+ mOriginalChannel = nullptr;
+ }
+}
+
+bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
+{
+ // Search the obsolete pref strings.
+ nsAdoptingCString prefCString = Preferences::GetCString(prefName);
+ if (prefCString.IsEmpty()) {
+ // Default is true, if not found in the pref string.
+ return true;
+ }
+
+ NS_UnescapeURL(prefCString);
+ nsACString::const_iterator start, end;
+ prefCString.BeginReading(start);
+ prefCString.EndReading(end);
+ return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
+ start, end);
+}
+
+nsresult nsExternalAppHandler::MaybeCloseWindow()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mContentContext);
+ NS_ENSURE_STATE(window);
+
+ if (mShouldCloseWindow) {
+ // Reset the window context to the opener window so that the dependent
+ // dialogs have a parent
+ nsCOMPtr<nsPIDOMWindowOuter> opener = window->GetOpener();
+
+ if (opener && !opener->Closed()) {
+ mContentContext = do_GetInterface(opener);
+
+ // Now close the old window. Do it on a timer so that we don't run
+ // into issues trying to close the window before it has fully opened.
+ NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
+ mWindowToClose = window;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::Notify(nsITimer* timer)
+{
+ NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
+
+ mWindowToClose->Close();
+ mWindowToClose = nullptr;
+ mTimer = nullptr;
+
+ return NS_OK;
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The following section contains our nsIMIMEService implementation and related methods.
+//
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// nsIMIMEService methods
+NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval)
+{
+ NS_PRECONDITION(!aMIMEType.IsEmpty() ||
+ !aFileExt.IsEmpty(),
+ "Give me something to work with");
+ LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
+ PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
+
+ *_retval = nullptr;
+
+ // OK... we need a type. Get one.
+ nsAutoCString typeToUse(aMIMEType);
+ if (typeToUse.IsEmpty()) {
+ nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We promise to only send lower case mime types to the OS
+ ToLowerCase(typeToUse);
+
+ // (1) Ask the OS for a mime info
+ bool found;
+ *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
+ LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
+ // If we got no mimeinfo, something went wrong. Probably lack of memory.
+ if (!*_retval)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // (2) Now, let's see if we can find something in our datastore
+ // This will not overwrite the OS information that interests us
+ // (i.e. default application, default app. description)
+ nsresult rv;
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ bool hasHandler = false;
+ (void) handlerSvc->Exists(*_retval, &hasHandler);
+ if (hasHandler) {
+ rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
+ LOG(("Data source: Via type: retval 0x%08x\n", rv));
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ found = found || NS_SUCCEEDED(rv);
+
+ if (!found || NS_FAILED(rv)) {
+ // No type match, try extension match
+ if (!aFileExt.IsEmpty()) {
+ nsAutoCString overrideType;
+ rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
+ if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
+ // We can't check handlerSvc->Exists() here, because we have a
+ // overideType. That's ok, it just results in some console noise.
+ // (If there's no handler for the override type, it throws)
+ rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
+ LOG(("Data source: Via ext: retval 0x%08x\n", rv));
+ found = found || NS_SUCCEEDED(rv);
+ }
+ }
+ }
+ }
+
+ // (3) No match yet. Ask extras.
+ if (!found) {
+ rv = NS_ERROR_FAILURE;
+#ifdef XP_WIN
+ /* XXX Gross hack to wallpaper over the most common Win32
+ * extension issues caused by the fix for bug 116938. See bug
+ * 120327, comment 271 for why this is needed. Not even sure we
+ * want to remove this once we have fixed all this stuff to work
+ * right; any info we get from extras on this type is pretty much
+ * useless....
+ */
+ if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
+#endif
+ rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
+ LOG(("Searched extras (by type), rv 0x%08X\n", rv));
+ // If that didn't work out, try file extension from extras
+ if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
+ rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
+ LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
+ }
+ // If that still didn't work, set the file description to "ext File"
+ if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
+ // XXXzpao This should probably be localized
+ nsAutoCString desc(aFileExt);
+ desc.AppendLiteral(" File");
+ (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
+ LOG(("Falling back to 'File' file description\n"));
+ }
+ }
+
+ // Finally, check if we got a file extension and if yes, if it is an
+ // extension on the mimeinfo, in which case we want it to be the primary one
+ if (!aFileExt.IsEmpty()) {
+ bool matches = false;
+ (*_retval)->ExtensionExists(aFileExt, &matches);
+ LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
+ if (matches)
+ (*_retval)->SetPrimaryExtension(aFileExt);
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString type;
+ (*_retval)->GetMIMEType(type);
+
+ nsAutoCString ext;
+ (*_retval)->GetPrimaryExtension(ext);
+ LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt,
+ nsACString& aContentType)
+{
+ // OK. We want to try the following sources of mimetype information, in this order:
+ // 1. defaultMimeEntries array
+ // 2. OS-provided information
+ // 3. our "extras" array
+ // 4. Information from plugins
+ // 5. The "ext-to-type-mapping" category
+ // Note that, we are intentionally not looking at the handler service, because
+ // that can be affected by websites, which leads to undesired behavior.
+
+ // Early return if called with an empty extension parameter
+ if (aFileExt.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // First of all, check our default entries
+ for (auto& entry : defaultMimeEntries) {
+ if (aFileExt.LowerCaseEqualsASCII(entry.mFileExtension)) {
+ aContentType = entry.mMimeType;
+ return NS_OK;
+ }
+ }
+
+ // Ask OS.
+ if (GetMIMETypeFromOSForExtension(aFileExt, aContentType)) {
+ return NS_OK;
+ }
+
+ // Check extras array.
+ bool found = GetTypeFromExtras(aFileExt, aContentType);
+ if (found) {
+ return NS_OK;
+ }
+
+ // Try the plugins
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if (pluginHost &&
+ pluginHost->HavePluginForExtension(aFileExt, aContentType)) {
+ return NS_OK;
+ }
+
+ // Let's see if an extension added something
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService("@mozilla.org/categorymanager;1"));
+ if (catMan) {
+ // The extension in the category entry is always stored as lowercase
+ nsAutoCString lowercaseFileExt(aFileExt);
+ ToLowerCase(lowercaseFileExt);
+ // Read the MIME type from the category entry, if available
+ nsXPIDLCString type;
+ nsresult rv = catMan->GetCategoryEntry("ext-to-type-mapping",
+ lowercaseFileExt.get(),
+ getter_Copies(type));
+ if (NS_SUCCEEDED(rv)) {
+ aContentType = type;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
+{
+ NS_ENSURE_ARG(!aMIMEType.IsEmpty());
+
+ nsCOMPtr<nsIMIMEInfo> mi;
+ nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mi->GetPrimaryExtension(_retval);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ aContentType.Truncate();
+
+ // First look for a file to use. If we have one, we just use that.
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
+ if (fileUrl) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = GetTypeFromFile(file, aContentType);
+ if (NS_SUCCEEDED(rv)) {
+ // we got something!
+ return rv;
+ }
+ }
+ }
+
+ // Now try to get an nsIURL so we don't have to do our own parsing
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (url) {
+ nsAutoCString ext;
+ rv = url->GetFileExtension(ext);
+ if (NS_FAILED(rv))
+ return rv;
+ if (ext.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ UnescapeFragment(ext, url, ext);
+
+ return GetTypeFromExtension(ext, aContentType);
+ }
+
+ // no url, let's give the raw spec a shot
+ nsAutoCString specStr;
+ rv = aURI->GetSpec(specStr);
+ if (NS_FAILED(rv))
+ return rv;
+ UnescapeFragment(specStr, aURI, specStr);
+
+ // find the file extension (if any)
+ int32_t extLoc = specStr.RFindChar('.');
+ int32_t specLength = specStr.Length();
+ if (-1 != extLoc &&
+ extLoc != specLength - 1 &&
+ // nothing over 20 chars long can be sanely considered an
+ // extension.... Dat dere would be just data.
+ specLength - extLoc < 20)
+ {
+ return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
+ }
+
+ // We found no information; say so.
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ // Get the Extension
+ nsAutoString fileName;
+ rv = aFile->GetLeafName(fileName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString fileExt;
+ if (!fileName.IsEmpty())
+ {
+ int32_t len = fileName.Length();
+ for (int32_t i = len; i >= 0; i--)
+ {
+ if (fileName[i] == char16_t('.'))
+ {
+ CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
+ break;
+ }
+ }
+ }
+
+ if (fileExt.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ return GetTypeFromExtension(fileExt, aContentType);
+}
+
+nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
+ const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
+{
+ NS_ENSURE_ARG( aMIMEInfo );
+
+ NS_ENSURE_ARG( !aContentType.IsEmpty() );
+
+ // Look for default entry with matching mime type.
+ nsAutoCString MIMEType(aContentType);
+ ToLowerCase(MIMEType);
+ int32_t numEntries = ArrayLength(extraMimeEntries);
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
+ {
+ // This is the one. Set attributes appropriately.
+ aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
+ aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
+ const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
+{
+ nsAutoCString type;
+ bool found = GetTypeFromExtras(aExtension, type);
+ if (!found)
+ return NS_ERROR_NOT_AVAILABLE;
+ return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
+}
+
+bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
+
+ // Look for default entry with matching extension.
+ nsDependentCString::const_iterator start, end, iter;
+ int32_t numEntries = ArrayLength(extraMimeEntries);
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
+ extList.BeginReading(start);
+ extList.EndReading(end);
+ iter = start;
+ while (start != end)
+ {
+ FindCharInReadable(',', iter, end);
+ if (Substring(start, iter).Equals(aExtension,
+ nsCaseInsensitiveCStringComparator()))
+ {
+ aMIMEType = extraMimeEntries[index].mMimeType;
+ return true;
+ }
+ if (iter != end) {
+ ++iter;
+ }
+ start = iter;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsExternalHelperAppService::GetMIMETypeFromOSForExtension(const nsACString& aExtension, nsACString& aMIMEType)
+{
+ bool found = false;
+ nsCOMPtr<nsIMIMEInfo> mimeInfo = GetMIMEInfoFromOS(EmptyCString(), aExtension, &found);
+ return found && mimeInfo && NS_SUCCEEDED(mimeInfo->GetMIMEType(aMIMEType));
+}
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
new file mode 100644
index 000000000..ceec66661
--- /dev/null
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsExternalHelperAppService_h__
+#define nsExternalHelperAppService_h__
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "nsIExternalHelperAppService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIWebProgressListener2.h"
+#include "nsIHelperAppLauncherDialog.h"
+
+#include "nsIMIMEInfo.h"
+#include "nsIMIMEService.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIChannel.h"
+#include "nsITimer.h"
+#include "nsIBackgroundFileSaver.h"
+
+#include "nsIHandlerService.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsCOMArray.h"
+#include "nsWeakReference.h"
+#include "nsIPrompt.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsExternalAppHandler;
+class nsIMIMEInfo;
+class nsITransfer;
+class nsPIDOMWindowOuter;
+
+/**
+ * The helper app service. Responsible for handling content that Mozilla
+ * itself can not handle
+ */
+class nsExternalHelperAppService
+: public nsIExternalHelperAppService,
+ public nsPIExternalAppLauncher,
+ public nsIExternalProtocolService,
+ public nsIMIMEService,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALHELPERAPPSERVICE
+ NS_DECL_NSPIEXTERNALAPPLAUNCHER
+ NS_DECL_NSIEXTERNALPROTOCOLSERVICE
+ NS_DECL_NSIMIMESERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsExternalHelperAppService();
+
+ /**
+ * Initializes internal state. Will be called automatically when
+ * this service is first instantiated.
+ */
+ MOZ_MUST_USE nsresult Init();
+
+ /**
+ * Given a mimetype and an extension, looks up a mime info from the OS.
+ * The mime type is given preference. This function follows the same rules
+ * as nsIMIMEService::GetFromTypeAndExtension.
+ * This is supposed to be overridden by the platform-specific
+ * nsOSHelperAppService!
+ * @param aFileExt The file extension; may be empty. UTF-8 encoded.
+ * @param [out] aFound
+ * Should be set to true if the os has a mapping, to
+ * false otherwise. Must not be null.
+ * @return A MIMEInfo. This function must return a MIMEInfo object if it
+ * can allocate one. The only justifiable reason for not
+ * returning one is an out-of-memory error.
+ * If null, the value of aFound is unspecified.
+ */
+ virtual already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool * aFound) = 0;
+
+ /**
+ * Given a string identifying an application, create an nsIFile representing
+ * it. This function should look in $PATH for the application.
+ * The base class implementation will first try to interpret platformAppPath
+ * as an absolute path, and if that fails it will look for a file next to the
+ * mozilla executable. Subclasses can override this method if they want a
+ * different behaviour.
+ * @param platformAppPath A platform specific path to an application that we
+ * got out of the rdf data source. This can be a mac
+ * file spec, a unix path or a windows path depending
+ * on the platform
+ * @param aFile [out] An nsIFile representation of that platform
+ * application path.
+ */
+ virtual nsresult GetFileTokenForPath(const char16_t * platformAppPath,
+ nsIFile ** aFile);
+
+ virtual nsresult OSProtocolHandlerExists(const char *aScheme,
+ bool *aExists) = 0;
+
+ /**
+ * Given an extension, get a MIME type string. If not overridden by
+ * the OS-specific nsOSHelperAppService, will call into GetMIMEInfoFromOS
+ * with an empty mimetype.
+ * @return true if we successfully found a mimetype.
+ */
+ virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType);
+
+protected:
+ virtual ~nsExternalHelperAppService();
+
+ /**
+ * Searches the "extra" array of MIMEInfo objects for an object
+ * with a specific type. If found, it will modify the passed-in
+ * MIMEInfo. Otherwise, it will return an error and the MIMEInfo
+ * will be untouched.
+ * @param aContentType The type to search for.
+ * @param aMIMEInfo [inout] The mime info, if found
+ */
+ nsresult FillMIMEInfoForMimeTypeFromExtras(
+ const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo);
+ /**
+ * Searches the "extra" array of MIMEInfo objects for an object
+ * with a specific extension.
+ *
+ * Does not change the MIME Type of the MIME Info.
+ *
+ * @see FillMIMEInfoForMimeTypeFromExtras
+ */
+ nsresult FillMIMEInfoForExtensionFromExtras(
+ const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo);
+
+ /**
+ * Searches the "extra" array for a MIME type, and gets its extension.
+ * @param aExtension The extension to search for
+ * @param aMIMEType [out] The found MIME type.
+ * @return true if the extension was found, false otherwise.
+ */
+ bool GetTypeFromExtras(const nsACString& aExtension,
+ nsACString& aMIMEType);
+
+ /**
+ * Logging Module. Usage: set MOZ_LOG=HelperAppService:level, where level
+ * should be 2 for errors, 3 for debug messages from the cross- platform
+ * nsExternalHelperAppService, and 4 for os-specific debug messages.
+ */
+ static mozilla::LazyLogModule mLog;
+
+ // friend, so that it can access the nspr log module.
+ friend class nsExternalAppHandler;
+
+ /**
+ * Helper function for ExpungeTemporaryFiles and ExpungeTemporaryPrivateFiles
+ */
+ static void ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList);
+ /**
+ * Helper function for DeleteTemporaryFileOnExit and DeleteTemporaryPrivateFileWhenPossible
+ */
+ static nsresult DeleteTemporaryFileHelper(nsIFile* aTemporaryFile,
+ nsCOMArray<nsIFile> &aFileList);
+ /**
+ * Functions related to the tempory file cleanup service provided by
+ * nsExternalHelperAppService
+ */
+ void ExpungeTemporaryFiles();
+ /**
+ * Functions related to the tempory file cleanup service provided by
+ * nsExternalHelperAppService (for the temporary files added during
+ * the private browsing mode)
+ */
+ void ExpungeTemporaryPrivateFiles();
+
+ /**
+ * Array for the files that should be deleted
+ */
+ nsCOMArray<nsIFile> mTemporaryFilesList;
+ /**
+ * Array for the files that should be deleted (for the temporary files
+ * added during the private browsing mode)
+ */
+ nsCOMArray<nsIFile> mTemporaryPrivateFilesList;
+
+private:
+ nsresult DoContentContentProcessHelper(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener);
+};
+
+/**
+ * An external app handler is just a small little class that presents itself as
+ * a nsIStreamListener. It saves the incoming data into a temp file. The handler
+ * is bound to an application when it is created. When it receives an
+ * OnStopRequest it launches the application using the temp file it has
+ * stored the data into. We create a handler every time we have to process
+ * data using a helper app.
+ */
+class nsExternalAppHandler final : public nsIStreamListener,
+ public nsIHelperAppLauncher,
+ public nsITimerCallback,
+ public nsIBackgroundFileSaverObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHELPERAPPLAUNCHER
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
+
+ /**
+ * @param aMIMEInfo MIMEInfo object, representing the type of the
+ * content that should be handled
+ * @param aFileExtension The extension we need to append to our temp file,
+ * INCLUDING the ".". e.g. .mp3
+ * @param aContentContext dom Window context, as passed to DoContent.
+ * @param aWindowContext Top level window context used in dialog parenting,
+ * as passed to DoContent. This parameter may be null,
+ * in which case dialogs will be parented to
+ * aContentContext.
+ * @param mExtProtSvc nsExternalHelperAppService on creation
+ * @param aFileName The filename to use
+ * @param aReason A constant from nsIHelperAppLauncherDialog indicating
+ * why the request is handled by a helper app.
+ */
+ nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, const nsCSubstring& aFileExtension,
+ nsIInterfaceRequestor * aContentContext,
+ nsIInterfaceRequestor * aWindowContext,
+ nsExternalHelperAppService * aExtProtSvc,
+ const nsAString& aFilename,
+ uint32_t aReason, bool aForceSave);
+
+ /**
+ * Clean up after the request was diverted to the parent process.
+ */
+ void DidDivertRequest(nsIRequest *request);
+
+ /**
+ * Apply content conversions if needed.
+ */
+ void MaybeApplyDecodingForExtension(nsIRequest *request);
+
+protected:
+ ~nsExternalAppHandler();
+
+ nsIInterfaceRequestor* GetDialogParent() {
+ return mWindowContext ? mWindowContext : mContentContext;
+ }
+
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIURI> mSourceUrl;
+ nsString mTempFileExtension;
+ nsString mTempLeafName;
+
+ /**
+ * The MIME Info for this load. Will never be null.
+ */
+ nsCOMPtr<nsIMIMEInfo> mMimeInfo;
+
+ /**
+ * The dom window associated with this request to handle content.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mContentContext;
+
+ /**
+ * If set, the parent window helper app dialogs and file pickers
+ * should use in parenting. If null, we use mContentContext.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mWindowContext;
+
+ /**
+ * Used to close the window on a timer, to avoid any exceptions that are
+ * thrown if we try to close the window before it's fully loaded.
+ */
+ nsCOMPtr<nsPIDOMWindowOuter> mWindowToClose;
+ nsCOMPtr<nsITimer> mTimer;
+
+ /**
+ * The following field is set if we were processing an http channel that had
+ * a content disposition header which specified the SUGGESTED file name we
+ * should present to the user in the save to disk dialog.
+ */
+ nsString mSuggestedFileName;
+
+ /**
+ * If set, this handler should forcibly save the file to disk regardless of
+ * MIME info settings or anything else, without ever popping up the
+ * unknown content type handling dialog.
+ */
+ bool mForceSave;
+
+ /**
+ * The canceled flag is set if the user canceled the launching of this
+ * application before we finished saving the data to a temp file.
+ */
+ bool mCanceled;
+
+ /**
+ * This is set based on whether the channel indicates that a new window
+ * was opened specifically for this download. If so, then we
+ * close it.
+ */
+ bool mShouldCloseWindow;
+
+ /**
+ * True if a stop request has been issued.
+ */
+ bool mStopRequestIssued;
+
+ bool mIsFileChannel;
+
+ /**
+ * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the
+ * reason the dialog was shown (unknown content type, server requested it,
+ * etc).
+ */
+ uint32_t mReason;
+
+ /**
+ * Track the executable-ness of the temporary file.
+ */
+ bool mTempFileIsExecutable;
+
+ PRTime mTimeDownloadStarted;
+ int64_t mContentLength;
+ int64_t mProgress; /**< Number of bytes received (for sending progress notifications). */
+
+ /**
+ * When we are told to save the temp file to disk (in a more permament
+ * location) before we are done writing the content to a temp file, then
+ * we need to remember the final destination until we are ready to use it.
+ */
+ nsCOMPtr<nsIFile> mFinalFileDestination;
+
+ uint32_t mBufferSize;
+
+ /**
+ * This object handles saving the data received from the network to a
+ * temporary location first, and then move the file to its final location,
+ * doing all the input/output on a background thread.
+ */
+ nsCOMPtr<nsIBackgroundFileSaver> mSaver;
+
+ /**
+ * Stores the SHA-256 hash associated with the file that we downloaded.
+ */
+ nsAutoCString mHash;
+ /**
+ * Stores the signature information of the downloaded file in an nsIArray of
+ * nsIX509CertList of nsIX509Cert. If the file is unsigned this will be
+ * empty.
+ */
+ nsCOMPtr<nsIArray> mSignatureInfo;
+ /**
+ * Stores the redirect information associated with the channel.
+ */
+ nsCOMPtr<nsIArray> mRedirects;
+ /**
+ * Creates the temporary file for the download and an output stream for it.
+ * Upon successful return, both mTempFile and mSaver will be valid.
+ */
+ nsresult SetUpTempFile(nsIChannel * aChannel);
+ /**
+ * When we download a helper app, we are going to retarget all load
+ * notifications into our own docloader and load group instead of
+ * using the window which initiated the load....RetargetLoadNotifications
+ * contains that information...
+ */
+ void RetargetLoadNotifications(nsIRequest *request);
+ /**
+ * Once the user tells us how they want to dispose of the content
+ * create an nsITransfer so they know what's going on. If this fails, the
+ * caller MUST call Cancel.
+ */
+ nsresult CreateTransfer();
+
+ /**
+ * If we fail to create the necessary temporary file to initiate a transfer
+ * we will report the failure by creating a failed nsITransfer.
+ */
+ nsresult CreateFailedTransfer(bool aIsPrivateBrowsing);
+
+ /*
+ * The following two functions are part of the split of SaveToDisk
+ * to make it async, and works as following:
+ *
+ * SaveToDisk -------> RequestSaveDestination
+ * .
+ * .
+ * v
+ * ContinueSave <------- SaveDestinationAvailable
+ */
+
+ /**
+ * This is called by SaveToDisk to decide what's the final
+ * file destination chosen by the user or by auto-download settings.
+ */
+ void RequestSaveDestination(const nsAFlatString &aDefaultFile,
+ const nsAFlatString &aDefaultFileExt);
+
+ /**
+ * When SaveToDisk is called, it possibly delegates to RequestSaveDestination
+ * to decide the file destination. ContinueSave must then be called when
+ * the final destination is finally known.
+ * @param aFile The file that was chosen as the final destination.
+ * Must not be null.
+ */
+ nsresult ContinueSave(nsIFile* aFile);
+
+ /**
+ * After we're done prompting the user for any information, if the original
+ * channel had a refresh url associated with it (which might point to a
+ * "thank you for downloading" kind of page, then process that....It is safe
+ * to invoke this method multiple times. We'll clear mOriginalChannel after
+ * it's called and this ensures we won't call it again....
+ */
+ void ProcessAnyRefreshTags();
+
+ /**
+ * Notify our nsITransfer object that we are done with the download. This is
+ * always called after the target file has been closed.
+ *
+ * @param aStatus
+ * NS_OK for success, or a failure code if the download failed.
+ * A partially downloaded file may still be available in this case.
+ */
+ void NotifyTransfer(nsresult aStatus);
+
+ /**
+ * Helper routine that searches a pref string for a given mime type
+ */
+ bool GetNeverAskFlagFromPref(const char * prefName, const char * aContentType);
+
+ /**
+ * Helper routine to ensure mSuggestedFileName is "correct";
+ * this ensures that mTempFileExtension only contains an extension when it
+ * is different from mSuggestedFileName's extension.
+ */
+ void EnsureSuggestedFileName();
+
+ typedef enum { kReadError, kWriteError, kLaunchError } ErrorType;
+ /**
+ * Utility function to send proper error notification to web progress listener
+ */
+ void SendStatusChange(ErrorType type, nsresult aStatus, nsIRequest *aRequest, const nsAFlatString &path);
+
+ /**
+ * Closes the window context if it does not have a refresh header
+ * and it never displayed content before the external helper app
+ * service was invoked.
+ */
+ nsresult MaybeCloseWindow();
+
+ /**
+ * Set in nsHelperDlgApp.js. This is always null after the user has chosen an
+ * action.
+ */
+ nsCOMPtr<nsIWebProgressListener2> mDialogProgressListener;
+ /**
+ * Set once the user has chosen an action. This is null after the download
+ * has been canceled or completes.
+ */
+ nsCOMPtr<nsITransfer> mTransfer;
+
+ nsCOMPtr<nsIChannel> mOriginalChannel; /**< in the case of a redirect, this will be the pre-redirect channel. */
+ nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
+
+ /**
+ * Keep request alive in case when helper non-modal dialog shown.
+ * Thus in OnStopRequest the mRequest will not be set to null (it will be set to null further).
+ */
+ bool mKeepRequestAlive;
+
+ /**
+ * The request that's being loaded. Initialized in OnStartRequest.
+ * Nulled out in OnStopRequest or once we know what we're doing
+ * with the data, whichever happens later.
+ */
+ nsCOMPtr<nsIRequest> mRequest;
+
+ RefPtr<nsExternalHelperAppService> mExtProtSvc;
+};
+
+#endif // nsExternalHelperAppService_h__
diff --git a/uriloader/exthandler/nsExternalProtocolHandler.cpp b/uriloader/exthandler/nsExternalProtocolHandler.cpp
new file mode 100644
index 000000000..5b3d07b4c
--- /dev/null
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -0,0 +1,561 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sts=2 sw=2 et cin:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsExternalProtocolHandler.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrompt.h"
+#include "nsNetUtil.h"
+#include "nsContentSecurityManager.h"
+#include "nsExternalHelperAppService.h"
+
+// used to dispatch urls to default protocol handlers
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIChildChannel.h"
+#include "nsIParentChannel.h"
+
+class nsILoadInfo;
+
+////////////////////////////////////////////////////////////////////////
+// a stub channel implemenation which will map calls to AsyncRead and OpenInputStream
+// to calls in the OS for loading the url.
+////////////////////////////////////////////////////////////////////////
+
+class nsExtProtocolChannel : public nsIChannel,
+ public nsIChildChannel,
+ public nsIParentChannel
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIPARENTCHANNEL
+
+ nsExtProtocolChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+private:
+ virtual ~nsExtProtocolChannel();
+
+ nsresult OpenURL();
+ void Finish(nsresult aResult);
+
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsresult mStatus;
+ nsLoadFlags mLoadFlags;
+ bool mWasOpened;
+ // Set true (as a result of ConnectParent invoked from child process)
+ // when this channel is on the parent process and is being used as
+ // a redirect target channel. It turns AsyncOpen into a no-op since
+ // we do it on the child.
+ bool mConnectedParent;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+};
+
+NS_IMPL_ADDREF(nsExtProtocolChannel)
+NS_IMPL_RELEASE(nsExtProtocolChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsExtProtocolChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsExtProtocolChannel::nsExtProtocolChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo)
+ : mUrl(aURI)
+ , mOriginalURI(aURI)
+ , mStatus(NS_OK)
+ , mLoadFlags(nsIRequest::LOAD_NORMAL)
+ , mWasOpened(false)
+ , mConnectedParent(false)
+ , mLoadInfo(aLoadInfo)
+{
+}
+
+nsExtProtocolChannel::~nsExtProtocolChannel()
+{}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExtProtocolChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ NS_ADDREF(*aURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = mUrl;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+nsresult nsExtProtocolChannel::OpenURL()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIExternalProtocolService> extProtService (do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+
+ if (extProtService)
+ {
+#ifdef DEBUG
+ nsAutoCString urlScheme;
+ mUrl->GetScheme(urlScheme);
+ bool haveHandler = false;
+ extProtService->ExternalProtocolHandlerExists(urlScheme.get(), &haveHandler);
+ NS_ASSERTION(haveHandler, "Why do we have a channel for this url if we don't support the protocol?");
+#endif
+
+ nsCOMPtr<nsIInterfaceRequestor> aggCallbacks;
+ rv = NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(aggCallbacks));
+ if (NS_FAILED(rv)) {
+ goto finish;
+ }
+
+ rv = extProtService->LoadURI(mUrl, aggCallbacks);
+ if (NS_SUCCEEDED(rv)) {
+ // despite success, we need to abort this channel, at the very least
+ // to make it clear to the caller that no on{Start,Stop}Request
+ // should be expected.
+ rv = NS_ERROR_NO_CONTENT;
+ }
+ }
+
+finish:
+ mCallbacks = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval)
+{
+ return OpenURL();
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ if (mConnectedParent) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mWasOpened = true;
+
+ return OpenURL();
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentType(nsACString &aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentType(const nsACString &aContentType)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentLength(int64_t * aContentLength)
+{
+ *aContentLength = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExtProtocolChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_NOTREACHED("SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetOwner(nsISupports * *aPrincipal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetOwner(nsISupports * aPrincipal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result)
+{
+ return mUrl->GetSpec(result);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::IsPending(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetStatus(nsresult *status)
+{
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Suspend()
+{
+ NS_NOTREACHED("Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Resume()
+{
+ NS_NOTREACHED("Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+///////////////////////////////////////////////////////////////////////
+// From nsIChildChannel
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::ConnectParent(uint32_t registrarId)
+{
+ mozilla::dom::ContentChild::GetSingleton()->
+ SendExtProtocolChannelConnectParent(registrarId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *context)
+{
+ // For redirects to external protocols we AsyncOpen on the child
+ // (not the parent) because child channel has the right docshell
+ // (which is needed for the select dialog).
+ return AsyncOpen(listener, context);
+}
+
+///////////////////////////////////////////////////////////////////////
+// From nsIParentChannel (derives from nsIStreamListener)
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // This is called as part of the connect parent operation from
+ // ContentParent::RecvExtProtocolChannelConnectParent. Setting
+ // this flag tells this channel to not proceed and makes AsyncOpen
+ // just no-op. Actual operation will happen from the child process
+ // via CompleteRedirectSetup call on the child channel.
+ mConnectedParent = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingProtectionDisabled()
+{
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Delete()
+{
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+///////////////////////////////////////////////////////////////////////
+// the default protocol handler implementation
+//////////////////////////////////////////////////////////////////////
+
+nsExternalProtocolHandler::nsExternalProtocolHandler()
+{
+ m_schemeName = "default";
+}
+
+
+nsExternalProtocolHandler::~nsExternalProtocolHandler()
+{}
+
+NS_IMPL_ADDREF(nsExternalProtocolHandler)
+NS_IMPL_RELEASE(nsExternalProtocolHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsIExternalProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme = m_schemeName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ *aDefaultPort = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+// returns TRUE if the OS can handle this protocol scheme and false otherwise.
+bool nsExternalProtocolHandler::HaveExternalProtocolHandler(nsIURI * aURI)
+{
+ MOZ_ASSERT(aURI);
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+
+ nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+ if (!extProtSvc) {
+ return false;
+ }
+
+ bool haveHandler = false;
+ extProtSvc->ExternalProtocolHandlerExists(scheme.get(), &haveHandler);
+ return haveHandler;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetProtocolFlags(uint32_t *aUritype)
+{
+ // Make it norelative since it is a simple uri
+ *aUritype = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE |
+ URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uri->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_retval = uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetval)
+{
+ NS_ENSURE_TRUE(aURI, NS_ERROR_UNKNOWN_PROTOCOL);
+ NS_ENSURE_TRUE(aRetval, NS_ERROR_UNKNOWN_PROTOCOL);
+
+ // Only try to return a channel if we have a protocol handler for the url.
+ // nsOSHelperAppService::LoadUriInternal relies on this to check trustedness
+ // for some platforms at least. (win uses ::ShellExecute and unix uses
+ // gnome_url_show.)
+ if (!HaveExternalProtocolHandler(aURI)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ nsCOMPtr<nsIChannel> channel = new nsExtProtocolChannel(aURI, aLoadInfo);
+ channel.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+///////////////////////////////////////////////////////////////////////
+// External protocol handler interface implementation
+//////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP nsExternalProtocolHandler::ExternalAppExistsForScheme(const nsACString& aScheme, bool *_retval)
+{
+ nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+ if (extProtSvc)
+ return extProtSvc->ExternalProtocolHandlerExists(
+ PromiseFlatCString(aScheme).get(), _retval);
+
+ // In case we don't have external protocol service.
+ *_retval = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/nsExternalProtocolHandler.h b/uriloader/exthandler/nsExternalProtocolHandler.h
new file mode 100644
index 000000000..426c98da1
--- /dev/null
+++ b/uriloader/exthandler/nsExternalProtocolHandler.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsExternalProtocolHandler_h___
+#define nsExternalProtocolHandler_h___
+
+#include "nsIExternalProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsIExternalProtocolService.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+
+// protocol handlers need to support weak references if we want the netlib nsIOService to cache them.
+class nsExternalProtocolHandler final : public nsIExternalProtocolHandler, public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIEXTERNALPROTOCOLHANDLER
+
+ nsExternalProtocolHandler();
+
+protected:
+ ~nsExternalProtocolHandler();
+
+ // helper function
+ bool HaveExternalProtocolHandler(nsIURI * aURI);
+ nsCString m_schemeName;
+};
+
+#endif // nsExternalProtocolHandler_h___
+
diff --git a/uriloader/exthandler/nsHandlerService.js b/uriloader/exthandler/nsHandlerService.js
new file mode 100644
index 000000000..c932f9f5d
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService.js
@@ -0,0 +1,1429 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+
+const CLASS_MIMEINFO = "mimetype";
+const CLASS_PROTOCOLINFO = "scheme";
+
+
+// namespace prefix
+const NC_NS = "http://home.netscape.com/NC-rdf#";
+
+// the most recent default handlers that have been injected. Note that
+// this is used to construct an RDF resource, which needs to have NC_NS
+// prepended, since that hasn't been done yet
+const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion";
+
+// type list properties
+
+const NC_MIME_TYPES = NC_NS + "MIME-types";
+const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes";
+
+// content type ("type") properties
+
+// nsIHandlerInfo::type
+const NC_VALUE = NC_NS + "value";
+const NC_DESCRIPTION = NC_NS + "description";
+
+// additional extensions
+const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions";
+
+// references nsIHandlerInfo record
+const NC_HANDLER_INFO = NC_NS + "handlerProp";
+
+// handler info ("info") properties
+
+// nsIHandlerInfo::preferredAction
+const NC_SAVE_TO_DISK = NC_NS + "saveToDisk";
+const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal";
+const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault";
+
+// nsIHandlerInfo::alwaysAskBeforeHandling
+const NC_ALWAYS_ASK = NC_NS + "alwaysAsk";
+
+// references nsIHandlerApp records
+const NC_PREFERRED_APP = NC_NS + "externalApplication";
+const NC_POSSIBLE_APP = NC_NS + "possibleApplication";
+
+// handler app ("handler") properties
+
+// nsIHandlerApp::name
+const NC_PRETTY_NAME = NC_NS + "prettyName";
+
+// nsILocalHandlerApp::executable
+const NC_PATH = NC_NS + "path";
+
+// nsIWebHandlerApp::uriTemplate
+const NC_URI_TEMPLATE = NC_NS + "uriTemplate";
+
+// nsIDBusHandlerApp::service
+const NC_SERVICE = NC_NS + "service";
+
+// nsIDBusHandlerApp::method
+const NC_METHOD = NC_NS + "method";
+
+// nsIDBusHandlerApp::objectPath
+const NC_OBJPATH = NC_NS + "objectPath";
+
+// nsIDBusHandlerApp::dbusInterface
+const NC_INTERFACE = NC_NS + "dBusInterface";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+function HandlerService() {
+ this._init();
+}
+
+const HandlerServiceFactory = {
+ _instance: null,
+ createInstance: function (outer, iid) {
+ if (this._instance)
+ return this._instance;
+
+ let processType = Cc["@mozilla.org/xre/runtime;1"].
+ getService(Ci.nsIXULRuntime).processType;
+ if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
+ return Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ return (this._instance = new HandlerService());
+ }
+};
+
+HandlerService.prototype = {
+ //**************************************************************************//
+ // XPCOM Plumbing
+
+ classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]),
+ _xpcom_factory: HandlerServiceFactory,
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ _init: function HS__init() {
+ // Observe profile-before-change so we can switch to the datasource
+ // in the new profile when the user changes profiles.
+ this._observerSvc.addObserver(this, "profile-before-change", false);
+
+ // Observe xpcom-shutdown so we can remove these observers
+ // when the application shuts down.
+ this._observerSvc.addObserver(this, "xpcom-shutdown", false);
+
+ // Observe profile-do-change so that non-default profiles get upgraded too
+ this._observerSvc.addObserver(this, "profile-do-change", false);
+
+ // do any necessary updating of the datastore
+ this._updateDB();
+ },
+
+ _updateDB: function HS__updateDB() {
+ try {
+ var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
+ } catch(ex) {
+ // accessing the datastore failed, we can't update anything
+ return;
+ }
+
+ try {
+ // if we don't have the current version of the default prefs for
+ // this locale, inject any new default handers into the datastore
+ if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) {
+
+ // set the new version first so that if we recurse we don't
+ // call _injectNewDefaults several times
+ this._datastoreDefaultHandlersVersion =
+ this._prefsDefaultHandlersVersion;
+ this._injectNewDefaults();
+ }
+ } catch (ex) {
+ // if injecting the defaults failed, set the version back to the
+ // previous value
+ this._datastoreDefaultHandlersVersion = defaultHandlersVersion;
+ }
+ },
+
+ get _currentLocale() {
+ var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry);
+ var currentLocale = chromeRegistry.getSelectedLocale("global");
+ return currentLocale;
+ },
+
+ _destroy: function HS__destroy() {
+ this._observerSvc.removeObserver(this, "profile-before-change");
+ this._observerSvc.removeObserver(this, "xpcom-shutdown");
+ this._observerSvc.removeObserver(this, "profile-do-change");
+
+ // XXX Should we also null references to all the services that get stored
+ // by our memoizing getters in the Convenience Getters section?
+ },
+
+ _onProfileChange: function HS__onProfileChange() {
+ // Lose our reference to the datasource so we reacquire it
+ // from the new profile the next time we need it.
+ this.__ds = null;
+ },
+
+ _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) {
+ var enumerator = aArray.enumerate();
+ while (enumerator.hasMoreElements()) {
+ let handler = enumerator.getNext();
+ handler.QueryInterface(Ci.nsIHandlerApp);
+ if (handler.equals(aHandler))
+ return true;
+ }
+
+ return false;
+ },
+
+ // note that this applies to the current locale only
+ get _datastoreDefaultHandlersVersion() {
+ var version = this._getValue("urn:root", NC_NS + this._currentLocale +
+ "_" + DEFAULT_HANDLERS_VERSION);
+
+ return version ? version : -1;
+ },
+
+ set _datastoreDefaultHandlersVersion(aNewVersion) {
+ return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" +
+ DEFAULT_HANDLERS_VERSION, aNewVersion);
+ },
+
+ get _prefsDefaultHandlersVersion() {
+ // get handler service pref branch
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService.");
+
+ // get the version of the preferences for this locale
+ return Number(handlerSvcBranch.
+ getComplexValue("defaultHandlersVersion",
+ Ci.nsIPrefLocalizedString).data);
+ },
+
+ _injectNewDefaults: function HS__injectNewDefaults() {
+ // get handler service pref branch
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+
+ let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes.");
+ let schemePrefList = schemesPrefBranch.getChildList("");
+
+ var schemes = {};
+
+ // read all the scheme prefs into a hash
+ for (var schemePrefName of schemePrefList) {
+
+ let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
+
+ try {
+ var attrData =
+ schemesPrefBranch.getComplexValue(schemePrefName,
+ Ci.nsIPrefLocalizedString).data;
+ if (!(scheme in schemes))
+ schemes[scheme] = {};
+
+ if (!(handlerNumber in schemes[scheme]))
+ schemes[scheme][handlerNumber] = {};
+
+ schemes[scheme][handlerNumber][attribute] = attrData;
+ } catch (ex) {}
+ }
+
+ let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ for (var scheme in schemes) {
+
+ // This clause is essentially a reimplementation of
+ // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
+ // Necessary because calling that from here would make XPConnect barf
+ // when getService tried to re-enter the constructor for this
+ // service.
+ let osDefaultHandlerFound = {};
+ let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme,
+ osDefaultHandlerFound);
+
+ if (this.exists(protoInfo))
+ this.fillHandlerInfo(protoInfo, null);
+ else
+ protoSvc.setProtocolHandlerDefaults(protoInfo,
+ osDefaultHandlerFound.value);
+
+ // cache the possible handlers to avoid extra xpconnect traversals.
+ let possibleHandlers = protoInfo.possibleApplicationHandlers;
+
+ for (let handlerNumber in schemes[scheme]) {
+ let handlerPrefs = schemes[scheme][handlerNumber];
+ let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+
+ handlerApp.uriTemplate = handlerPrefs.uriTemplate;
+ handlerApp.name = handlerPrefs.name;
+
+ if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
+ possibleHandlers.appendElement(handlerApp, false);
+ }
+ }
+
+ this.store(protoInfo);
+ }
+ },
+
+ //**************************************************************************//
+ // nsIObserver
+
+ observe: function HS__observe(subject, topic, data) {
+ switch(topic) {
+ case "profile-before-change":
+ this._onProfileChange();
+ break;
+ case "xpcom-shutdown":
+ this._destroy();
+ break;
+ case "profile-do-change":
+ this._updateDB();
+ break;
+ }
+ },
+
+
+ //**************************************************************************//
+ // nsIHandlerService
+
+ enumerate: function HS_enumerate() {
+ var handlers = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._appendHandlers(handlers, CLASS_MIMEINFO);
+ this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
+ return handlers.enumerate();
+ },
+
+ fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
+ var type = aOverrideType || aHandlerInfo.type;
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);
+
+ // Determine whether or not information about this handler is available
+ // in the datastore by looking for its "value" property, which stores its
+ // type and should always be present.
+ if (!this._hasValue(typeID, NC_VALUE))
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ // Retrieve the human-readable description of the type.
+ if (this._hasValue(typeID, NC_DESCRIPTION))
+ aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION);
+
+ // Note: for historical reasons, we don't actually check that the type
+ // record has a "handlerProp" property referencing the info record. It's
+ // unclear whether or not we should start doing this check; perhaps some
+ // legacy datasources don't have such references.
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), type);
+
+ aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID);
+
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), type);
+
+ // Retrieve the preferred handler.
+ // Note: for historical reasons, we don't actually check that the info
+ // record has an "externalApplication" property referencing the preferred
+ // handler record. It's unclear whether or not we should start doing
+ // this check; perhaps some legacy datasources don't have such references.
+ aHandlerInfo.preferredApplicationHandler =
+ this._retrieveHandlerApp(preferredHandlerID);
+
+ // Fill the array of possible handlers with the ones in the datastore.
+ this._fillPossibleHandlers(infoID,
+ aHandlerInfo.possibleApplicationHandlers,
+ aHandlerInfo.preferredApplicationHandler);
+
+ // If we have an "always ask" flag stored in the RDF, always use its
+ // value. Otherwise, use the default value stored in the pref service.
+ var alwaysAsk;
+ if (this._hasValue(infoID, NC_ALWAYS_ASK)) {
+ alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false");
+ } else {
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefBranch = prefSvc.getBranch("network.protocol-handler.");
+ try {
+ alwaysAsk = prefBranch.getBoolPref("warn-external." + type);
+ } catch (e) {
+ // will throw if pref didn't exist.
+ try {
+ alwaysAsk = prefBranch.getBoolPref("warn-external-default");
+ } catch (e) {
+ // Nothing to tell us what to do, so be paranoid and prompt.
+ alwaysAsk = true;
+ }
+ }
+ }
+ aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk;
+
+ // If the object represents a MIME type handler, then also retrieve
+ // any file extensions.
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
+ for (let fileExtension of this._retrieveFileExtensions(typeID))
+ aHandlerInfo.appendExtension(fileExtension);
+ },
+
+ store: function HS_store(aHandlerInfo) {
+ // FIXME: when we switch from RDF to something with transactions (like
+ // SQLite), enclose the following changes in a transaction so they all
+ // get rolled back if any of them fail and we don't leave the datastore
+ // in an inconsistent state.
+
+ this._ensureRecordsForType(aHandlerInfo);
+ this._storePreferredAction(aHandlerInfo);
+ this._storePreferredHandler(aHandlerInfo);
+ this._storePossibleHandlers(aHandlerInfo);
+ this._storeAlwaysAsk(aHandlerInfo);
+ this._storeExtensions(aHandlerInfo);
+
+ // Write the changes to the database immediately so we don't lose them
+ // if the application crashes.
+ if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+ this._ds.Flush();
+ },
+
+ exists: function HS_exists(aHandlerInfo) {
+ var found;
+
+ try {
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type);
+ } catch (e) {
+ // If the RDF threw (eg, corrupt file), treat as nonexistent.
+ found = false;
+ }
+
+ return found;
+ },
+
+ remove: function HS_remove(aHandlerInfo) {
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._removeAssertions(preferredHandlerID);
+
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ // Get a list of possible handlers. After we have removed the info record,
+ // we'll check if any other info records reference these handlers, and we'll
+ // remove the handler records that aren't referenced by other info records.
+ var possibleHandlerIDs = [];
+ var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
+ while (possibleHandlerTargets.hasMoreElements()) {
+ let possibleHandlerTarget = possibleHandlerTargets.getNext();
+ // Note: possibleHandlerTarget should always be an nsIRDFResource.
+ // The conditional is just here in case of a corrupt RDF datasource.
+ if (possibleHandlerTarget instanceof Ci.nsIRDFResource)
+ possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8);
+ }
+
+ // Remove the info record.
+ this._removeAssertions(infoID);
+
+ // Now that we've removed the info record, remove any possible handlers
+ // that aren't referenced by other info records.
+ for (let possibleHandlerID of possibleHandlerIDs)
+ if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID))
+ this._removeAssertions(possibleHandlerID);
+
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._removeAssertions(typeID);
+
+ // Now that there's no longer a handler for this type, remove the type
+ // from the list of types for which there are known handlers.
+ var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
+ var type = this._rdf.GetResource(typeID);
+ var typeIndex = typeList.IndexOf(type);
+ if (typeIndex != -1)
+ typeList.RemoveElementAt(typeIndex, true);
+
+ // Write the changes to the database immediately so we don't lose them
+ // if the application crashes.
+ // XXX If we're removing a bunch of handlers at once, will flushing
+ // after every removal cause a significant performance hit?
+ if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+ this._ds.Flush();
+ },
+
+ getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) {
+ var fileExtension = aFileExtension.toLowerCase();
+ var typeID;
+
+ // See bug 1100069 for why we want to fail gracefully and silently here.
+ try {
+ this._ds;
+ } catch (ex) {
+ Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE;
+ return;
+ }
+
+ if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension))
+ typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension);
+
+ if (typeID && this._hasValue(typeID, NC_VALUE)) {
+ let type = this._getValue(typeID, NC_VALUE);
+ if (type == "")
+ throw Cr.NS_ERROR_FAILURE;
+ return type;
+ }
+
+ return "";
+ },
+
+
+ //**************************************************************************//
+ // Retrieval Methods
+
+ /**
+ * Retrieve the preferred action for the info record with the given ID.
+ *
+ * @param aInfoID {string} the info record ID
+ *
+ * @returns {integer} the preferred action enumeration value
+ */
+ _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) {
+ if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true")
+ return Ci.nsIHandlerInfo.saveToDisk;
+
+ if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true")
+ return Ci.nsIHandlerInfo.useSystemDefault;
+
+ if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true")
+ return Ci.nsIHandlerInfo.handleInternally;
+
+ return Ci.nsIHandlerInfo.useHelperApp;
+ },
+
+ /**
+ * Fill an array of possible handlers with the handlers for the given info ID.
+ *
+ * @param aInfoID {string} the ID of the info record
+ * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers
+ * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any
+ */
+ _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID,
+ aPossibleHandlers,
+ aPreferredHandler) {
+ // The set of possible handlers should include the preferred handler,
+ // but legacy datastores (from before we added possible handlers) won't
+ // include the preferred handler, so check if it's included as we build
+ // the list of handlers, and, if it's not included, add it to the list.
+ if (aPreferredHandler)
+ aPossibleHandlers.appendElement(aPreferredHandler, false);
+
+ var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP);
+
+ while (possibleHandlerTargets.hasMoreElements()) {
+ let possibleHandlerTarget = possibleHandlerTargets.getNext();
+ if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource))
+ continue;
+
+ let possibleHandlerID = possibleHandlerTarget.ValueUTF8;
+ let possibleHandler = this._retrieveHandlerApp(possibleHandlerID);
+ if (possibleHandler && (!aPreferredHandler ||
+ !possibleHandler.equals(aPreferredHandler)))
+ aPossibleHandlers.appendElement(possibleHandler, false);
+ }
+ },
+
+ /**
+ * Retrieve the handler app object with the given ID.
+ *
+ * @param aHandlerAppID {string} the ID of the handler app to retrieve
+ *
+ * @returns {nsIHandlerApp} the handler app, if any; otherwise null
+ */
+ _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) {
+ var handlerApp;
+
+ // If it has a path, it's a local handler; otherwise, it's a web handler.
+ if (this._hasValue(aHandlerAppID, NC_PATH)) {
+ let executable =
+ this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH));
+ if (!executable)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = executable;
+ }
+ else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) {
+ let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE);
+ if (!uriTemplate)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handlerApp.uriTemplate = uriTemplate;
+ }
+ else if (this._hasValue(aHandlerAppID, NC_SERVICE)) {
+ let service = this._getValue(aHandlerAppID, NC_SERVICE);
+ if (!service)
+ return null;
+
+ let method = this._getValue(aHandlerAppID, NC_METHOD);
+ if (!method)
+ return null;
+
+ let objpath = this._getValue(aHandlerAppID, NC_OBJPATH);
+ if (!objpath)
+ return null;
+
+ let iface = this._getValue(aHandlerAppID, NC_INTERFACE);
+ if (!iface)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
+ createInstance(Ci.nsIDBusHandlerApp);
+ handlerApp.service = service;
+ handlerApp.method = method;
+ handlerApp.objectPath = objpath;
+ handlerApp.dBusInterface = iface;
+
+ }
+ else
+ return null;
+
+ handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME);
+
+ return handlerApp;
+ },
+
+ /*
+ * Retrieve file extensions, if any, for the MIME type with the given type ID.
+ *
+ * @param aTypeID {string} the type record ID
+ */
+ _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) {
+ var fileExtensions = [];
+
+ var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS);
+
+ while (fileExtensionTargets.hasMoreElements()) {
+ let fileExtensionTarget = fileExtensionTargets.getNext();
+ if (fileExtensionTarget instanceof Ci.nsIRDFLiteral &&
+ fileExtensionTarget.Value != "")
+ fileExtensions.push(fileExtensionTarget.Value);
+ }
+
+ return fileExtensions;
+ },
+
+ /**
+ * Get the file with the given path. This is not as simple as merely
+ * initializing a local file object with the path, because the path might be
+ * relative to the current process directory, in which case we have to
+ * construct a path starting from that directory.
+ *
+ * @param aPath {string} a path to a file
+ *
+ * @returns {nsILocalFile} the file, or null if the file does not exist
+ */
+ _getFileWithPath: function HS__getFileWithPath(aPath) {
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+
+ try {
+ file.initWithPath(aPath);
+
+ if (file.exists())
+ return file;
+ }
+ catch(ex) {
+ // Note: for historical reasons, we don't actually check to see
+ // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what
+ // nsILocalFile::initWithPath throws when a path is relative.
+
+ file = this._dirSvc.get("XCurProcD", Ci.nsIFile);
+
+ try {
+ file.append(aPath);
+ if (file.exists())
+ return file;
+ }
+ catch(ex) {}
+ }
+
+ return null;
+ },
+
+
+ //**************************************************************************//
+ // Storage Methods
+
+ _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ switch(aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ this._setLiteral(infoID, NC_SAVE_TO_DISK, "true");
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true");
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true");
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ break;
+
+ // This value is indicated in the datastore either by the absence of
+ // the three properties or by setting them all "false". Of these two
+ // options, the former seems preferable, because it reduces the size
+ // of the RDF file and thus the amount of stuff we have to parse.
+ case Ci.nsIHandlerInfo.useHelperApp:
+ default:
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+ }
+ },
+
+ _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var handlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ var handler = aHandlerInfo.preferredApplicationHandler;
+
+ if (handler) {
+ this._storeHandlerApp(handlerID, handler);
+
+ // Make this app be the preferred app for the handler info.
+ //
+ // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
+ // this setting and instead identifies the preferred app as the resource
+ // whose URI follows the pattern urn:<class>:externalApplication:<type>.
+ // But the old downloadactions.js code used to set this property, so just
+ // in case there is still some code somewhere that relies on its presence,
+ // we set it here.
+ this._setResource(infoID, NC_PREFERRED_APP, handlerID);
+ }
+ else {
+ // There isn't a preferred handler. Remove the existing record for it,
+ // if any.
+ this._removeTarget(infoID, NC_PREFERRED_APP);
+ this._removeAssertions(handlerID);
+ }
+ },
+
+ /**
+ * Store the list of possible handler apps for the content type represented
+ * by the given handler info object.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the handler info object
+ */
+ _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ // First, retrieve the set of handler apps currently stored for the type,
+ // keeping track of their IDs in a hash that we'll use to determine which
+ // ones are no longer valid and should be removed.
+ var currentHandlerApps = {};
+ var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
+ while (currentHandlerTargets.hasMoreElements()) {
+ let handlerApp = currentHandlerTargets.getNext();
+ if (handlerApp instanceof Ci.nsIRDFResource) {
+ let handlerAppID = handlerApp.ValueUTF8;
+ currentHandlerApps[handlerAppID] = true;
+ }
+ }
+
+ // Next, store any new handler apps.
+ var newHandlerApps =
+ aHandlerInfo.possibleApplicationHandlers.enumerate();
+ while (newHandlerApps.hasMoreElements()) {
+ let handlerApp =
+ newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
+ let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
+ if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
+ this._storeHandlerApp(handlerAppID, handlerApp);
+ this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
+ }
+ delete currentHandlerApps[handlerAppID];
+ }
+
+ // Finally, remove any old handler apps that aren't being used anymore,
+ // and if those handler apps aren't being used by any other type either,
+ // then completely remove their record from the datastore so we don't
+ // leave it clogged up with information about handler apps we don't care
+ // about anymore.
+ for (let handlerAppID in currentHandlerApps) {
+ this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
+ if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID))
+ this._removeAssertions(handlerAppID);
+ }
+ },
+
+ /**
+ * Store the given handler app.
+ *
+ * Note: the reason this method takes the ID of the handler app in a param
+ * is that the ID is different than it usually is when the handler app
+ * in question is a preferred handler app, so this method can't just derive
+ * the ID of the handler app by calling _getPossibleHandlerAppID, its callers
+ * have to do that for it.
+ *
+ * @param aHandlerAppID {string} the ID of the handler app to store
+ * @param aHandlerApp {nsIHandlerApp} the handler app to store
+ */
+ _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) {
+ aHandlerApp.QueryInterface(Ci.nsIHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name);
+
+ // In the case of the preferred handler, the handler ID could have been
+ // used to refer to a different kind of handler in the past (i.e. either
+ // a local hander or a web handler), so if the new handler is a local
+ // handler, then we remove any web handler properties and vice versa.
+ // This is unnecessary but harmless for possible handlers.
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp) {
+ this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path);
+ this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
+ this._removeTarget(aHandlerAppID, NC_METHOD);
+ this._removeTarget(aHandlerAppID, NC_SERVICE);
+ this._removeTarget(aHandlerAppID, NC_OBJPATH);
+ this._removeTarget(aHandlerAppID, NC_INTERFACE);
+ }
+ else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate);
+ this._removeTarget(aHandlerAppID, NC_PATH);
+ this._removeTarget(aHandlerAppID, NC_METHOD);
+ this._removeTarget(aHandlerAppID, NC_SERVICE);
+ this._removeTarget(aHandlerAppID, NC_OBJPATH);
+ this._removeTarget(aHandlerAppID, NC_INTERFACE);
+ }
+ else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service);
+ this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method);
+ this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath);
+ this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface);
+ this._removeTarget(aHandlerAppID, NC_PATH);
+ this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
+ }
+ else {
+ throw "unknown handler type";
+ }
+
+ },
+
+ _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(infoID,
+ NC_ALWAYS_ASK,
+ aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
+ },
+
+ _storeExtensions: function HS__storeExtensions(aHandlerInfo) {
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo) {
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var extEnum = aHandlerInfo.getFileExtensions();
+ while (extEnum.hasMore()) {
+ let ext = extEnum.getNext().toLowerCase();
+ if (!this._hasLiteralAssertion(typeID, NC_FILE_EXTENSIONS, ext)) {
+ this._setLiteral(typeID, NC_FILE_EXTENSIONS, ext);
+ }
+ }
+ }
+ },
+
+
+ //**************************************************************************//
+ // Convenience Getters
+
+ // Observer Service
+ __observerSvc: null,
+ get _observerSvc() {
+ if (!this.__observerSvc)
+ this.__observerSvc =
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ return this.__observerSvc;
+ },
+
+ // Directory Service
+ __dirSvc: null,
+ get _dirSvc() {
+ if (!this.__dirSvc)
+ this.__dirSvc =
+ Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ return this.__dirSvc;
+ },
+
+ // MIME Service
+ __mimeSvc: null,
+ get _mimeSvc() {
+ if (!this.__mimeSvc)
+ this.__mimeSvc =
+ Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+ return this.__mimeSvc;
+ },
+
+ // Protocol Service
+ __protocolSvc: null,
+ get _protocolSvc() {
+ if (!this.__protocolSvc)
+ this.__protocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ return this.__protocolSvc;
+ },
+
+ // RDF Service
+ __rdf: null,
+ get _rdf() {
+ if (!this.__rdf)
+ this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
+ return this.__rdf;
+ },
+
+ // RDF Container Utils
+ __containerUtils: null,
+ get _containerUtils() {
+ if (!this.__containerUtils)
+ this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"].
+ getService(Ci.nsIRDFContainerUtils);
+ return this.__containerUtils;
+ },
+
+ // RDF datasource containing content handling config (i.e. mimeTypes.rdf)
+ __ds: null,
+ get _ds() {
+ if (!this.__ds) {
+ var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ // FIXME: make this a memoizing getter if we use it anywhere else.
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var fileHandler = ioService.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ this.__ds =
+ this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
+ }
+
+ return this.__ds;
+ },
+
+
+ //**************************************************************************//
+ // Datastore Utils
+
+ /**
+ * Get the string identifying whether this is a MIME or a protocol handler.
+ * This string is used in the URI IDs of various RDF properties.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
+ *
+ * @returns {string} the class
+ */
+ _getClass: function HS__getClass(aHandlerInfo) {
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
+ return CLASS_MIMEINFO;
+ else
+ return CLASS_PROTOCOLINFO;
+ },
+
+ /**
+ * Return the unique identifier for a content type record, which stores
+ * the value field plus a reference to the content type's handler info record.
+ *
+ * |urn:<class>:<type>|
+ *
+ * XXX: should this be a property of nsIHandlerInfo?
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getTypeID: function HS__getTypeID(aClass, aType) {
+ return "urn:" + aClass + ":" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a handler info record, which stores
+ * the preferredAction and alwaysAsk fields plus a reference to the preferred
+ * handler app. Roughly equivalent to the nsIHandlerInfo interface.
+ *
+ * |urn:<class>:handler:<type>|
+ *
+ * FIXME: the type info record should be merged into the type record,
+ * since there's a one to one relationship between them, and this record
+ * merely stores additional attributes of a content type.
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getInfoID: function HS__getInfoID(aClass, aType) {
+ return "urn:" + aClass + ":handler:" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a preferred handler record, which stores
+ * information about the preferred handler for a given content type, including
+ * its human-readable name and the path to its executable (for a local app)
+ * or its URI template (for a web app).
+ *
+ * |urn:<class>:externalApplication:<type>|
+ *
+ * XXX: should this be a property of nsIHandlerApp?
+ *
+ * FIXME: this should be an arbitrary ID, and we should retrieve it from
+ * the datastore for a given content type via the NC:ExternalApplication
+ * property rather than looking for a specific ID, so a handler doesn't
+ * have to change IDs when it goes from being a possible handler to being
+ * the preferred one (once we support possible handlers).
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) {
+ return "urn:" + aClass + ":externalApplication:" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a handler app record, which stores
+ * information about a possible handler for one or more content types,
+ * including its human-readable name and the path to its executable (for a
+ * local app) or its URI template (for a web app).
+ *
+ * Note: handler app IDs for preferred handlers are different. For those,
+ * see the _getPreferredHandlerID method.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app object
+ */
+ _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) {
+ var handlerAppID = "urn:handler:";
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ handlerAppID += "local:" + aHandlerApp.executable.path;
+ else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
+ handlerAppID += "web:" + aHandlerApp.uriTemplate;
+ }
+ else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
+ handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate;
+ }else{
+ throw "unknown handler type";
+ }
+
+ return handlerAppID;
+ },
+
+ /**
+ * Get the list of types for the given class, creating the list if it doesn't
+ * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
+ * (i.e. the result of a call to _getClass).
+ *
+ * |urn:<class>s|
+ * |urn:<class>s:root|
+ *
+ * @param aClass {string} the class for which to retrieve a list of types
+ *
+ * @returns {nsIRDFContainer} the list of types
+ */
+ _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
+ var source = this._rdf.GetResource("urn:" + aClass + "s");
+ var property =
+ this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
+ : NC_PROTOCOL_SCHEMES);
+ var target = this._rdf.GetResource("urn:" + aClass + "s:root");
+
+ // Make sure we have an arc from the source to the target.
+ if (!this._ds.HasAssertion(source, property, target, true))
+ this._ds.Assert(source, property, target, true);
+
+ // Make sure the target is a container.
+ if (!this._containerUtils.IsContainer(this._ds, target))
+ this._containerUtils.MakeSeq(this._ds, target);
+
+ // Get the type list as an RDF container.
+ var typeList = Cc["@mozilla.org/rdf/container;1"].
+ createInstance(Ci.nsIRDFContainer);
+ typeList.Init(this._ds, target);
+
+ return typeList;
+ },
+
+ /**
+ * Make sure there are records in the datasource for the given content type
+ * by creating them if they don't already exist. We have to do this before
+ * storing any specific data, because we can't assume the presence
+ * of the records (the nsIHandlerInfo object might have been created
+ * from the OS), and the records have to all be there in order for the helper
+ * app service to properly construct an nsIHandlerInfo object for the type.
+ *
+ * Based on old downloadactions.js::_ensureMIMERegistryEntry.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record
+ */
+ _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) {
+ // Get the list of types.
+ var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
+
+ // If there's already a record in the datastore for this type, then we
+ // don't need to do anything more.
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var type = this._rdf.GetResource(typeID);
+ if (typeList.IndexOf(type) != -1)
+ return;
+
+ // Create a basic type record for this type.
+ typeList.AppendElement(type);
+ this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
+
+ // Create a basic info record for this type.
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
+ this._setResource(typeID, NC_HANDLER_INFO, infoID);
+ // XXX Shouldn't we set preferredAction to useSystemDefault?
+ // That's what it is if there's no record in the datastore; why should it
+ // change to useHelperApp just because we add a record to the datastore?
+
+ // Create a basic preferred handler record for this type.
+ // XXX Not sure this is necessary, since preferred handlers are optional,
+ // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem
+ // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
+ // used to create it, so we'll do the same.
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(preferredHandlerID, NC_PATH, "");
+ this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
+ },
+
+ /**
+ * Append known handlers of the given class to the given array. The class
+ * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
+ *
+ * @param aHandlers {array} the array of handlers to append to
+ * @param aClass {string} the class for which to append handlers
+ */
+ _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
+ var typeList = this._ensureAndGetTypeList(aClass);
+ var enumerator = typeList.GetElements();
+
+ while (enumerator.hasMoreElements()) {
+ var element = enumerator.getNext();
+
+ // This should never happen. If it does, that means our datasource
+ // is corrupted with type list entries that point to literal values
+ // instead of resources. If it does happen, let's just do our best
+ // to recover by ignoring this entry and moving on to the next one.
+ if (!(element instanceof Ci.nsIRDFResource))
+ continue;
+
+ // Get the value of the element's NC:value property, which contains
+ // the MIME type or scheme for which we're retrieving a handler info.
+ var type = this._getValue(element.ValueUTF8, NC_VALUE);
+ if (!type)
+ continue;
+
+ var handler;
+ if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root")
+ handler = this._mimeSvc.getFromTypeAndExtension(type, null);
+ else
+ handler = this._protocolSvc.getProtocolHandlerInfo(type);
+
+ aHandlers.appendElement(handler, false);
+ }
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @returns {boolean} whether or not the property has a value
+ */
+ _hasValue: function HS__hasValue(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ return this._ds.hasArcOut(source, property);
+ },
+
+ /**
+ * Get the value of a property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @returns {string} the value of the property
+ */
+ _getValue: function HS__getValue(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ var target = this._ds.GetTarget(source, property, true);
+
+ if (!target)
+ return null;
+
+ if (target instanceof Ci.nsIRDFResource)
+ return target.ValueUTF8;
+
+ if (target instanceof Ci.nsIRDFLiteral)
+ return target.Value;
+
+ return null;
+ },
+
+ /**
+ * Get all targets for the property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ *
+ * @returns {nsISimpleEnumerator} an enumerator of targets
+ */
+ _getTargets: function HS__getTargets(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ return this._ds.GetTargets(source, property, true);
+ },
+
+ /**
+ * Set a property of an RDF source to a literal value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ */
+ _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ this._setTarget(source, property, target);
+ },
+
+ /**
+ * Set a property of an RDF source to a resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._setTarget(source, property, target);
+ },
+
+ /**
+ * Assert an arc into the RDF datasource if there is no arc with the given
+ * source and property; otherwise, if there is already an existing arc,
+ * change it to point to the given target. _setLiteral and _setResource
+ * call this after converting their string arguments into resources
+ * and literals, and most callers should call one of those two methods
+ * instead of this one.
+ *
+ * @param source {nsIRDFResource} the source
+ * @param property {nsIRDFResource} the property
+ * @param target {nsIRDFNode} the target
+ */
+ _setTarget: function HS__setTarget(source, property, target) {
+ if (this._ds.hasArcOut(source, property)) {
+ var oldTarget = this._ds.GetTarget(source, property, true);
+ this._ds.Change(source, property, oldTarget, target);
+ }
+ else
+ this._ds.Assert(source, property, target, true);
+ },
+
+ /**
+ * Assert that a property of an RDF source has a resource target.
+ *
+ * The difference between this method and _setResource is that this one adds
+ * an assertion even if one already exists, which allows its callers to make
+ * sets of assertions (i.e. to set a property to multiple targets).
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _addResourceAssertion: function HS__addResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._ds.Assert(source, property, target, true);
+ },
+
+ /**
+ * Remove an assertion with a resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._ds.Unassert(source, property, target);
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a given resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ *
+ * @returns {boolean} whether or not there is such an assertion
+ */
+ _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ return this._ds.HasAssertion(source, property, target, true);
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a given literal value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ *
+ * @returns {boolean} whether or not there is such an assertion
+ */
+ _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI,
+ propertyURI,
+ value) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ return this._ds.HasAssertion(source, property, target, true);
+ },
+
+ /**
+ * Whether or not there is an RDF source that has the given property set to
+ * the given literal value.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ *
+ * @returns {boolean} whether or not there is a source
+ */
+ _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ return this._ds.hasArcIn(target, property);
+ },
+
+ /**
+ * Get the source for a property set to a given literal value.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ */
+ _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ var source = this._ds.GetSource(property, target, true);
+ if (source)
+ return source.ValueUTF8;
+
+ return null;
+ },
+
+ /**
+ * Whether or not there is an RDF source that has the given property set to
+ * the given resource target.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ *
+ * @returns {boolean} whether or not there is a source
+ */
+ _existsResourceTarget: function HS__existsResourceTarget(propertyURI,
+ targetURI) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ return this._ds.hasArcIn(target, property);
+ },
+
+ /**
+ * Remove a property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ */
+ _removeTarget: function HS__removeTarget(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ if (this._ds.hasArcOut(source, property)) {
+ var target = this._ds.GetTarget(source, property, true);
+ this._ds.Unassert(source, property, target);
+ }
+ },
+
+ /**
+ * Remove all assertions about a given RDF source.
+ *
+ * Note: not recursive. If some assertions point to other resources,
+ * and you want to remove assertions about those resources too, you need
+ * to do so manually.
+ *
+ * @param sourceURI {string} the URI of the source
+ */
+ _removeAssertions: function HS__removeAssertions(sourceURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var properties = this._ds.ArcLabelsOut(source);
+
+ while (properties.hasMoreElements()) {
+ let property = properties.getNext();
+ let targets = this._ds.GetTargets(source, property, true);
+ while (targets.hasMoreElements()) {
+ let target = targets.getNext();
+ this._ds.Unassert(source, property, target);
+ }
+ }
+ }
+
+};
+
+//****************************************************************************//
+// More XPCOM Plumbing
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);
diff --git a/uriloader/exthandler/nsHandlerService.manifest b/uriloader/exthandler/nsHandlerService.manifest
new file mode 100644
index 000000000..5ed86c798
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService.manifest
@@ -0,0 +1,2 @@
+component {32314cc8-22f7-4f7f-a645-1a45453ba6a6} nsHandlerService.js
+contract @mozilla.org/uriloader/handler-service;1 {32314cc8-22f7-4f7f-a645-1a45453ba6a6} process=main \ No newline at end of file
diff --git a/uriloader/exthandler/nsIContentDispatchChooser.idl b/uriloader/exthandler/nsIContentDispatchChooser.idl
new file mode 100644
index 000000000..7c186deee
--- /dev/null
+++ b/uriloader/exthandler/nsIContentDispatchChooser.idl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHandlerInfo;
+interface nsIHelperAppLauncher;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+/**
+ * This is used to ask a user what they would like to do with a given piece of
+ * content.
+ */
+[scriptable, uuid(456ca3b2-02be-4f97-89a2-08c08d3ad88f)]
+interface nsIContentDispatchChooser : nsISupports {
+ /**
+ * This request is passed to the helper app dialog because Gecko can not
+ * handle content of this type.
+ */
+ const unsigned long REASON_CANNOT_HANDLE = 0;
+
+ /**
+ * Asks the user what to do with the content.
+ *
+ * @param aHander
+ * The interface describing the details of how this content should or
+ * can be handled.
+ * @param aWindowContext
+ * The parent window context to show this chooser. This can be null,
+ * and some implementations may not care about it. Generally, you'll
+ * want to pass an nsIDOMWindow in so the chooser can be properly
+ * parented when opened.
+ * @param aURI
+ * The URI of the resource that we are asking about.
+ * @param aReason
+ * The reason why we are asking (see above).
+ */
+ void ask(in nsIHandlerInfo aHandler,
+ in nsIInterfaceRequestor aWindowContext,
+ in nsIURI aURI,
+ in unsigned long aReason);
+};
+
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
new file mode 100644
index 000000000..bfdfff5ce
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICancelable.idl"
+
+interface nsIURI;
+interface nsIRequest;
+interface nsIStreamListener;
+interface nsIFile;
+interface nsIMIMEInfo;
+interface nsIWebProgressListener2;
+interface nsIInterfaceRequestor;
+
+/**
+ * The external helper app service is used for finding and launching
+ * platform specific external applications for a given mime content type.
+ */
+[scriptable, uuid(1E4F3AE1-B737-431F-A95D-31FA8DA70199)]
+interface nsIExternalHelperAppService : nsISupports
+{
+ /**
+ * Binds an external helper application to a stream listener. The caller
+ * should pump data into the returned stream listener. When the OnStopRequest
+ * is issued, the stream listener implementation will launch the helper app
+ * with this data.
+ * @param aMimeContentType The content type of the incoming data
+ * @param aRequest The request corresponding to the incoming data
+ * @param aContentContext Used in processing content document refresh
+ * headers after target content is downloaded. Note in e10s land
+ * this is likely a CPOW that points to a window in the child process.
+ * @param aForceSave True to always save this content to disk, regardless of
+ * nsIMIMEInfo and other such influences.
+ * @param aWindowContext Used in parenting helper app dialogs, usually
+ * points to the parent browser window. This parameter may be null,
+ * in which case dialogs will be parented to aContentContext.
+ * @return A nsIStreamListener which the caller should pump the data into.
+ */
+ nsIStreamListener doContent (in ACString aMimeContentType,
+ in nsIRequest aRequest,
+ in nsIInterfaceRequestor aContentContext,
+ in boolean aForceSave,
+ [optional] in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Returns true if data from a URL with this extension combination
+ * is to be decoded from aEncodingType prior to saving or passing
+ * off to helper apps, false otherwise.
+ */
+ boolean applyDecodingForExtension(in AUTF8String aExtension,
+ in ACString aEncodingType);
+
+};
+
+/**
+ * This is a private interface shared between external app handlers and the platform specific
+ * external helper app service
+ */
+[scriptable, uuid(6613e2e7-feab-4e3a-bb1f-b03200d544ec)]
+interface nsPIExternalAppLauncher : nsISupports
+{
+ /**
+ * mscott --> eventually I should move this into a new service so other
+ * consumers can add temporary files they want deleted on exit.
+ * @param aTemporaryFile A temporary file we should delete on exit.
+ */
+ void deleteTemporaryFileOnExit(in nsIFile aTemporaryFile);
+ /**
+ * Delete a temporary file created inside private browsing mode when
+ * the private browsing mode has ended.
+ */
+ void deleteTemporaryPrivateFileWhenPossible(in nsIFile aTemporaryFile);
+};
+
+/**
+ * A helper app launcher is a small object created to handle the launching
+ * of an external application.
+ *
+ * Note that cancelling the load via the nsICancelable interface will release
+ * the reference to the launcher dialog.
+ */
+[scriptable, uuid(acf2a516-7d7f-4771-8b22-6c4a559c088e)]
+interface nsIHelperAppLauncher : nsICancelable
+{
+ /**
+ * The mime info object associated with the content type this helper app
+ * launcher is currently attempting to load
+ */
+ readonly attribute nsIMIMEInfo MIMEInfo;
+
+ /**
+ * The source uri
+ */
+ readonly attribute nsIURI source;
+
+ /**
+ * The suggested name for this file
+ */
+ readonly attribute AString suggestedFileName;
+
+ /**
+ * Saves the final destination of the file. Does not actually perform the
+ * save.
+ * NOTE: This will release the reference to the
+ * nsIHelperAppLauncherDialog.
+ */
+ void saveToDisk(in nsIFile aNewFileLocation, in boolean aRememberThisPreference);
+
+ /**
+ * Remembers that aApplication should be used to launch this content. Does
+ * not actually launch the application.
+ * NOTE: This will release the reference to the nsIHelperAppLauncherDialog.
+ * @param aApplication nsIFile corresponding to the location of the application to use.
+ * @param aRememberThisPreference TRUE if we should remember this choice.
+ */
+ void launchWithApplication(in nsIFile aApplication, in boolean aRememberThisPreference);
+
+ /**
+ * Callback invoked by nsIHelperAppLauncherDialog::promptForSaveToFileAsync
+ * after the user has chosen a file through the File Picker (or dismissed it).
+ * @param aFile The file that was chosen by the user (or null if dialog was dismissed).
+ */
+ void saveDestinationAvailable(in nsIFile aFile);
+
+ /**
+ * The following methods are used by the progress dialog to get or set
+ * information on the current helper app launcher download.
+ * This reference will be released when the download is finished (after the
+ * listener receives the STATE_STOP notification).
+ */
+ void setWebProgressListener(in nsIWebProgressListener2 aWebProgressListener);
+
+ /**
+ * The file we are saving to
+ */
+ readonly attribute nsIFile targetFile;
+
+ /**
+ * The executable-ness of the target file
+ */
+ readonly attribute boolean targetFileIsExecutable;
+
+ /**
+ * Time when the download started
+ */
+ readonly attribute PRTime timeDownloadStarted;
+
+ /**
+ * The download content length, or -1 if the length is not available.
+ */
+ readonly attribute int64_t contentLength;
+};
diff --git a/uriloader/exthandler/nsIExternalProtocolService.idl b/uriloader/exthandler/nsIExternalProtocolService.idl
new file mode 100644
index 000000000..44d756030
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalProtocolService.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIInterfaceRequestor;
+interface nsIHandlerInfo;
+
+/**
+ * The external protocol service is used for finding and launching
+ * web handlers (a la registerProtocolHandler in the HTML5 draft) or
+ * platform-specific applications for handling particular protocols.
+ *
+ * You can ask the external protocol service if it has an external
+ * handler for a given protocol scheme. And you can ask it to load
+ * the url using the default handler.
+ */
+[scriptable, uuid(70f93b7a-3ec6-4bcb-b093-92d9984c9f83)]
+interface nsIExternalProtocolService : nsISupports
+{
+ /**
+ * Check whether a handler for a specific protocol exists. Specifically,
+ * this looks to see whether there are any known possible application handlers
+ * in either the nsIHandlerService datastore or registered with the OS.
+ *
+ * @param aProtocolScheme The scheme from a url: http, ftp, mailto, etc.
+ *
+ * @return true if we have a handler and false otherwise.
+ *
+ * XXX shouldn't aProtocolScheme be an ACString like nsIURI::scheme?
+ */
+ boolean externalProtocolHandlerExists(in string aProtocolScheme);
+
+ /**
+ * Check whether a handler for a specific protocol is "exposed" as a visible
+ * feature of the current application.
+ *
+ * An exposed protocol handler is one that can be used in all contexts. A
+ * non-exposed protocol handler is one that can only be used internally by the
+ * application. For example, a non-exposed protocol would not be loaded by the
+ * application in response to a link click or a X-remote openURL command.
+ * Instead, it would be deferred to the system's external protocol handler.
+ * XXX shouldn't aProtocolScheme be an ACString like nsIURI::scheme?
+ */
+ boolean isExposedProtocol(in string aProtocolScheme);
+
+ /**
+ * Retrieve the handler for the given protocol. If neither the application
+ * nor the OS knows about a handler for the protocol, the object this method
+ * returns will represent a default handler for unknown content.
+ *
+ * @param aProtocolScheme the scheme from a URL: http, ftp, mailto, etc.
+ *
+ * Note: aProtocolScheme should not include a trailing colon, which is part
+ * of the URI syntax, not part of the scheme itself (i.e. pass "mailto" not
+ * "mailto:").
+ *
+ * @return the handler, if any; otherwise a default handler
+ */
+ nsIHandlerInfo getProtocolHandlerInfo(in ACString aProtocolScheme);
+
+ /**
+ * Given a scheme, looks up the protocol info from the OS. This should be
+ * overridden by each OS's implementation.
+ *
+ * @param aScheme The protocol scheme we are looking for.
+ * @param aFound Was an OS default handler for this scheme found?
+ * @return An nsIHanderInfo for the protocol.
+ */
+ nsIHandlerInfo getProtocolHandlerInfoFromOS(in ACString aProtocolScheme,
+ out boolean aFound);
+
+ /**
+ * Set some sane defaults for a protocol handler object.
+ *
+ * @param aHandlerInfo nsIHandlerInfo object, as returned by
+ * getProtocolHandlerInfoFromOS
+ * @param aOSHandlerExists was the object above created for an extant
+ * OS default handler? This is generally the
+ * value of the aFound out param from
+ * getProtocolHandlerInfoFromOS.
+ */
+ void setProtocolHandlerDefaults(in nsIHandlerInfo aHandlerInfo,
+ in boolean aOSHandlerExists);
+
+ /**
+ * Used to load a url via an external protocol handler (if one exists)
+ *
+ * @param aURL The url to load
+ *
+ * @deprecated Use LoadURI instead (See Bug 389565 for removal)
+ */
+ [deprecated] void loadUrl(in nsIURI aURL);
+
+ /**
+ * Used to load a URI via an external application. Might prompt the user for
+ * permission to load the external application.
+ *
+ * @param aURI
+ * The URI to load
+ *
+ * @param aWindowContext
+ * The window to parent the dialog against, and, if a web handler
+ * is chosen, it is loaded in this window as well. This parameter
+ * may be ultimately passed nsIURILoader.openURI in the case of a
+ * web handler, and aWindowContext is null or not present, web
+ * handlers will fail. We need to do better than that; bug 394483
+ * filed in order to track.
+ *
+ * @note Embedders that do not expose the http protocol should not currently
+ * use web-based protocol handlers, as handoff won't work correctly
+ * (bug 394479).
+ */
+ void loadURI(in nsIURI aURI,
+ [optional] in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Gets a human-readable description for the application responsible for
+ * handling a specific protocol.
+ *
+ * @param aScheme The scheme to look up. For example, "mms".
+ *
+ * @throw NS_ERROR_NOT_IMPLEMENTED
+ * If getting descriptions for protocol helpers is not supported
+ * @throw NS_ERROR_NOT_AVAILABLE
+ * If no protocol helper exists for this scheme, or if it is not
+ * possible to get a description for it.
+ */
+ AString getApplicationDescription(in AUTF8String aScheme);
+};
diff --git a/uriloader/exthandler/nsIExternalSharingAppService.idl b/uriloader/exthandler/nsIExternalSharingAppService.idl
new file mode 100644
index 000000000..f58f6981d
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalSharingAppService.idl
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMIMEInfo.idl"
+
+%{C++
+#define NS_EXTERNALSHARINGAPPSERVICE_CONTRACTID "@mozilla.org/uriloader/external-sharing-app-service;1"
+%}
+
+
+[scriptable, uuid(7111f769-53ec-41fd-b314-613661d5b6ba)]
+interface nsISharingHandlerApp : nsIHandlerApp
+{
+ void share(in AString data, [optional] in AString title);
+};
+
+[scriptable, uuid(cf7d04e5-3892-482e-81bb-073dc1c83f76)]
+interface nsIExternalSharingAppService : nsISupports {
+ void shareWithDefault(in AString data, in AString mime,
+ [optional] in AString title);
+
+ void getSharingApps(in AString aMIMEType,
+ [optional] out unsigned long aLen,
+ [array, size_is(aLen), retval] out nsISharingHandlerApp handlerApps);
+};
+
+
diff --git a/uriloader/exthandler/nsIExternalURLHandlerService.idl b/uriloader/exthandler/nsIExternalURLHandlerService.idl
new file mode 100644
index 000000000..357349759
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalURLHandlerService.idl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMIMEInfo.idl"
+
+/**
+ * The external URL handler service is used for finding
+ * platform-specific applications for handling particular URLs.
+ */
+
+[scriptable, uuid(56c5c7d3-6fd3-43f8-9429-4397e111453a)]
+interface nsIExternalURLHandlerService : nsISupports
+{
+ /**
+ * Given a URL, looks up the handler info from the OS. This should be
+ * overridden by each OS's implementation.
+ *
+ * @param aURL The URL we are looking for.
+ * @param aFound Was an OS default handler for this URL found?
+ * @return An nsIHanderInfo for the protocol.
+ */
+ nsIHandlerInfo getURLHandlerInfoFromOS(in nsIURI aURL,
+ out boolean aFound);
+
+};
diff --git a/uriloader/exthandler/nsIHandlerService.idl b/uriloader/exthandler/nsIHandlerService.idl
new file mode 100644
index 000000000..efea43937
--- /dev/null
+++ b/uriloader/exthandler/nsIHandlerService.idl
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHandlerInfo;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(53f0ad17-ec62-46a1-adbc-efccc06babcd)]
+interface nsIHandlerService : nsISupports
+{
+ /**
+ * Retrieve a list of all handlers in the datastore. This list is not
+ * guaranteed to be in any particular order, and callers should not assume
+ * it will remain in the same order in the future.
+ *
+ * @returns a list of all handlers in the datastore
+ */
+ nsISimpleEnumerator enumerate();
+
+ /**
+ * Fill a handler info object with information from the datastore.
+ *
+ * Note: because of the way the external helper app service currently mixes
+ * OS and user handler info in the same handler info object, this method
+ * takes an existing handler info object (probably retrieved from the OS)
+ * and "fills it in" with information from the datastore, overriding any
+ * existing properties on the object with properties from the datastore.
+ *
+ * Ultimately, however, we're going to separate OS and user handler info
+ * into separate objects, at which point this method should be renamed to
+ * something like "get" or "retrieve", take a class and type (or perhaps
+ * a type whose class can be determined by querying the type, for example
+ * an nsIContentType which is also an nsIMIMEType or an nsIProtocolScheme),
+ * and return a handler info object representing only the user info.
+ *
+ * Note: if you specify an override type, then the service will fill in
+ * the handler info object with information about that type instead of
+ * the type specified by the object's nsIHandlerInfo::type attribute.
+ *
+ * This is useful when you are trying to retrieve information about a MIME
+ * type that doesn't exist in the datastore, but you have a file extension
+ * for that type, and nsIHandlerService::getTypeFromExtension returns another
+ * MIME type that does exist in the datastore and can handle that extension.
+ *
+ * For example, the user clicks on a link, and the content has a MIME type
+ * that isn't in the datastore, but the link has a file extension, and that
+ * extension is associated with another MIME type in the datastore (perhaps
+ * an unofficial MIME type preceded an official one, like with image/x-png
+ * and image/png).
+ *
+ * In that situation, you can call this method to fill in the handler info
+ * object with information about that other type by passing the other type
+ * as the aOverrideType parameter.
+ *
+ * @param aHandlerInfo the handler info object
+ * @param aOverrideType a type to use instead of aHandlerInfo::type
+ *
+ * Note: if there is no information in the datastore about this type,
+ * this method throws NS_ERROR_NOT_AVAILABLE. Callers are encouraged to
+ * check exists() before calling fillHandlerInfo(), to prevent spamming the
+ * console with XPCOM exception errors.
+ */
+ void fillHandlerInfo(in nsIHandlerInfo aHandlerInfo,
+ in ACString aOverrideType);
+
+ /**
+ * Save the preferred action, preferred handler, possible handlers, and
+ * always ask properties of the given handler info object to the datastore.
+ * Updates an existing record or creates a new one if necessary.
+ *
+ * Note: if preferred action is undefined or invalid, then we assume
+ * the default value nsIHandlerInfo::useHelperApp.
+ *
+ * @param aHandlerInfo the handler info object
+ */
+ void store(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Whether or not a record for the given handler info object exists
+ * in the datastore. If the datastore is corrupt (or some other error
+ * is caught in the implementation), false will be returned.
+ *
+ * @param aHandlerInfo a handler info object
+ *
+ * @returns whether or not a record exists
+ */
+ boolean exists(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Remove the given handler info object from the datastore. Deletes all
+ * records associated with the object, including the preferred handler, info,
+ * and type records plus the entry in the list of types, if they exist.
+ * Otherwise, it does nothing and does not return an error.
+ *
+ * @param aHandlerInfo the handler info object
+ */
+ void remove(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Get the MIME type mapped to the given file extension in the datastore.
+ *
+ * XXX If we ever support extension -> protocol scheme mappings, then this
+ * method should work for those as well.
+ *
+ * Note: in general, you should use nsIMIMEService::getTypeFromExtension
+ * to get a MIME type from a file extension, as that method checks a variety
+ * of other sources besides just the datastore. Use this only when you want
+ * to specifically get only the mapping available in the datastore.
+ *
+ * @param aFileExtension the file extension
+ *
+ * @returns the MIME type, if any; otherwise returns an empty string ("").
+ */
+ ACString getTypeFromExtension(in ACString aFileExtension);
+};
diff --git a/uriloader/exthandler/nsIHelperAppLauncherDialog.idl b/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
new file mode 100644
index 000000000..f8190e744
--- /dev/null
+++ b/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHelperAppLauncher;
+interface nsIFile;
+
+/**
+ * This interface is used to display a confirmation dialog before
+ * launching a "helper app" to handle content not handled by
+ * Mozilla.
+ *
+ * Usage: Clients (of which there is one: the nsIExternalHelperAppService
+ * implementation in mozilla/uriloader/exthandler) create an instance of
+ * this interface (using the contract ID) and then call the show() method.
+ *
+ * The dialog is shown non-modally. The implementation of the dialog
+ * will access methods of the nsIHelperAppLauncher passed in to show()
+ * in order to cause a "save to disk" or "open using" action.
+ */
+[scriptable, uuid(bfc739f3-8d75-4034-a6f8-1039a5996bad)]
+interface nsIHelperAppLauncherDialog : nsISupports {
+ /**
+ * This request is passed to the helper app dialog because Gecko can not
+ * handle content of this type.
+ */
+ const unsigned long REASON_CANTHANDLE = 0;
+
+ /**
+ * The server requested external handling.
+ */
+ const unsigned long REASON_SERVERREQUEST = 1;
+
+ /**
+ * Gecko detected that the type sent by the server (e.g. text/plain) does
+ * not match the actual type.
+ */
+ const unsigned long REASON_TYPESNIFFED = 2;
+
+ /**
+ * Show confirmation dialog for launching application (or "save to
+ * disk") for content specified by aLauncher.
+ *
+ * @param aLauncher
+ * A nsIHelperAppLauncher to be invoked when a file is selected.
+ * @param aWindowContext
+ * Window associated with action.
+ * @param aReason
+ * One of the constants from above. It indicates why the dialog is
+ * shown. Implementors should treat unknown reasons like
+ * REASON_CANTHANDLE.
+ */
+ void show(in nsIHelperAppLauncher aLauncher,
+ in nsISupports aWindowContext,
+ in unsigned long aReason);
+
+ /**
+ * Async invoke a save-to-file dialog instead of the full fledged helper app
+ * dialog. When the file is chosen (or the dialog is closed), the callback
+ * in aLauncher (aLauncher.saveDestinationAvailable) is called with the
+ * selected file.
+ *
+ * @param aLauncher
+ * A nsIHelperAppLauncher to be invoked when a file is selected.
+ * @param aWindowContext
+ * Window associated with action.
+ * @param aDefaultFileName
+ * Default file name to provide (can be null)
+ * @param aSuggestedFileExtension
+ * Sugested file extension
+ * @param aForcePrompt
+ * Set to true to force prompting the user for thet file
+ * name/location, otherwise perferences may control if the user is
+ * prompted.
+ */
+ void promptForSaveToFileAsync(in nsIHelperAppLauncher aLauncher,
+ in nsISupports aWindowContext,
+ in wstring aDefaultFileName,
+ in wstring aSuggestedFileExtension,
+ in boolean aForcePrompt);
+};
+
+
+%{C++
+#define NS_HELPERAPPLAUNCHERDLG_CONTRACTID "@mozilla.org/helperapplauncherdialog;1"
+%}
diff --git a/uriloader/exthandler/nsLocalHandlerApp.cpp b/uriloader/exthandler/nsLocalHandlerApp.cpp
new file mode 100644
index 000000000..f1b65dca2
--- /dev/null
+++ b/uriloader/exthandler/nsLocalHandlerApp.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsLocalHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIProcess.h"
+
+// XXX why does nsMIMEInfoImpl have a threadsafe nsISupports? do we need one
+// here too?
+NS_IMPL_ISUPPORTS(nsLocalHandlerApp, nsILocalHandlerApp, nsIHandlerApp)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerApp
+
+NS_IMETHODIMP nsLocalHandlerApp::GetName(nsAString& aName)
+{
+ if (mName.IsEmpty() && mExecutable) {
+ // Don't want to cache this, just in case someone resets the app
+ // without changing the description....
+ mExecutable->GetLeafName(aName);
+ } else {
+ aName.Assign(mName);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDetailedDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetDetailedDescription(nsAString& aDescription)
+{
+ aDescription.Assign(mDetailedDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aHandlerApp);
+
+ *_retval = false;
+
+ // If the handler app isn't a local handler app, then it's not the same app.
+ nsCOMPtr <nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp);
+ if (!localHandlerApp)
+ return NS_OK;
+
+ // If either handler app doesn't have an executable, then they aren't
+ // the same app.
+ nsCOMPtr<nsIFile> executable;
+ nsresult rv = localHandlerApp->GetExecutable(getter_AddRefs(executable));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Equality for two empty nsIHandlerApp
+ if (!executable && !mExecutable) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ // At least one is set so they are not equal
+ if (!mExecutable || !executable)
+ return NS_OK;
+
+ // Check the command line parameter list lengths
+ uint32_t len;
+ localHandlerApp->GetParameterCount(&len);
+ if (mParameters.Length() != len)
+ return NS_OK;
+
+ // Check the command line params lists
+ for (uint32_t idx = 0; idx < mParameters.Length(); idx++) {
+ nsAutoString param;
+ if (NS_FAILED(localHandlerApp->GetParameter(idx, param)) ||
+ !param.Equals(mParameters[idx]))
+ return NS_OK;
+ }
+
+ return executable->Equals(mExecutable, _retval);
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ // pass the entire URI to the handler.
+ nsAutoCString spec;
+ aURI->GetAsciiSpec(spec);
+ return LaunchWithIProcess(spec);
+}
+
+nsresult
+nsLocalHandlerApp::LaunchWithIProcess(const nsCString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (NS_FAILED(rv = process->Init(mExecutable)))
+ return rv;
+
+ const char *string = aArg.get();
+
+ return process->Run(false, &string, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsILocalHandlerApp
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetExecutable(nsIFile **aExecutable)
+{
+ NS_IF_ADDREF(*aExecutable = mExecutable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::SetExecutable(nsIFile *aExecutable)
+{
+ mExecutable = aExecutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetParameterCount(uint32_t *aParameterCount)
+{
+ *aParameterCount = mParameters.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::ClearParameters()
+{
+ mParameters.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::AppendParameter(const nsAString & aParam)
+{
+ mParameters.AppendElement(aParam);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetParameter(uint32_t parameterIndex, nsAString & _retval)
+{
+ if (mParameters.Length() <= parameterIndex)
+ return NS_ERROR_INVALID_ARG;
+
+ _retval.Assign(mParameters[parameterIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::ParameterExists(const nsAString & aParam, bool *_retval)
+{
+ *_retval = mParameters.Contains(aParam);
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/nsLocalHandlerApp.h b/uriloader/exthandler/nsLocalHandlerApp.h
new file mode 100644
index 000000000..30b410ce3
--- /dev/null
+++ b/uriloader/exthandler/nsLocalHandlerApp.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsLocalHandlerAppImpl_h__
+#define __nsLocalHandlerAppImpl_h__
+
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+
+class nsLocalHandlerApp : public nsILocalHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSILOCALHANDLERAPP
+
+ nsLocalHandlerApp() { }
+
+ nsLocalHandlerApp(const char16_t *aName, nsIFile *aExecutable)
+ : mName(aName), mExecutable(aExecutable) { }
+
+ nsLocalHandlerApp(const nsAString & aName, nsIFile *aExecutable)
+ : mName(aName), mExecutable(aExecutable) { }
+
+protected:
+ virtual ~nsLocalHandlerApp() { }
+
+ nsString mName;
+ nsString mDetailedDescription;
+ nsTArray<nsString> mParameters;
+ nsCOMPtr<nsIFile> mExecutable;
+
+ /**
+ * Launches this application with a single argument (typically either
+ * a file path or a URI spec). This is meant as a helper method for
+ * implementations of (e.g.) LaunchWithURI.
+ *
+ * @param aApp The application to launch (may not be null)
+ * @param aArg The argument to pass on the command line
+ */
+ nsresult LaunchWithIProcess(const nsCString &aArg);
+};
+
+// any platforms that need a platform-specific class instead of just
+// using nsLocalHandlerApp need to add an include and a typedef here.
+#ifdef XP_MACOSX
+# ifndef NSLOCALHANDLERAPPMAC_H_
+# include "mac/nsLocalHandlerAppMac.h"
+typedef nsLocalHandlerAppMac PlatformLocalHandlerApp_t;
+# endif
+#else
+typedef nsLocalHandlerApp PlatformLocalHandlerApp_t;
+#endif
+
+#endif // __nsLocalHandlerAppImpl_h__
diff --git a/uriloader/exthandler/nsMIMEInfoImpl.cpp b/uriloader/exthandler/nsMIMEInfoImpl.cpp
new file mode 100644
index 000000000..59886e465
--- /dev/null
+++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoImpl.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsEscape.h"
+#include "nsIURILoader.h"
+#include "nsCURILoader.h"
+
+// nsISupports methods
+NS_IMPL_ADDREF(nsMIMEInfoBase)
+NS_IMPL_RELEASE(nsMIMEInfoBase)
+
+NS_INTERFACE_MAP_BEGIN(nsMIMEInfoBase)
+ NS_INTERFACE_MAP_ENTRY(nsIHandlerInfo)
+ // This is only an nsIMIMEInfo if it's a MIME handler.
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMIMEInfo, mClass == eMIMEInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHandlerInfo)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+// nsMIMEInfoImpl methods
+
+// Constructors for a MIME handler.
+nsMIMEInfoBase::nsMIMEInfoBase(const char *aMIMEType) :
+ mSchemeOrType(aMIMEType),
+ mClass(eMIMEInfo),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aMIMEType) :
+ mSchemeOrType(aMIMEType),
+ mClass(eMIMEInfo),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+// Constructor for a handler that lets the caller specify whether this is a
+// MIME handler or a protocol handler. In the long run, these will be distinct
+// classes (f.e. nsMIMEInfo and nsProtocolInfo), but for now we reuse this class
+// for both and distinguish between the two kinds of handlers via the aClass
+// argument to this method, which can be either eMIMEInfo or eProtocolInfo.
+nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aType, HandlerClass aClass) :
+ mSchemeOrType(aType),
+ mClass(aClass),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+nsMIMEInfoBase::~nsMIMEInfoBase()
+{
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator** aResult)
+{
+ return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::ExtensionExists(const nsACString& aExtension, bool *_retval)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+ bool found = false;
+ uint32_t extCount = mExtensions.Length();
+ if (extCount < 1) return NS_OK;
+
+ for (uint8_t i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+
+ *_retval = found;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPrimaryExtension(nsACString& _retval)
+{
+ if (!mExtensions.Length())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ _retval = mExtensions[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPrimaryExtension(const nsACString& aExtension)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+ uint32_t extCount = mExtensions.Length();
+ uint8_t i;
+ bool found = false;
+ for (i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ mExtensions.RemoveElementAt(i);
+ }
+
+ mExtensions.InsertElementAt(0, aExtension);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::AppendExtension(const nsACString& aExtension)
+{
+ mExtensions.AppendElement(aExtension);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetType(nsACString& aType)
+{
+ if (mSchemeOrType.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aType = mSchemeOrType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetMIMEType(nsACString& aMIMEType)
+{
+ if (mSchemeOrType.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aMIMEType = mSchemeOrType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetDescription(nsAString& aDescription)
+{
+ aDescription = mDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetDescription(const nsAString& aDescription)
+{
+ mDescription = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval)
+{
+ if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString type;
+ nsresult rv = aMIMEInfo->GetMIMEType(type);
+ if (NS_FAILED(rv)) return rv;
+
+ *_retval = mSchemeOrType.Equals(type);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetFileExtensions(const nsACString& aExtensions)
+{
+ mExtensions.Clear();
+ nsCString extList( aExtensions );
+
+ int32_t breakLocation = -1;
+ while ( (breakLocation= extList.FindChar(',') )!= -1)
+ {
+ mExtensions.AppendElement(Substring(extList.get(), extList.get() + breakLocation));
+ extList.Cut(0, breakLocation+1 );
+ }
+ if ( !extList.IsEmpty() )
+ mExtensions.AppendElement( extList );
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ aDefaultDescription = mDefaultAppDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPreferredApplicationHandler(nsIHandlerApp ** aPreferredAppHandler)
+{
+ *aPreferredAppHandler = mPreferredApplication;
+ NS_IF_ADDREF(*aPreferredAppHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler)
+{
+ mPreferredApplication = aPreferredAppHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPossibleApplicationHandlers(nsIMutableArray ** aPossibleAppHandlers)
+{
+ if (!mPossibleApplications)
+ mPossibleApplications = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mPossibleApplications)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *aPossibleAppHandlers = mPossibleApplications;
+ NS_IF_ADDREF(*aPossibleAppHandlers);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction * aPreferredAction)
+{
+ *aPreferredAction = mPreferredAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction)
+{
+ mPreferredAction = aPreferredAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool * aAlwaysAsk)
+{
+ *aAlwaysAsk = mAlwaysAskBeforeHandling;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk)
+{
+ mAlwaysAskBeforeHandling = aAlwaysAsk;
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aFile)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile)
+{
+ nsresult rv;
+
+ // it doesn't make any sense to call this on protocol handlers
+ NS_ASSERTION(mClass == eMIMEInfo,
+ "nsMIMEInfoBase should have mClass == eMIMEInfo");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LaunchDefaultWithFile(aFile);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // at the moment, we only know how to hand files off to local handlers
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+ aFile->GetNativePath(path);
+ return LaunchWithIProcess(executable, path);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::LaunchWithURI(nsIURI* aURI,
+ nsIInterfaceRequestor* aWindowContext)
+{
+ // for now, this is only being called with protocol handlers; that
+ // will change once we get to more general registerContentHandler
+ // support
+ NS_ASSERTION(mClass == eProtocolInfo,
+ "nsMIMEInfoBase should be a protocol handler");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LoadUriInternal(aURI);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ return mPreferredApplication->LaunchWithURI(aURI, aWindowContext);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+void
+nsMIMEInfoBase::CopyBasicDataTo(nsMIMEInfoBase* aOther)
+{
+ aOther->mSchemeOrType = mSchemeOrType;
+ aOther->mDefaultAppDescription = mDefaultAppDescription;
+ aOther->mExtensions = mExtensions;
+}
+
+/* static */
+already_AddRefed<nsIProcess>
+nsMIMEInfoBase::InitProcess(nsIFile* aApp, nsresult* aResult)
+{
+ NS_ASSERTION(aApp, "Unexpected null pointer, fix caller");
+
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID,
+ aResult);
+ if (NS_FAILED(*aResult))
+ return nullptr;
+
+ *aResult = process->Init(aApp);
+ if (NS_FAILED(*aResult))
+ return nullptr;
+
+ return process.forget();
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const nsCString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char *string = aArg.get();
+
+ return process->Run(false, &string, 1);
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const nsString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char16_t *string = aArg.get();
+
+ return process->Runw(false, &string, 1);
+}
+
+// nsMIMEInfoImpl implementation
+NS_IMETHODIMP
+nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ if (mDefaultAppDescription.IsEmpty() && mDefaultApplication) {
+ // Don't want to cache this, just in case someone resets the app
+ // without changing the description....
+ mDefaultApplication->GetLeafName(aDefaultDescription);
+ } else {
+ aDefaultDescription = mDefaultAppDescription;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoImpl::GetHasDefaultHandler(bool * _retval)
+{
+ *_retval = !mDefaultAppDescription.IsEmpty();
+ if (mDefaultApplication) {
+ bool exists;
+ *_retval = NS_SUCCEEDED(mDefaultApplication->Exists(&exists)) && exists;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ if (!mDefaultApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
+ return LaunchWithIProcess(mDefaultApplication, nativePath);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/nsMIMEInfoImpl.h b/uriloader/exthandler/nsMIMEInfoImpl.h
new file mode 100644
index 000000000..34f244242
--- /dev/null
+++ b/uriloader/exthandler/nsMIMEInfoImpl.h
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsmimeinfoimpl_h___
+#define __nsmimeinfoimpl_h___
+
+#include "nsIMIMEInfo.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIProcess.h"
+
+/**
+ * UTF8 moz-icon URI string for the default handler application's icon, if
+ * available.
+ */
+#define PROPERTY_DEFAULT_APP_ICON_URL "defaultApplicationIconURL"
+/**
+ * UTF8 moz-icon URI string for the user's preferred handler application's
+ * icon, if available.
+ */
+#define PROPERTY_CUSTOM_APP_ICON_URL "customApplicationIconURL"
+
+/**
+ * Basic implementation of nsIMIMEInfo. Incomplete - it is meant to be
+ * subclassed, and GetHasDefaultHandler as well as LaunchDefaultWithFile need to
+ * be implemented.
+ */
+class nsMIMEInfoBase : public nsIMIMEInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // I'd use NS_DECL_NSIMIMEINFO, but I don't want GetHasDefaultHandler
+ NS_IMETHOD GetFileExtensions(nsIUTF8StringEnumerator **_retval) override;
+ NS_IMETHOD SetFileExtensions(const nsACString & aExtensions) override;
+ NS_IMETHOD ExtensionExists(const nsACString & aExtension, bool *_retval) override;
+ NS_IMETHOD AppendExtension(const nsACString & aExtension) override;
+ NS_IMETHOD GetPrimaryExtension(nsACString & aPrimaryExtension) override;
+ NS_IMETHOD SetPrimaryExtension(const nsACString & aPrimaryExtension) override;
+ NS_IMETHOD GetType(nsACString & aType) override;
+ NS_IMETHOD GetMIMEType(nsACString & aMIMEType) override;
+ NS_IMETHOD GetDescription(nsAString & aDescription) override;
+ NS_IMETHOD SetDescription(const nsAString & aDescription) override;
+ NS_IMETHOD Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval) override;
+ NS_IMETHOD GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredAppHandler) override;
+ NS_IMETHOD SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler) override;
+ NS_IMETHOD GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleAppHandlers) override;
+ NS_IMETHOD GetDefaultDescription(nsAString & aDefaultDescription) override;
+ NS_IMETHOD LaunchWithFile(nsIFile *aFile) override;
+ NS_IMETHOD LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext) override;
+ NS_IMETHOD GetPreferredAction(nsHandlerInfoAction *aPreferredAction) override;
+ NS_IMETHOD SetPreferredAction(nsHandlerInfoAction aPreferredAction) override;
+ NS_IMETHOD GetAlwaysAskBeforeHandling(bool *aAlwaysAskBeforeHandling) override;
+ NS_IMETHOD SetAlwaysAskBeforeHandling(bool aAlwaysAskBeforeHandling) override;
+ NS_IMETHOD GetPossibleLocalHandlers(nsIArray **_retval) override;
+
+ enum HandlerClass {
+ eMIMEInfo,
+ eProtocolInfo
+ };
+
+ // nsMIMEInfoBase methods
+ explicit nsMIMEInfoBase(const char *aMIMEType = "");
+ explicit nsMIMEInfoBase(const nsACString& aMIMEType);
+ nsMIMEInfoBase(const nsACString& aType, HandlerClass aClass);
+
+ void SetMIMEType(const nsACString & aMIMEType) { mSchemeOrType = aMIMEType; }
+
+ void SetDefaultDescription(const nsString& aDesc) { mDefaultAppDescription = aDesc; }
+
+ /**
+ * Copies basic data of this MIME Info Implementation to the given other
+ * MIME Info. The data consists of the MIME Type, the (default) description,
+ * the MacOS type and creator, and the extension list (this object's
+ * extension list will replace aOther's list, not append to it). This
+ * function also ensures that aOther's primary extension will be the same as
+ * the one of this object.
+ */
+ void CopyBasicDataTo(nsMIMEInfoBase* aOther);
+
+ /**
+ * Return whether this MIMEInfo has any extensions
+ */
+ bool HasExtensions() const { return mExtensions.Length() != 0; }
+
+ protected:
+ virtual ~nsMIMEInfoBase(); // must be virtual, as the the base class's Release should call the subclass's destructor
+
+ /**
+ * Launch the default application for the given file.
+ * For even more control over the launching, override launchWithFile.
+ * Also see the comment about nsIMIMEInfo in general, above.
+ *
+ * @param aFile The file that should be opened
+ */
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile) = 0;
+
+ /**
+ * Loads the URI with the OS default app.
+ *
+ * @param aURI The URI to pass off to the OS.
+ */
+ virtual nsresult LoadUriInternal(nsIURI *aURI) = 0;
+
+ static already_AddRefed<nsIProcess> InitProcess(nsIFile* aApp,
+ nsresult* aResult);
+
+ /**
+ * This method can be used to launch the file or URI with a single
+ * argument (typically either a file path or a URI spec). This is
+ * meant as a helper method for implementations of
+ * LaunchWithURI/LaunchDefaultWithFile.
+ *
+ * @param aApp The application to launch (may not be null)
+ * @param aArg The argument to pass on the command line
+ */
+ static nsresult LaunchWithIProcess(nsIFile* aApp,
+ const nsCString &aArg);
+ static nsresult LaunchWithIProcess(nsIFile* aApp,
+ const nsString &aArg);
+
+ /**
+ * Given a file: nsIURI, return the associated nsIFile
+ *
+ * @param aURI the file: URI in question
+ * @param aFile the associated nsIFile (out param)
+ */
+ static nsresult GetLocalFileFromURI(nsIURI *aURI,
+ nsIFile **aFile);
+
+ // member variables
+ nsTArray<nsCString> mExtensions; ///< array of file extensions associated w/ this MIME obj
+ nsString mDescription; ///< human readable description
+ nsCString mSchemeOrType;
+ HandlerClass mClass;
+ nsCOMPtr<nsIHandlerApp> mPreferredApplication;
+ nsCOMPtr<nsIMutableArray> mPossibleApplications;
+ nsHandlerInfoAction mPreferredAction; ///< preferred action to associate with this type
+ nsString mPreferredAppDescription;
+ nsString mDefaultAppDescription;
+ bool mAlwaysAskBeforeHandling;
+};
+
+
+/**
+ * This is a complete implementation of nsIMIMEInfo, and contains all necessary
+ * methods. However, depending on your platform you may want to use a different
+ * way of launching applications. This class stores the default application in a
+ * member variable and provides a function for setting it. For local
+ * applications, launching is done using nsIProcess, native path of the file to
+ * open as first argument.
+ */
+class nsMIMEInfoImpl : public nsMIMEInfoBase {
+ public:
+ explicit nsMIMEInfoImpl(const char *aMIMEType = "") : nsMIMEInfoBase(aMIMEType) {}
+ explicit nsMIMEInfoImpl(const nsACString& aMIMEType) : nsMIMEInfoBase(aMIMEType) {}
+ nsMIMEInfoImpl(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoBase(aType, aClass) {}
+ virtual ~nsMIMEInfoImpl() {}
+
+ // nsIMIMEInfo methods
+ NS_IMETHOD GetHasDefaultHandler(bool *_retval);
+ NS_IMETHOD GetDefaultDescription(nsAString& aDefaultDescription);
+
+ // additional methods
+ /**
+ * Sets the default application. Supposed to be only called by the OS Helper
+ * App Services; the default application is immutable after it is first set.
+ */
+ void SetDefaultApplication(nsIFile* aApp) { if (!mDefaultApplication) mDefaultApplication = aApp; }
+
+ protected:
+ // nsMIMEInfoBase methods
+ /**
+ * The base class implementation is to use LaunchWithIProcess in combination
+ * with mDefaultApplication. Subclasses can override that behaviour.
+ */
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile);
+
+ /**
+ * Loads the URI with the OS default app. This should be overridden by each
+ * OS's implementation.
+ */
+ virtual nsresult LoadUriInternal(nsIURI *aURI) = 0;
+
+ nsCOMPtr<nsIFile> mDefaultApplication; ///< default application associated with this type.
+};
+
+#endif //__nsmimeinfoimpl_h___
diff --git a/uriloader/exthandler/nsWebHandlerApp.js b/uriloader/exthandler/nsWebHandlerApp.js
new file mode 100644
index 000000000..65d600f55
--- /dev/null
+++ b/uriloader/exthandler/nsWebHandlerApp.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsWebHandler class
+
+function nsWebHandlerApp() {}
+
+nsWebHandlerApp.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsWebHandler
+
+ classDescription: "A web handler for protocols and content",
+ classID: Components.ID("8b1ae382-51a9-4972-b930-56977a57919d"),
+ contractID: "@mozilla.org/uriloader/web-handler-app;1",
+
+ _name: null,
+ _detailedDescription: null,
+ _uriTemplate: null,
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIHandlerApp
+
+ get name() {
+ return this._name;
+ },
+
+ set name(aName) {
+ this._name = aName;
+ },
+
+ get detailedDescription() {
+ return this._detailedDescription;
+ },
+
+ set detailedDescription(aDesc) {
+ this._detailedDescription = aDesc;
+ },
+
+ equals: function(aHandlerApp) {
+ if (!aHandlerApp)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp &&
+ aHandlerApp.uriTemplate &&
+ this.uriTemplate &&
+ aHandlerApp.uriTemplate == this.uriTemplate)
+ return true;
+
+ return false;
+ },
+
+ launchWithURI: function nWHA__launchWithURI(aURI, aWindowContext) {
+
+ // XXX need to strip passwd & username from URI to handle, as per the
+ // WhatWG HTML5 draft. nsSimpleURL, which is what we're going to get,
+ // can't do this directly. Ideally, we'd fix nsStandardURL to make it
+ // possible to turn off all of its quirks handling, and use that...
+
+ // encode the URI to be handled
+ var escapedUriSpecToHandle = encodeURIComponent(aURI.spec);
+
+ // insert the encoded URI and create the object version
+ var uriSpecToSend = this.uriTemplate.replace("%s", escapedUriSpecToHandle);
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var uriToSend = ioService.newURI(uriSpecToSend, null, null);
+
+ // if we have a window context, use the URI loader to load there
+ if (aWindowContext) {
+ try {
+ // getInterface throws if the object doesn't implement the given
+ // interface, so this try/catch statement is more of an if.
+ // If aWindowContext refers to a remote docshell, send the load
+ // request to the correct process.
+ aWindowContext.getInterface(Ci.nsIRemoteWindowContext)
+ .openURI(uriToSend);
+ return;
+ } catch (e) {
+ if (e.result != Cr.NS_NOINTERFACE) {
+ throw e;
+ }
+ }
+
+ // create a channel from this URI
+ var channel = NetUtil.newChannel({
+ uri: uriToSend,
+ loadUsingSystemPrincipal: true
+ });
+ channel.loadFlags = Ci.nsIChannel.LOAD_DOCUMENT_URI;
+
+ // load the channel
+ var uriLoader = Cc["@mozilla.org/uriloader;1"].
+ getService(Ci.nsIURILoader);
+ // XXX ideally, whether to pass the IS_CONTENT_PREFERRED flag should be
+ // passed in from above. Practically, the flag is probably a reasonable
+ // default since browsers don't care much, and link click is likely to be
+ // the more interesting case for non-browser apps. See
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=392957#c9> for details.
+ uriLoader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED,
+ aWindowContext);
+ return;
+ }
+
+ // since we don't have a window context, hand it off to a browser
+ var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+
+ // get browser dom window
+ var browserDOMWin = windowMediator.getMostRecentWindow("navigator:browser")
+ .QueryInterface(Ci.nsIDOMChromeWindow)
+ .browserDOMWindow;
+
+ // if we got an exception, there are several possible reasons why:
+ // a) this gecko embedding doesn't provide an nsIBrowserDOMWindow
+ // implementation (i.e. doesn't support browser-style functionality),
+ // so we need to kick the URL out to the OS default browser. This is
+ // the subject of bug 394479.
+ // b) this embedding does provide an nsIBrowserDOMWindow impl, but
+ // there doesn't happen to be a browser window open at the moment; one
+ // should be opened. It's not clear whether this situation will really
+ // ever occur in real life. If it does, the only API that I can find
+ // that seems reasonably likely to work for most embedders is the
+ // command line handler.
+ // c) something else went wrong
+ //
+ // it's not clear how one would differentiate between the three cases
+ // above, so for now we don't catch the exception
+
+ // openURI
+ browserDOMWin.openURI(uriToSend,
+ null, // no window.opener
+ Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ Ci.nsIBrowserDOMWindow.OPEN_NEW);
+
+ return;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIWebHandlerApp
+
+ get uriTemplate() {
+ return this._uriTemplate;
+ },
+
+ set uriTemplate(aURITemplate) {
+ this._uriTemplate = aURITemplate;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebHandlerApp, Ci.nsIHandlerApp])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsWebHandlerApp]);
+
diff --git a/uriloader/exthandler/nsWebHandlerApp.manifest b/uriloader/exthandler/nsWebHandlerApp.manifest
new file mode 100644
index 000000000..02b1d02af
--- /dev/null
+++ b/uriloader/exthandler/nsWebHandlerApp.manifest
@@ -0,0 +1,2 @@
+component {8b1ae382-51a9-4972-b930-56977a57919d} nsWebHandlerApp.js
+contract @mozilla.org/uriloader/web-handler-app;1 {8b1ae382-51a9-4972-b930-56977a57919d}
diff --git a/uriloader/exthandler/tests/Makefile.in b/uriloader/exthandler/tests/Makefile.in
new file mode 100644
index 000000000..13250c8ab
--- /dev/null
+++ b/uriloader/exthandler/tests/Makefile.in
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/rules.mk
+
+# need the executable for running the xpcshell unit tests
+ifneq (,$(SIMPLE_PROGRAMS))
+libs::
+ $(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit
+endif
diff --git a/uriloader/exthandler/tests/WriteArgument.cpp b/uriloader/exthandler/tests/WriteArgument.cpp
new file mode 100644
index 000000000..2efbed906
--- /dev/null
+++ b/uriloader/exthandler/tests/WriteArgument.cpp
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include "prenv.h"
+
+int main(int argc, char* argv[])
+{
+ if (argc != 2)
+ return 1;
+
+ const char* value = PR_GetEnv("WRITE_ARGUMENT_FILE");
+
+ if (!value)
+ return 2;
+
+ FILE* outfile = fopen(value, "w");
+ if (!outfile)
+ return 3;
+
+ // We only need to write out the first argument (no newline).
+ fputs(argv[argc -1], outfile);
+
+ fclose(outfile);
+
+ return 0;
+}
diff --git a/uriloader/exthandler/tests/mochitest/browser.ini b/uriloader/exthandler/tests/mochitest/browser.ini
new file mode 100644
index 000000000..9647dcf8c
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head.js
+support-files =
+ protocolHandler.html
+
+[browser_download_always_ask_preferred_app.js]
+[browser_remember_download_option.js]
+[browser_web_protocol_handlers.js]
diff --git a/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js b/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js
new file mode 100644
index 000000000..40f7ef71e
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js
@@ -0,0 +1,19 @@
+add_task(function*() {
+ // Create mocked objects for test
+ let launcher = createMockedObjects(false);
+ // Open helper app dialog with mocked launcher
+ let dlg = yield* openHelperAppDialog(launcher);
+ let doc = dlg.document;
+ let location = doc.getElementById("source");
+ let expectedValue = launcher.source.prePath;
+ if (location.value != expectedValue) {
+ info("Waiting for dialog to be populated.");
+ yield BrowserTestUtils.waitForAttribute("value", location, expectedValue);
+ }
+ is(doc.getElementById("mode").selectedItem.id, "open", "Should be opening the file.");
+ ok(!dlg.document.getElementById("openHandler").selectedItem.hidden,
+ "Should not have selected a hidden item.");
+ let helperAppDialogHiddenPromise = BrowserTestUtils.windowClosed(dlg);
+ doc.documentElement.cancelDialog();
+ yield helperAppDialogHiddenPromise;
+});
diff --git a/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js b/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js
new file mode 100644
index 000000000..996e5ad69
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js
@@ -0,0 +1,49 @@
+add_task(function*() {
+ // create mocked objects
+ let launcher = createMockedObjects(true);
+
+ // open helper app dialog with mocked launcher
+ let dlg = yield* openHelperAppDialog(launcher);
+
+ let doc = dlg.document;
+
+ // Set remember choice
+ ok(!doc.getElementById("rememberChoice").checked,
+ "Remember choice checkbox should be not checked.");
+ doc.getElementById("rememberChoice").checked = true;
+
+ // Make sure the mock handler information is not in nsIHandlerService
+ ok(!gHandlerSvc.exists(launcher.MIMEInfo), "Should not be in nsIHandlerService.");
+
+ // close the dialog by pushing the ok button.
+ let dialogClosedPromise = BrowserTestUtils.windowClosed(dlg);
+ // Make sure the ok button is enabled, since the ok button might be disabled by
+ // EnableDelayHelper mechanism. Please refer the detailed
+ // https://dxr.mozilla.org/mozilla-central/source/toolkit/components/prompts/src/SharedPromptUtils.jsm#53
+ doc.documentElement.getButton("accept").disabled = false;
+ doc.documentElement.acceptDialog();
+ yield dialogClosedPromise;
+
+ // check the mocked handler information is saved in nsIHandlerService
+ ok(gHandlerSvc.exists(launcher.MIMEInfo), "Should be in nsIHandlerService.");
+ // check the extension.
+ var mimeType = gHandlerSvc.getTypeFromExtension("abc");
+ is(mimeType, launcher.MIMEInfo.type, "Got correct mime type.");
+ var handlerInfos = gHandlerSvc.enumerate();
+ while (handlerInfos.hasMoreElements()) {
+ let handlerInfo = handlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ if (handlerInfo.type == launcher.MIMEInfo.type) {
+ // check the alwaysAskBeforeHandling
+ ok(!handlerInfo.alwaysAskBeforeHandling,
+ "Should turn off the always ask.");
+ // check the preferredApplicationHandler
+ ok(handlerInfo.preferredApplicationHandler.equals(
+ launcher.MIMEInfo.preferredApplicationHandler),
+ "Should be equal to the mockedHandlerApp.");
+ // check the perferredAction
+ is(handlerInfo.preferredAction, launcher.MIMEInfo.preferredAction,
+ "Should be equal to Ci.nsIHandlerInfo.useHelperApp.");
+ break;
+ }
+ }
+});
diff --git a/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js b/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js
new file mode 100644
index 000000000..ac3e66258
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js
@@ -0,0 +1,76 @@
+let testURL = "http://example.com/browser/" +
+ "uriloader/exthandler/tests/mochitest/protocolHandler.html";
+
+add_task(function*() {
+ // Load a page registering a protocol handler.
+ let browser = gBrowser.selectedBrowser;
+ browser.loadURI(testURL);
+ yield BrowserTestUtils.browserLoaded(browser, testURL);
+
+ // Register the protocol handler by clicking the notificationbar button.
+ let notificationValue = "Protocol Registration: testprotocol";
+ let getNotification = () =>
+ gBrowser.getNotificationBox().getNotificationWithValue(notificationValue);
+ yield BrowserTestUtils.waitForCondition(getNotification);
+ let notification = getNotification();
+ let button =
+ notification.getElementsByClassName("notification-button-default")[0];
+ ok(button, "got registration button");
+ button.click();
+
+ // Set the new handler as default.
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("testprotocol");
+ is(protoInfo.preferredAction, protoInfo.useHelperApp,
+ "using a helper application is the preferred action");
+ ok(!protoInfo.preferredApplicationHandler, "no preferred handler is set");
+ let handlers = protoInfo.possibleApplicationHandlers;
+ is(1, handlers.length, "only one handler registered for testprotocol");
+ let handler = handlers.queryElementAt(0, Ci.nsIHandlerApp);
+ ok(handler instanceof Ci.nsIWebHandlerApp, "the handler is a web handler");
+ is(handler.uriTemplate, "https://example.com/foobar?uri=%s",
+ "correct url template")
+ protoInfo.preferredApplicationHandler = handler;
+ protoInfo.alwaysAskBeforeHandling = false;
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ handlerSvc.store(protoInfo);
+
+ // Middle-click a testprotocol link and check the new tab is correct
+ let link = "#link";
+ const expectedURL = "https://example.com/foobar?uri=testprotocol%3Atest";
+
+ let promiseTabOpened =
+ BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {button: 1}, browser);
+ let tab = yield promiseTabOpened;
+ gBrowser.selectedTab = tab;
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Shift-click the testprotocol link and check the new window.
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {shiftKey: true},
+ browser);
+ let win = yield newWindowPromise;
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.waitForCondition(() => win.gBrowser.currentURI.spec == expectedURL);
+ is(win.gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+ yield BrowserTestUtils.closeWindow(win);
+
+ // Click the testprotocol link and check the url in the current tab.
+ let loadPromise = BrowserTestUtils.browserLoaded(browser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {}, browser);
+ yield loadPromise;
+ yield BrowserTestUtils.waitForCondition(() => gURLBar.value != testURL);
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+
+ // Cleanup.
+ protoInfo.preferredApplicationHandler = null;
+ handlers.removeElementAt(0);
+ handlerSvc.store(protoInfo);
+});
diff --git a/uriloader/exthandler/tests/mochitest/handlerApp.xhtml b/uriloader/exthandler/tests/mochitest/handlerApp.xhtml
new file mode 100644
index 000000000..83ba0d1a5
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/handlerApp.xhtml
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Pseudo Web Handler App</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="onLoad()">
+Pseudo Web Handler App
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+function onLoad() {
+
+ // if we have a window.opener, this must be the windowContext
+ // instance of this test. check that we got the URI right and clean up.
+ if (window.opener) {
+ window.opener.is(location.search,
+ "?uri=" + encodeURIComponent(window.opener.testURI),
+ "uri passed to web-handler app");
+ window.opener.SimpleTest.finish();
+ }
+
+ window.close();
+}
+]]>
+</script>
+
+</body>
+</html>
+
diff --git a/uriloader/exthandler/tests/mochitest/handlerApps.js b/uriloader/exthandler/tests/mochitest/handlerApps.js
new file mode 100644
index 000000000..597f9442d
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/handlerApps.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// handlerApp.xhtml grabs this for verification purposes via window.opener
+var testURI = "webcal://127.0.0.1/rheeeeet.html";
+
+const Cc = SpecialPowers.Cc;
+
+function test() {
+
+ // set up the web handler object
+ var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(SpecialPowers.Ci.nsIWebHandlerApp);
+ webHandler.name = "Test Web Handler App";
+ webHandler.uriTemplate =
+ "http://mochi.test:8888/tests/uriloader/exthandler/tests/mochitest/" +
+ "handlerApp.xhtml?uri=%s";
+
+ // set up the uri to test with
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(SpecialPowers.Ci.nsIIOService);
+ var uri = ioService.newURI(testURI, null, null);
+
+ // create a window, and launch the handler in it
+ var newWindow = window.open("", "handlerWindow", "height=300,width=300");
+ var windowContext =
+ SpecialPowers.wrap(newWindow).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+ webHandler.launchWithURI(uri, windowContext);
+
+ // if we get this far without an exception, we've at least partly passed
+ // (remaining check in handlerApp.xhtml)
+ ok(true, "webHandler launchWithURI (existing window/tab) started");
+
+ // make the web browser launch in its own window/tab
+ webHandler.launchWithURI(uri);
+
+ // if we get this far without an exception, we've passed
+ ok(true, "webHandler launchWithURI (new window/tab) test started");
+
+ // set up the local handler object
+ var localHandler = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(SpecialPowers.Ci.nsILocalHandlerApp);
+ localHandler.name = "Test Local Handler App";
+
+ // get a local app that we know will be there and do something sane
+ var osString = Cc["@mozilla.org/xre/app-info;1"].
+ getService(SpecialPowers.Ci.nsIXULRuntime).OS;
+
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(SpecialPowers.Ci.nsIDirectoryServiceProvider);
+ if (osString == "WINNT") {
+ var windowsDir = dirSvc.getFile("WinD", {});
+ var exe = windowsDir.clone().QueryInterface(SpecialPowers.Ci.nsILocalFile);
+ exe.appendRelativePath("SYSTEM32\\HOSTNAME.EXE");
+
+ } else if (osString == "Darwin") {
+ var localAppsDir = dirSvc.getFile("LocApp", {});
+ exe = localAppsDir.clone();
+ exe.append("iCal.app"); // lingers after the tests finish, but this seems
+ // seems better than explicitly killing it, since
+ // developers who run the tests locally may well
+ // information in their running copy of iCal
+
+ if (navigator.userAgent.match(/ SeaMonkey\//)) {
+ // SeaMonkey tinderboxes don't like to have iCal lingering (and focused)
+ // on next test suite run(s).
+ todo(false, "On SeaMonkey, testing OS X as generic Unix. (Bug 749872)");
+
+ // assume a generic UNIX variant
+ exe = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ exe.initWithPath("/bin/echo");
+ }
+ } else {
+ // assume a generic UNIX variant
+ exe = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ exe.initWithPath("/bin/echo");
+ }
+
+ localHandler.executable = exe;
+ localHandler.launchWithURI(ioService.newURI(testURI, null, null));
+
+ // if we get this far without an exception, we've passed
+ ok(true, "localHandler launchWithURI test");
+
+ // if we ever decide that killing iCal is the right thing to do, change
+ // the if statement below from "NOTDarwin" to "Darwin"
+ if (osString == "NOTDarwin") {
+
+ var killall = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ killall.initWithPath("/usr/bin/killall");
+
+ var process = Cc["@mozilla.org/process/util;1"].
+ createInstance(SpecialPowers.Ci.nsIProcess);
+ process.init(killall);
+
+ var args = ['iCal'];
+ process.run(false, args, args.length);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+}
+
+test();
diff --git a/uriloader/exthandler/tests/mochitest/head.js b/uriloader/exthandler/tests/mochitest/head.js
new file mode 100644
index 000000000..dad0493f8
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/head.js
@@ -0,0 +1,105 @@
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+
+function createMockedHandlerApp() {
+ // Mock the executable
+ let mockedExecutable = FileUtils.getFile("TmpD", ["mockedExecutable"]);
+ if (!mockedExecutable.exists()) {
+ mockedExecutable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
+ }
+
+ // Mock the handler app
+ let mockedHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Ci.nsILocalHandlerApp);
+ mockedHandlerApp.executable = mockedExecutable;
+ mockedHandlerApp.detailedDescription = "Mocked handler app";
+
+ registerCleanupFunction(function() {
+ // remove the mocked executable from disk.
+ if (mockedExecutable.exists()) {
+ mockedExecutable.remove(true);
+ }
+ });
+
+ return mockedHandlerApp;
+}
+
+function createMockedObjects(createHandlerApp) {
+ // Mock the mime info
+ let internalMockedMIME = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ internalMockedMIME.alwaysAskBeforeHandling = true;
+ internalMockedMIME.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ internalMockedMIME.appendExtension("abc");
+ if (createHandlerApp) {
+ let mockedHandlerApp = createMockedHandlerApp();
+ internalMockedMIME.description = mockedHandlerApp.detailedDescription;
+ internalMockedMIME.possibleApplicationHandlers.appendElement(mockedHandlerApp, false);
+ internalMockedMIME.preferredApplicationHandler = mockedHandlerApp;
+ }
+
+ // Proxy for the mocked MIME info for faking the read-only attributes
+ let mockedMIME = new Proxy(internalMockedMIME, {
+ get: function (target, property) {
+ switch (property) {
+ case "hasDefaultHandler":
+ return true;
+ case "defaultDescription":
+ return "Default description";
+ default:
+ return target[property];
+ }
+ },
+ });
+
+ // Mock the launcher:
+ let mockedLauncher = {
+ MIMEInfo: mockedMIME,
+ source: Services.io.newURI("http://www.mozilla.org/", null, null),
+ suggestedFileName: "test_download_dialog.abc",
+ targetFileIsExecutable: false,
+ saveToDisk() {},
+ cancel() {},
+ launchWithApplication() {},
+ setWebProgressListener() {},
+ saveDestinationAvailable() {},
+ contentLength: 42,
+ targetFile: null, // never read
+ // PRTime is microseconds since epoch, Date.now() returns milliseconds:
+ timeDownloadStarted: Date.now() * 1000,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable, Ci.nsIHelperAppLauncher])
+ };
+
+ registerCleanupFunction(function() {
+ // remove the mocked mime info from database.
+ let mockHandlerInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ if (gHandlerSvc.exists(mockHandlerInfo)) {
+ gHandlerSvc.remove(mockHandlerInfo);
+ }
+ });
+
+ return mockedLauncher;
+}
+
+function* openHelperAppDialog(launcher) {
+ let helperAppDialog = Cc["@mozilla.org/helperapplauncherdialog;1"].
+ createInstance(Ci.nsIHelperAppLauncherDialog);
+
+ let helperAppDialogShownPromise = BrowserTestUtils.domWindowOpened();
+ try {
+ helperAppDialog.show(launcher, window, "foopy");
+ } catch (ex) {
+ ok(false, "Trying to show unknownContentType.xul failed with exception: " + ex);
+ Cu.reportError(ex);
+ }
+ let dlg = yield helperAppDialogShownPromise;
+
+ yield BrowserTestUtils.waitForEvent(dlg, "load", false);
+
+ is(dlg.location.href, "chrome://mozapps/content/downloads/unknownContentType.xul",
+ "Got correct dialog");
+
+ return dlg;
+}
diff --git a/uriloader/exthandler/tests/mochitest/mochitest.ini b/uriloader/exthandler/tests/mochitest/mochitest.ini
new file mode 100644
index 000000000..ae191dc7b
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/mochitest.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ handlerApp.xhtml
+ handlerApps.js
+ unsafeBidi_chromeScript.js
+ unsafeBidiFileName.sjs
+
+[test_handlerApps.xhtml]
+skip-if = (toolkit == 'android' || os == 'mac') || e10s # OS X: bug 786938
+[test_unsafeBidiChars.xhtml]
diff --git a/uriloader/exthandler/tests/mochitest/protocolHandler.html b/uriloader/exthandler/tests/mochitest/protocolHandler.html
new file mode 100644
index 000000000..5a929ba99
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/protocolHandler.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Protocol handler</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ <a id="link" href="testprotocol:test">testprotocol link</a>
+ </body>
+</html>
diff --git a/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml b/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml
new file mode 100644
index 000000000..e5d73a232
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for Handler Apps </title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="handlerApps.js"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+</body>
+</html>
+
diff --git a/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
new file mode 100644
index 000000000..fafe0b4c5
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
@@ -0,0 +1,77 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for Handling of unsafe bidi chars</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe id="test"></iframe>
+<script type="text/javascript">
+<![CDATA[
+
+var unsafeBidiChars = [
+ "\xe2\x80\xaa", // LRE
+ "\xe2\x80\xab", // RLE
+ "\xe2\x80\xac", // PDF
+ "\xe2\x80\xad", // LRO
+ "\xe2\x80\xae" // RLO
+];
+
+var tests = [
+ "{1}.test",
+ "{1}File.test",
+ "Fi{1}le.test",
+ "File{1}.test",
+ "File.{1}test",
+ "File.te{1}st",
+ "File.test{1}",
+ "File.{1}",
+];
+
+function replace(name, x) {
+ return name.replace(/\{1\}/, x);
+}
+
+function sanitize(name) {
+ return replace(name, '_');
+}
+
+add_task(function* () {
+ let url = SimpleTest.getTestFileURL("unsafeBidi_chromeScript.js");
+ let chromeScript = SpecialPowers.loadChromeScript(url);
+
+ for (let test of tests) {
+ for (let char of unsafeBidiChars) {
+ let promiseName = new Promise(function(resolve) {
+ chromeScript.addMessageListener("suggestedFileName",
+ function listener(data) {
+ chromeScript.removeMessageListener("suggestedFileName", listener);
+ resolve(data);
+ });
+ });
+ let name = replace(test, char);
+ let expected = sanitize(test);
+ document.getElementById("test").src =
+ "unsafeBidiFileName.sjs?name=" + encodeURIComponent(name);
+ is((yield promiseName), expected, "got the expected sanitized name");
+ }
+ }
+
+ let promise = new Promise(function(resolve) {
+ chromeScript.addMessageListener("unregistered", function listener() {
+ chromeScript.removeMessageListener("unregistered", listener);
+ resolve();
+ });
+ });
+ chromeScript.sendAsyncMessage("unregister");
+ yield promise;
+
+ chromeScript.destroy();
+});
+
+]]>
+</script>
+</body>
+</html>
diff --git a/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs b/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs
new file mode 100644
index 000000000..48301be5b
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ if (!request.queryString.match(/^name=/))
+ return;
+ var name = decodeURIComponent(request.queryString.substring(5));
+
+ response.setHeader("Content-Type", "application/octet-stream; name=\"" + name + "\"");
+ response.setHeader("Content-Disposition", "inline; filename=\"" + name + "\"");
+}
diff --git a/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js b/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js
new file mode 100644
index 000000000..16c82d818
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js
@@ -0,0 +1,28 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const HELPERAPP_DIALOG_CONTRACT = "@mozilla.org/helperapplauncherdialog;1";
+const HELPERAPP_DIALOG_CID =
+ Components.ID(Cc[HELPERAPP_DIALOG_CONTRACT].number);
+
+const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"].
+ getService(Ci.nsIUUIDGenerator).generateUUID();
+
+function HelperAppLauncherDialog() {}
+HelperAppLauncherDialog.prototype = {
+ show: function(aLauncher, aWindowContext, aReason) {
+ sendAsyncMessage("suggestedFileName", aLauncher.suggestedFileName);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog])
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(FAKE_CID, "", HELPERAPP_DIALOG_CONTRACT,
+ XPCOMUtils._getFactory(HelperAppLauncherDialog));
+
+addMessageListener("unregister", function() {
+ registrar.registerFactory(HELPERAPP_DIALOG_CID, "",
+ HELPERAPP_DIALOG_CONTRACT, null);
+ sendAsyncMessage("unregistered");
+});
diff --git a/uriloader/exthandler/tests/moz.build b/uriloader/exthandler/tests/moz.build
new file mode 100644
index 000000000..6aadfdc52
--- /dev/null
+++ b/uriloader/exthandler/tests/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+
+BROWSER_CHROME_MANIFESTS += ['mochitest/browser.ini']
+
+# The encoding test is already implemented in the Downloads API by a set of
+# test cases with the string "content_encoding" in their names.
+if not CONFIG['MOZ_JSDOWNLOADS']:
+ XPCSHELL_TESTS_MANIFESTS += ['unit_ipc/xpcshell.ini']
+
+GeckoSimplePrograms([
+ 'WriteArgument',
+], linkage=None)
+
+USE_LIBS += [
+ 'nspr',
+]
diff --git a/uriloader/exthandler/tests/unit/head_handlerService.js b/uriloader/exthandler/tests/unit/head_handlerService.js
new file mode 100644
index 000000000..8b6803d24
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/head_handlerService.js
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Inspired by the Places infrastructure in head_bookmarks.js
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+var HandlerServiceTest = {
+ //**************************************************************************//
+ // Convenience Getters
+
+ __dirSvc: null,
+ get _dirSvc() {
+ if (!this.__dirSvc)
+ this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ QueryInterface(Ci.nsIDirectoryService);
+ return this.__dirSvc;
+ },
+
+ __consoleSvc: null,
+ get _consoleSvc() {
+ if (!this.__consoleSvc)
+ this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService);
+ return this.__consoleSvc;
+ },
+
+
+ //**************************************************************************//
+ // nsISupports
+
+ interfaces: [Ci.nsIDirectoryServiceProvider, Ci.nsISupports],
+
+ QueryInterface: function HandlerServiceTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function HandlerServiceTest_init() {
+ // Register ourselves as a directory provider for the datasource file
+ // if there isn't one registered already.
+ try {
+ this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ } catch (ex) {
+ this._dirSvc.registerProvider(this);
+ this._providerRegistered = true;
+ }
+
+ // Delete the existing datasource file, if any, so we start from scratch.
+ // We also do this after finishing the tests, so there shouldn't be an old
+ // file lying around, but just in case we delete it here as well.
+ this._deleteDatasourceFile();
+
+ // Turn on logging so we can troubleshoot problems with the tests.
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.contentHandling.log", true);
+ },
+
+ destroy: function HandlerServiceTest_destroy() {
+ // Delete the existing datasource file, if any, so we don't leave test files
+ // lying around and we start from scratch the next time.
+ this._deleteDatasourceFile();
+ // Unregister the directory service provider
+ if (this._providerRegistered)
+ this._dirSvc.unregisterProvider(this);
+ },
+
+
+ //**************************************************************************//
+ // nsIDirectoryServiceProvider
+
+ getFile: function HandlerServiceTest_getFile(property, persistent) {
+ this.log("getFile: requesting " + property);
+
+ persistent.value = true;
+
+ if (property == "UMimTyp") {
+ var datasourceFile = this._dirSvc.get("CurProcD", Ci.nsIFile);
+ datasourceFile.append("mimeTypes.rdf");
+ return datasourceFile;
+ }
+
+ // This causes extraneous errors to show up in the log when the directory
+ // service asks us first for CurProcD and MozBinD. I wish there was a way
+ // to suppress those errors.
+ this.log("the following NS_ERROR_FAILURE exception in " +
+ "nsIDirectoryServiceProvider::getFile is expected, " +
+ "as we don't provide the '" + property + "' file");
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+
+ //**************************************************************************//
+ // Utilities
+
+ /**
+ * Delete the datasource file.
+ */
+ _deleteDatasourceFile: function HandlerServiceTest__deleteDatasourceFile() {
+ var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ if (file.exists())
+ file.remove(false);
+ },
+
+ /**
+ * Get the contents of the datasource as a serialized string. Useful for
+ * debugging problems with test failures, i.e.:
+ *
+ * HandlerServiceTest.log(HandlerServiceTest.getDatasourceContents());
+ *
+ * @returns {string} the serialized datasource
+ */
+ getDatasourceContents: function HandlerServiceTest_getDatasourceContents() {
+ var rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var fileHandler = ioService.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ var fileURL = fileHandler.getURLSpecFromFile(this.getDatasourceFile());
+ var ds = rdf.GetDataSourceBlocking(fileURL);
+
+ var outputStream = {
+ data: "",
+ close: function() {},
+ flush: function() {},
+ write: function (buffer,count) {
+ this.data += buffer;
+ return count;
+ },
+ writeFrom: function (stream,count) {},
+ isNonBlocking: false
+ };
+
+ ds.QueryInterface(Components.interfaces.nsIRDFXMLSource);
+ ds.Serialize(outputStream);
+
+ return outputStream.data;
+ },
+
+ /**
+ * Log a message to the console and the test log.
+ */
+ log: function HandlerServiceTest_log(message) {
+ message = "*** HandlerServiceTest: " + message;
+ this._consoleSvc.logStringMessage(message);
+ print(message);
+ }
+
+};
+
+HandlerServiceTest.init();
diff --git a/uriloader/exthandler/tests/unit/mailcap b/uriloader/exthandler/tests/unit/mailcap
new file mode 100644
index 000000000..dc93ef804
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/mailcap
@@ -0,0 +1,2 @@
+text/plain; cat '%s'; needsterminal
+text/plain; sed '%s'
diff --git a/uriloader/exthandler/tests/unit/tail_handlerService.js b/uriloader/exthandler/tests/unit/tail_handlerService.js
new file mode 100644
index 000000000..1d1989127
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/tail_handlerService.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+HandlerServiceTest.destroy();
diff --git a/uriloader/exthandler/tests/unit/test_badMIMEType.js b/uriloader/exthandler/tests/unit/test_badMIMEType.js
new file mode 100644
index 000000000..df1202a0f
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_badMIMEType.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ // "text/plain" has an 0xFF character appended to it. This means it's an
+ // invalid string, which is tricky to enter using a text editor (I used
+ // emacs' hexl-mode). It also means an ordinary text editor might drop it
+ // or convert it to something that *is* valid (in UTF8). So we measure
+ // its length to make sure this hasn't happened.
+ var badMimeType = "text/plainÿ";
+ do_check_eq(badMimeType.length, 11);
+
+ try {
+ var type = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService).
+ getFromTypeAndExtension(badMimeType, "txt");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined
+ } finally {
+ }
+ // Not crashing is good enough
+ do_check_eq(true, true);
+}
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
new file mode 100644
index 000000000..d83c486bb
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 508030 <https://bugzilla.mozilla.org/show_bug.cgi?id=508030>:
+ * nsIMIMEService.getTypeFromExtension fails to find a match in the
+ * "ext-to-type-mapping" category if the provided extension is not lowercase.
+ */
+function run_test() {
+ // --- Common services ---
+
+ const mimeService = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+
+ const categoryManager = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+
+ // --- Test procedure ---
+
+ const kTestExtension = "testextension";
+ const kTestExtensionMixedCase = "testExtensIon";
+ const kTestMimeType = "application/x-testextension";
+
+ // Ensure that the test extension is not initially recognized by the operating
+ // system or the "ext-to-type-mapping" category.
+ try {
+ // Try and get the MIME type associated with the extension.
+ mimeService.getTypeFromExtension(kTestExtension);
+ // The line above should have thrown an exception.
+ do_throw("nsIMIMEService.getTypeFromExtension succeeded unexpectedly");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined.
+ // Any other exception would cause the test to fail.
+ }
+
+ // Add a temporary category entry mapping the extension to the MIME type.
+ categoryManager.addCategoryEntry("ext-to-type-mapping", kTestExtension,
+ kTestMimeType, false, true);
+
+ // Check that the mapping is recognized in the simple case.
+ var type = mimeService.getTypeFromExtension(kTestExtension);
+ do_check_eq(type, kTestMimeType);
+
+ // Check that the mapping is recognized even if the extension has mixed case.
+ type = mimeService.getTypeFromExtension(kTestExtensionMixedCase);
+ do_check_eq(type, kTestMimeType);
+
+ // Clean up after ourselves.
+ categoryManager.deleteCategoryEntry("ext-to-type-mapping", kTestExtension, false);
+}
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
new file mode 100644
index 000000000..1ae2a6fcf
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
@@ -0,0 +1,186 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 484579 <https://bugzilla.mozilla.org/show_bug.cgi?id=484579>:
+ * nsIMIMEService.getTypeFromExtension may fail unexpectedly on Windows when
+ * "Content Type" is empty in the registry.
+ */
+function run_test() {
+ // --- Preliminary platform check ---
+
+ // If this test is not running on the Windows platform, stop now, before
+ // calling XPCOMUtils.generateQI during the MockWindowsRegKey declaration.
+ if (mozinfo.os != "win")
+ return;
+
+ // --- Modified nsIWindowsRegKey implementation ---
+
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ /**
+ * Constructs a new mock registry key by wrapping the provided object.
+ *
+ * This mock implementation is tailored for this test, and forces consumers
+ * of the readStringValue method to believe that the "Content Type" value of
+ * the ".txt" key under HKEY_CLASSES_ROOT is an empty string.
+ *
+ * The same value read from "HKEY_LOCAL_MACHINE\SOFTWARE\Classes" is not
+ * affected.
+ *
+ * @param aWrappedObject An actual nsIWindowsRegKey implementation.
+ */
+ function MockWindowsRegKey(aWrappedObject) {
+ this._wrappedObject = aWrappedObject;
+
+ // This function creates a forwarding function for wrappedObject
+ function makeForwardingFunction(functionName) {
+ return function() {
+ return aWrappedObject[functionName].apply(aWrappedObject, arguments);
+ }
+ }
+
+ // Forward all the functions that are not explicitly overridden
+ for (var propertyName in aWrappedObject) {
+ if (!(propertyName in this)) {
+ if (typeof aWrappedObject[propertyName] == "function") {
+ this[propertyName] = makeForwardingFunction(propertyName);
+ } else {
+ this[propertyName] = aWrappedObject[propertyName];
+ }
+ }
+ }
+ }
+
+ MockWindowsRegKey.prototype = {
+ // --- Overridden nsISupports interface functions ---
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowsRegKey]),
+
+ // --- Overridden nsIWindowsRegKey interface functions ---
+
+ open: function(aRootKey, aRelPath, aMode) {
+ // Remember the provided root key and path
+ this._rootKey = aRootKey;
+ this._relPath = aRelPath;
+
+ // Create the actual registry key
+ return this._wrappedObject.open(aRootKey, aRelPath, aMode);
+ },
+
+ openChild: function(aRelPath, aMode) {
+ // Open the child key and wrap it
+ var innerKey = this._wrappedObject.openChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ createChild: function(aRelPath, aMode) {
+ // Create the child key and wrap it
+ var innerKey = this._wrappedObject.createChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ get childCount() {
+ return this._wrappedObject.childCount;
+ },
+
+ get valueCount() {
+ return this._wrappedObject.valueCount;
+ },
+
+ readStringValue: function(aName) {
+ // If this is the key under test, return a fake value
+ if (this._rootKey == Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT &&
+ this._relPath.toLowerCase() == ".txt" &&
+ aName.toLowerCase() == "content type") {
+ return "";
+ }
+
+ // Return the real value in the registry
+ return this._wrappedObject.readStringValue(aName);
+ }
+ };
+
+ // --- Mock nsIWindowsRegKey factory ---
+
+ var componentRegistrar = Components.manager.
+ QueryInterface(Ci.nsIComponentRegistrar);
+
+ var originalWindowsRegKeyCID;
+ var mockWindowsRegKeyFactory;
+
+ const kMockCID = Components.ID("{9b23dfe9-296b-4740-ba1c-d39c9a16e55e}");
+ const kWindowsRegKeyContractID = "@mozilla.org/windows-registry-key;1";
+ const kWindowsRegKeyClassName = "nsWindowsRegKey";
+
+ function registerMockWindowsRegKeyFactory() {
+ mockWindowsRegKeyFactory = {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ var innerKey = originalWindowsRegKeyFactory.createInstance(null, aIid);
+ var key = new MockWindowsRegKey(innerKey);
+
+ return key.QueryInterface(aIid);
+ }
+ };
+
+ // Preserve the original factory
+ originalWindowsRegKeyCID = Cc[kWindowsRegKeyContractID].number;
+
+ // Register the mock factory
+ componentRegistrar.registerFactory(
+ kMockCID,
+ "Mock Windows Registry Key Implementation",
+ kWindowsRegKeyContractID,
+ mockWindowsRegKeyFactory
+ );
+ }
+
+ function unregisterMockWindowsRegKeyFactory() {
+ // Free references to the mock factory
+ componentRegistrar.unregisterFactory(
+ kMockCID,
+ mockWindowsRegKeyFactory
+ );
+
+ // Restore the original factory
+ componentRegistrar.registerFactory(
+ Components.ID(originalWindowsRegKeyCID),
+ "",
+ kWindowsRegKeyContractID,
+ null
+ );
+ }
+
+ // --- Test procedure ---
+
+ // Activate the override of the ".txt" file association data in the registry
+ registerMockWindowsRegKeyFactory();
+ try {
+ // Try and get the MIME type associated with the extension. If this
+ // operation does not throw an unexpected exception, the test succeeds.
+ var type = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService).
+ getTypeFromExtension(".txt");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined
+ } finally {
+ // Ensure we restore the original factory when the test is finished
+ unregisterMockWindowsRegKeyFactory();
+ }
+}
diff --git a/uriloader/exthandler/tests/unit/test_handlerService.js b/uriloader/exthandler/tests/unit/test_handlerService.js
new file mode 100644
index 000000000..3facc63ae
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService.js
@@ -0,0 +1,470 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ //**************************************************************************//
+ // Constants
+
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+
+ const mimeSvc = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+
+ const prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+
+ const ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ const env = Cc["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+
+ const rootPrefBranch = prefSvc.getBranch("");
+
+ let noMailto = false;
+ if (mozinfo.os == "win") {
+ // Check mailto handler from registry.
+ // If registry entry is nothing, no mailto handler
+ let regSvc = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ regSvc.open(regSvc.ROOT_KEY_CLASSES_ROOT,
+ "mailto",
+ regSvc.ACCESS_READ);
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ regSvc.close();
+ }
+
+ if (mozinfo.os == "linux") {
+ // Check mailto handler from GIO
+ // If there isn't one, then we have no mailto handler
+ let gIOSvc = Cc["@mozilla.org/gio-service;1"].
+ createInstance(Ci.nsIGIOService);
+ try {
+ gIOSvc.getAppForURIScheme("mailto");
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ }
+
+ //**************************************************************************//
+ // Sample Data
+
+ // It doesn't matter whether or not this nsIFile is actually executable,
+ // only that it has a path and exists. Since we don't know any executable
+ // that exists on all platforms (except possibly the application being
+ // tested, but there doesn't seem to be a way to get a reference to that
+ // from the directory service), we use the temporary directory itself.
+ var executable = HandlerServiceTest._dirSvc.get("TmpD", Ci.nsIFile);
+ // XXX We could, of course, create an actual executable in the directory:
+ //executable.append("localhandler");
+ //if (!executable.exists())
+ // executable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
+
+ var localHandler = {
+ name: "Local Handler",
+ executable: executable,
+ interfaces: [Ci.nsIHandlerApp, Ci.nsILocalHandlerApp, Ci.nsISupports],
+ QueryInterface: function(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ };
+
+ var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ webHandler.name = "Web Handler";
+ webHandler.uriTemplate = "http://www.example.com/?%s";
+
+ // FIXME: these tests create and manipulate enough variables that it would
+ // make sense to move each test into its own scope so we don't run the risk
+ // of one test stomping on another's data.
+
+
+ //**************************************************************************//
+ // Test Default Properties
+
+ // Get a handler info for a MIME type that neither the application nor
+ // the OS knows about and make sure its properties are set to the proper
+ // default values.
+
+ var handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ // Make sure it's also an nsIHandlerInfo.
+ do_check_true(handlerInfo instanceof Ci.nsIHandlerInfo);
+
+ do_check_eq(handlerInfo.type, "nonexistent/type");
+
+ // Deprecated property, but we should still make sure it's set correctly.
+ do_check_eq(handlerInfo.MIMEType, "nonexistent/type");
+
+ // These properties are the ones the handler service knows how to store.
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+ do_check_eq(handlerInfo.preferredApplicationHandler, null);
+ do_check_eq(handlerInfo.possibleApplicationHandlers.length, 0);
+ do_check_true(handlerInfo.alwaysAskBeforeHandling);
+
+ // These properties are initialized to default values by the service,
+ // so we might as well make sure they're initialized to the right defaults.
+ do_check_eq(handlerInfo.description, "");
+ do_check_eq(handlerInfo.hasDefaultHandler, false);
+ do_check_eq(handlerInfo.defaultDescription, "");
+
+ // test some default protocol info properties
+ var haveDefaultHandlersVersion = false;
+ try {
+ // If we have a defaultHandlersVersion pref, then assume that we're in the
+ // firefox tree and that we'll also have default handlers.
+ // Bug 395131 has been filed to make this test work more generically
+ // by providing our own prefs for this test rather than this icky
+ // special casing.
+ rootPrefBranch.getCharPref("gecko.handlerService.defaultHandlersVersion");
+ haveDefaultHandlersVersion = true;
+ } catch (ex) {}
+
+ const kExternalWarningDefault =
+ "network.protocol-handler.warn-external-default";
+ prefSvc.setBoolPref(kExternalWarningDefault, true);
+
+ // XXX add more thorough protocol info property checking
+
+ // no OS default handler exists
+ var protoInfo = protoSvc.getProtocolHandlerInfo("x-moz-rheet");
+ do_check_eq(protoInfo.preferredAction, protoInfo.alwaysAsk);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: false
+ const kExternalWarningPrefPrefix = "network.protocol-handler.warn-external.";
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ // OS handler isn't included in possibleApplicationHandlers, so length is 0
+ // Once they become instances of nsILocalHandlerApp, this number will need
+ // to change.
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default exists, explicit warning pref: false
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (haveDefaultHandlersVersion)
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ else
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+
+ // Win7+ or Linux's GIO might not have a default mailto: handler
+ if (noMailto)
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ else
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default exists, explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (haveDefaultHandlersVersion) {
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ // Win7+ or Linux's GIO may have no default mailto: handler. Otherwise
+ // alwaysAskBeforeHandling is expected to be false here, because although
+ // the pref is true, the value in RDF is false. The injected mailto handler
+ // carried over the default pref value, and so when we set the pref above
+ // to true it's ignored.
+ if (noMailto)
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ else
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ } else {
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ }
+
+ if (haveDefaultHandlersVersion) {
+ // Now set the value stored in RDF to true, and the pref to false, to make
+ // sure we still get the right value. (Basically, same thing as above but
+ // with the values reversed.)
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo.alwaysAskBeforeHandling = true;
+ handlerSvc.store(protoInfo);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ }
+
+
+ //**************************************************************************//
+ // Test Round-Trip Data Integrity
+
+ // Test round-trip data integrity by setting the properties of the handler
+ // info object to different values, telling the handler service to store the
+ // object, and then retrieving a new info object for the same type and making
+ // sure its properties are identical.
+
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ handlerInfo.preferredApplicationHandler = localHandler;
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ handlerSvc.store(handlerInfo);
+
+ handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp);
+
+ do_check_neq(handlerInfo.preferredApplicationHandler, null);
+ var preferredHandler = handlerInfo.preferredApplicationHandler;
+ do_check_eq(typeof preferredHandler, "object");
+ do_check_eq(preferredHandler.name, "Local Handler");
+ do_check_true(preferredHandler instanceof Ci.nsILocalHandlerApp);
+ preferredHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ do_check_eq(preferredHandler.executable.path, localHandler.executable.path);
+
+ do_check_false(handlerInfo.alwaysAskBeforeHandling);
+
+ // Make sure the handler service's enumerate method lists all known handlers.
+ var handlerInfo2 = mimeSvc.getFromTypeAndExtension("nonexistent/type2", null);
+ handlerSvc.store(handlerInfo2);
+ var handlerTypes = ["nonexistent/type", "nonexistent/type2"];
+ if (haveDefaultHandlersVersion) {
+ handlerTypes.push("webcal");
+ handlerTypes.push("mailto");
+ handlerTypes.push("irc");
+ handlerTypes.push("ircs");
+ }
+ var handlers = handlerSvc.enumerate();
+ while (handlers.hasMoreElements()) {
+ var handler = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ do_check_neq(handlerTypes.indexOf(handler.type), -1);
+ handlerTypes.splice(handlerTypes.indexOf(handler.type), 1);
+ }
+ do_check_eq(handlerTypes.length, 0);
+
+ // Make sure the handler service's remove method removes a handler record.
+ handlerSvc.remove(handlerInfo2);
+ handlers = handlerSvc.enumerate();
+ while (handlers.hasMoreElements())
+ do_check_neq(handlers.getNext().QueryInterface(Ci.nsIHandlerInfo).type,
+ handlerInfo2.type);
+
+ // Make sure we can store and retrieve a handler info object with no preferred
+ // handler.
+ var noPreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
+ handlerSvc.store(noPreferredHandlerInfo);
+ noPreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
+ do_check_eq(noPreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure that the handler service removes an existing handler record
+ // if we store a handler info object with no preferred handler.
+ var removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ removePreferredHandlerInfo.preferredApplicationHandler = localHandler;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ removePreferredHandlerInfo.preferredApplicationHandler = null;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ do_check_eq(removePreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure we can store and retrieve a handler info object with possible
+ // handlers. We test both adding and removing handlers.
+
+ // Get a handler info and make sure it has no possible handlers.
+ var possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Store and re-retrieve the handler and make sure it still has no possible
+ // handlers.
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Add two handlers, store the object, re-retrieve it, and make sure it has
+ // two handlers.
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(localHandler,
+ false);
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(webHandler,
+ false);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 2);
+
+ // Figure out which is the local and which is the web handler and the index
+ // in the array of the local handler, which is the one we're going to remove
+ // to test removal of a handler.
+ var handler1 = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(0, Ci.nsIHandlerApp);
+ var handler2 = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(1, Ci.nsIHandlerApp);
+ var localPossibleHandler, webPossibleHandler, localIndex;
+ if (handler1 instanceof Ci.nsILocalHandlerApp)
+ [localPossibleHandler, webPossibleHandler, localIndex] = [handler1,
+ handler2,
+ 0];
+ else
+ [localPossibleHandler, webPossibleHandler, localIndex] = [handler2,
+ handler1,
+ 1];
+ localPossibleHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ webPossibleHandler.QueryInterface(Ci.nsIWebHandlerApp);
+
+ // Make sure the two handlers are the ones we stored.
+ do_check_eq(localPossibleHandler.name, localHandler.name);
+ do_check_true(localPossibleHandler.equals(localHandler));
+ do_check_eq(webPossibleHandler.name, webHandler.name);
+ do_check_true(webPossibleHandler.equals(webHandler));
+
+ // Remove a handler, store the object, re-retrieve it, and make sure
+ // it only has one handler.
+ possibleHandlersInfo.possibleApplicationHandlers.removeElementAt(localIndex);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 1);
+
+ // Make sure the handler is the one we didn't remove.
+ webPossibleHandler = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(0, Ci.nsIWebHandlerApp);
+ do_check_eq(webPossibleHandler.name, webHandler.name);
+ do_check_true(webPossibleHandler.equals(webHandler));
+
+ //////////////////////////////////////////////////////
+ // handler info command line parameters and equality
+ var localApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ var handlerApp = localApp.QueryInterface(Ci.nsIHandlerApp);
+
+ do_check_true(handlerApp.equals(localApp));
+
+ localApp.executable = executable;
+
+ do_check_eq(0, localApp.parameterCount);
+ localApp.appendParameter("-test1");
+ do_check_eq(1, localApp.parameterCount);
+ localApp.appendParameter("-test2");
+ do_check_eq(2, localApp.parameterCount);
+ do_check_true(localApp.parameterExists("-test1"));
+ do_check_true(localApp.parameterExists("-test2"));
+ do_check_false(localApp.parameterExists("-false"));
+ localApp.clearParameters();
+ do_check_eq(0, localApp.parameterCount);
+
+ var localApp2 = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+
+ localApp2.executable = executable;
+
+ localApp.clearParameters();
+ do_check_true(localApp.equals(localApp2));
+
+ // equal:
+ // cut -d 1 -f 2
+ // cut -d 1 -f 2
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test3");
+ do_check_true(localApp.equals(localApp2));
+
+ // not equal:
+ // cut -d 1 -f 2
+ // cut -f 1 -d 2
+
+ localApp.clearParameters();
+ localApp2.clearParameters();
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test3");
+ do_check_false(localApp2.equals(localApp));
+
+ var str;
+ str = localApp.getParameter(0)
+ do_check_eq(str, "-test1");
+ str = localApp.getParameter(1)
+ do_check_eq(str, "-test2");
+ str = localApp.getParameter(2)
+ do_check_eq(str, "-test3");
+
+ // FIXME: test round trip integrity for a protocol.
+ // FIXME: test round trip integrity for a handler info with a web handler.
+
+ //**************************************************************************//
+ // getTypeFromExtension tests
+
+ // test nonexistent extension
+ var lolType = handlerSvc.getTypeFromExtension("lolcat");
+ do_check_eq(lolType, "");
+
+
+ // add a handler for the extension
+ var lolHandler = mimeSvc.getFromTypeAndExtension("application/lolcat", null);
+
+ do_check_false(lolHandler.extensionExists("lolcat"));
+ lolHandler.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ lolHandler.preferredApplicationHandler = localHandler;
+ lolHandler.alwaysAskBeforeHandling = false;
+
+ // store the handler
+ do_check_false(handlerSvc.exists(lolHandler));
+ handlerSvc.store(lolHandler);
+ do_check_true(handlerSvc.exists(lolHandler));
+
+ // Get a file:// string pointing to mimeTypes.rdf
+ var rdfFile = HandlerServiceTest._dirSvc.get("UMimTyp", Ci.nsIFile);
+ var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+ var rdfFileURI = fileHandler.getURLSpecFromFile(rdfFile);
+
+ // Assign a file extenstion to the handler. handlerSvc.store() doesn't
+ // actually store any file extensions added with setFileExtensions(), you
+ // have to wade into RDF muck to do so.
+
+ // Based on toolkit/mozapps/downloads/content/helperApps.js :: addExtension()
+ var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+ var mimeSource = gRDF.GetUnicodeResource("urn:mimetype:application/lolcat");
+ var valueProperty = gRDF.GetUnicodeResource("http://home.netscape.com/NC-rdf#fileExtensions");
+ var mimeLiteral = gRDF.GetLiteral("lolcat");
+
+ var DS = gRDF.GetDataSourceBlocking(rdfFileURI);
+ DS.Assert(mimeSource, valueProperty, mimeLiteral, true);
+
+
+ // test now-existent extension
+ lolType = handlerSvc.getTypeFromExtension("lolcat");
+ do_check_eq(lolType, "application/lolcat");
+
+ // test mailcap entries with needsterminal are ignored on non-Windows non-Mac.
+ if (mozinfo.os != "win" && mozinfo.os != "mac") {
+ env.set('PERSONAL_MAILCAP', do_get_file('mailcap').path);
+ handlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", null);
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useSystemDefault);
+ do_check_eq(handlerInfo.defaultDescription, "sed");
+ }
+}
diff --git a/uriloader/exthandler/tests/unit/test_punycodeURIs.js b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
new file mode 100644
index 000000000..38622c840
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Encoded test URI to work on all platforms/independent of file encoding
+const kTestURI = "http://\u65e5\u672c\u8a93.jp/";
+const kExpectedURI = "http://xn--wgv71a309e.jp/";
+const kOutputFile = "result.txt";
+
+// Try several times in case the box we're running on is slow.
+const kMaxCheckExistAttempts = 30; // seconds
+var gCheckExistsAttempts = 0;
+
+const tempDir = do_get_tempdir();
+
+function checkFile() {
+ // This is where we expect the output
+ var tempFile = tempDir.clone();
+ tempFile.append(kOutputFile);
+
+ if (!tempFile.exists()) {
+ if (gCheckExistsAttempts >= kMaxCheckExistAttempts) {
+ do_throw("Expected File " + tempFile.path + " does not exist after " +
+ kMaxCheckExistAttempts + " seconds");
+ }
+ else {
+ ++gCheckExistsAttempts;
+ // Wait a bit longer then try again
+ do_timeout(1000, checkFile);
+ return;
+ }
+ }
+
+ // Now read it
+ var fstream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ var sstream =
+ Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ fstream.init(tempFile, -1, 0, 0);
+ sstream.init(fstream);
+
+ // Read the first line only as that's the one we expect WriteArguments
+ // to be writing the argument to.
+ var data = sstream.read(4096);
+
+ sstream.close();
+ fstream.close();
+
+ // Now remove the old file
+ tempFile.remove(false);
+
+ // This currently fails on Mac with an argument like -psn_0_nnnnnn
+ // This seems to be to do with how the executable is called, but I couldn't
+ // find a way around it.
+ // Additionally the lack of OS detection in xpcshell tests sucks, so we'll
+ // have to check for the argument mac gives us.
+ if (data.substring(0, 7) != "-psn_0_")
+ do_check_eq(data, kExpectedURI);
+
+ do_test_finished();
+}
+
+function run_test() {
+ if (mozinfo.os == "mac") {
+ dump("INFO | test_punycodeURIs.js | Skipping test on mac, bug 599475")
+ return;
+ }
+
+ // set up the uri to test with
+ var ioService =
+ Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ // set up the local handler object
+ var localHandler =
+ Components.classes["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Components.interfaces.nsILocalHandlerApp);
+ localHandler.name = "Test Local Handler App";
+
+ // WriteArgument will just dump its arguments to a file for us.
+ var processDir = do_get_cwd();
+ var exe = processDir.clone();
+ exe.append("WriteArgument");
+
+ if (!exe.exists()) {
+ // Maybe we are on windows
+ exe.leafName = "WriteArgument.exe";
+ if (!exe.exists())
+ do_throw("Could not locate the WriteArgument tests executable\n");
+ }
+
+ var outFile = tempDir.clone();
+ outFile.append(kOutputFile);
+
+ // Set an environment variable for WriteArgument to pick up
+ var envSvc =
+ Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+
+ // The Write Argument file needs to know where its libraries are, so
+ // just force the path variable
+ // For mac
+ var greDir = HandlerServiceTest._dirSvc.get("GreD", Components.interfaces.nsIFile);
+
+ envSvc.set("DYLD_LIBRARY_PATH", greDir.path);
+ // For Linux
+ envSvc.set("LD_LIBRARY_PATH", greDir.path);
+ //XXX: handle windows
+
+ // Now tell it where we want the file.
+ envSvc.set("WRITE_ARGUMENT_FILE", outFile.path);
+
+ var uri = ioService.newURI(kTestURI, null, null);
+
+ // Just check we've got these matching, if we haven't there's a problem
+ // with ascii spec or our test case.
+ do_check_eq(uri.asciiSpec, kExpectedURI);
+
+ localHandler.executable = exe;
+ localHandler.launchWithURI(uri);
+
+ do_test_pending();
+ do_timeout(1000, checkFile);
+}
diff --git a/uriloader/exthandler/tests/unit/xpcshell.ini b/uriloader/exthandler/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..e268ff9c3
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/xpcshell.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+head = head_handlerService.js
+tail = tail_handlerService.js
+run-sequentially = Bug 912235 - Intermittent failures
+
+[test_getTypeFromExtension_ext_to_type_mapping.js]
+[test_getTypeFromExtension_with_empty_Content_Type.js]
+[test_badMIMEType.js]
+[test_handlerService.js]
+support-files = mailcap
+# Bug 676997: test consistently fails on Android
+fail-if = os == "android"
+[test_punycodeURIs.js]
+# Bug 676997: test consistently fails on Android
+fail-if = os == "android"
diff --git a/uriloader/exthandler/tests/unit_ipc/test_encoding.js b/uriloader/exthandler/tests/unit_ipc/test_encoding.js
new file mode 100644
index 000000000..a2a00937a
--- /dev/null
+++ b/uriloader/exthandler/tests/unit_ipc/test_encoding.js
@@ -0,0 +1,231 @@
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://testing-common/MockRegistrar.js");
+
+do_get_profile();
+
+var DownloadListener = {
+ init: function () {
+ let obs = Services.obs;
+ obs.addObserver(this, "dl-done", true);
+ },
+
+ observe: function (subject, topic, data) {
+ this.onFinished(subject, topic, data);
+ },
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+DownloadListener.init();
+
+function HelperAppDlg() { }
+HelperAppDlg.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
+ show: function (launcher, ctx, reason, usePrivateUI) {
+ launcher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToFile;
+ launcher.launchWithApplication(null, false);
+ }
+}
+
+// Override the download-manager-ui to prevent anyone from trying to open
+// a window.
+function DownloadMgrUI() { }
+DownloadMgrUI.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
+ show: function (ir, aID, reason) { },
+
+ visible: false,
+
+ getAttention: function () { }
+}
+
+function AlertsSVC() { }
+AlertsSVC.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService]),
+ showAlertNotification: function (url, title, text, clickable, cookie, listener, name) { },
+}
+
+MockRegistrar.register("@mozilla.org/helperapplauncherdialog;1",
+ HelperAppDlg);
+MockRegistrar.register("@mozilla.org/download-manager-ui;1",
+ DownloadMgrUI);
+MockRegistrar.register("@mozilla.org/alerts-service;1",
+ AlertsSVC);
+
+function initChildTestEnv()
+{
+ sendCommand(' \
+ const Cc = Components.classes; \
+ const Ci = Components.interfaces; \
+ const Cr = Components.results; \
+ const Cu = Components.utils; \
+ Cu.import("resource://gre/modules/Services.jsm"); \
+ function WindowContext() { } \
+ \
+ WindowContext.prototype = { \
+ getInterface: function (iid) { \
+ if (iid.equals(Ci.nsIInterfaceRequestor) || \
+ iid.equals(Ci.nsIURIContentListener) || \
+ iid.equals(Ci.nsILoadGroup) || \
+ iid.equals(Ci.nsIDocumentLoader) || \
+ iid.equals(Ci.nsIDOMWindow)) \
+ return this; \
+ \
+ throw Cr.NS_ERROR_NO_INTERFACE; \
+ }, \
+ \
+ /* nsIURIContentListener */ \
+ onStartURIOpen: function (uri) { }, \
+ isPreferred: function (type, desiredtype) { return false; }, \
+ \
+ /* nsILoadGroup */ \
+ addRequest: function (request, context) { }, \
+ removeRequest: function (request, context, status) { } \
+ }; \
+ \
+ var ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);\
+ var uriloader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);\
+ ');
+}
+
+function testFinisher(endFunc) {
+ let ef = endFunc;
+ return function (file) {
+ ef(file);
+ runNextTest();
+ }
+}
+
+function runChildTestSet(set)
+{
+ DownloadListener.onFinished = testFinisher(set[2]);
+ sendCommand('\
+ let uri = ioservice.newURI("http://localhost:4444' + set[0] + '", null, null); \
+ let channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); \
+ uriloader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED, new WindowContext()); \
+ ');
+}
+
+var httpserver = null;
+var currentTest = 0;
+function runNextTest()
+{
+ if (currentTest == tests.length) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ let set = tests[currentTest++];
+ runChildTestSet(set);
+}
+
+const responseBody = [0x1f, 0x8b, 0x08, 0x00, 0x16, 0x5a, 0x8a, 0x48, 0x02,
+ 0x03, 0x2b, 0x49, 0x2d, 0x2e, 0xe1, 0x02, 0x00, 0xc6,
+ 0x35, 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00];
+
+/*
+ * First test: a file with Content-Type application/x-gzip and Content-Encoding gzip
+ * should not be decoded in a round-trip
+ */
+function testResponse1(metadata, response) {
+ response.setHeader("Content-Type", "application/x-gzip", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest1(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test1.gz") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ do_check_matches(str, responseBody);
+}
+
+/*
+ * Second test: a file with Content-Type text/html and Content-Encoding gzip
+ * should not be decoded in a round-trip, if its filename ends in ".gz".
+ * We specify a Content-disposition header to force it to be saved as a file.
+ */
+function testResponse2(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest2(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test2.gz") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ do_check_matches(str, responseBody);
+}
+
+function testResponse3(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest3(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test3.txt") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ let decodedBody = [ 116, 101, 115, 116, 10 ]; // 't','e','s','t','\n'
+ do_check_matches(str, decodedBody);
+}
+
+var tests = [
+ [ "/test1.gz", testResponse1, finishTest1 ],
+ [ "/test2.gz", testResponse2, finishTest2 ],
+ [ "/test3.txt", testResponse3, finishTest3 ],
+];
+
+function run_test() {
+// do_load_child_test_harness();
+ httpserver = new HttpServer();
+ httpserver.start(4444);
+ do_test_pending();
+
+ initChildTestEnv();
+
+ for (let set of tests)
+ httpserver.registerPathHandler(set[0], set[1]);
+
+ runNextTest();
+}
diff --git a/uriloader/exthandler/tests/unit_ipc/xpcshell.ini b/uriloader/exthandler/tests/unit_ipc/xpcshell.ini
new file mode 100644
index 000000000..962b93dec
--- /dev/null
+++ b/uriloader/exthandler/tests/unit_ipc/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head =
+tail =
+
+[test_encoding.js]
+# Bug 676995: test hangs consistently on Android
+# Bug 907732: thunderbird still uses legacy downloads manager.
+skip-if = (os == "android" || buildapp == '../mail')
diff --git a/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h
new file mode 100644
index 000000000..9d5ef31da
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nslocalhandlerappuikit_h_
+#define nslocalhandlerappuikit_h_
+
+#include "nsLocalHandlerApp.h"
+
+class nsLocalHandlerAppUIKit final : public nsLocalHandlerApp
+{
+public:
+ nsLocalHandlerAppUIKit()
+ {}
+ ~nsLocalHandlerAppUIKit()
+ {}
+
+ nsLocalHandlerAppUIKit(const char16_t* aName, nsIFile* aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable)
+ {}
+
+ nsLocalHandlerAppUIKit(const nsAString& aName, nsIFile* aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable)
+ {}
+
+
+ NS_IMETHOD LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* aWindowContext);
+};
+
+#endif /* nslocalhandlerappuikit_h_ */
diff --git a/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm
new file mode 100644
index 000000000..afa600721
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "nsLocalHandlerAppUIKit.h"
+#include "nsIURI.h"
+
+NS_IMETHODIMP
+nsLocalHandlerAppUIKit::LaunchWithURI(nsIURI* aURI,
+ nsIInterfaceRequestor* aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h
new file mode 100644
index 000000000..3f6bc8b42
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoUIKit_h_
+#define nsMIMEInfoUIKit_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoUIKit final : public nsMIMEInfoImpl
+{
+public:
+ explicit nsMIMEInfoUIKit(const nsACString& aMIMEType)
+ : nsMIMEInfoImpl(aMIMEType)
+ {}
+ nsMIMEInfoUIKit(const nsACString& aType, HandlerClass aClass)
+ : nsMIMEInfoImpl(aType, aClass)
+ {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+
+protected:
+ virtual nsresult LoadUriInternal(nsIURI* aURI);
+#ifdef DEBUG
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile)
+ {
+ NS_NOTREACHED("do not call this method, use LaunchWithFile");
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+ static nsresult OpenApplicationWithURI(nsIFile* aApplication,
+ const nsCString& aURI);
+};
+
+#endif
diff --git a/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm
new file mode 100644
index 000000000..4b950dffc
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoUIKit.h"
+#include "nsIFileURL.h"
+
+NS_IMETHODIMP
+nsMIMEInfoUIKit::LaunchWithFile(nsIFile* aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMIMEInfoUIKit::LoadUriInternal(nsIURI* aURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.h b/uriloader/exthandler/uikit/nsOSHelperAppService.h
new file mode 100644
index 000000000..1f33be0fb
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsOSHelperAppService.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and
+// is implemented on each platform. It contains platform specific code for
+// finding helper applications for a given mime type in addition to launching
+// those applications. This is the UIKit version.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsCOMPtr.h"
+
+class nsOSHelperAppService final : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme,
+ nsAString& _retval);
+
+ // method overrides --> used to hook the mime service into internet config....
+ NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType,
+ const nsACString& aFileExt,
+ nsIMIMEInfo** aMIMEInfo);
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
+ bool* found,
+ nsIHandlerInfo** _retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got
+ // out of the rdf data source. This can be a mac file
+ // spec, a unix path or a windows path depending on the
+ // platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual nsresult GetFileTokenForPath(const char16_t* platformAppPath,
+ nsIFile** aFile);
+
+ nsresult OSProtocolHandlerExists(const char* aScheme, bool* aHandlerExists);
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.mm b/uriloader/exthandler/uikit/nsOSHelperAppService.mm
new file mode 100644
index 000000000..dfee24b2d
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsOSHelperAppService.mm
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+
+nsOSHelperAppService::nsOSHelperAppService()
+ : nsExternalHelperAppService()
+{}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aProtocolScheme,
+ bool* aHandlerExists)
+{
+ *aHandlerExists = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme,
+ nsAString& _retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsOSHelperAppService::GetFileTokenForPath(const char16_t* aPlatformAppPath,
+ nsIFile** aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetFromTypeAndExtension(const nsACString& aType,
+ const nsACString& aFileExt,
+ nsIMIMEInfo** aMIMEInfo)
+{
+ return nsExternalHelperAppService::GetFromTypeAndExtension(aType,
+ aFileExt,
+ aMIMEInfo);
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ *aFound = false;
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
+ bool* found,
+ nsIHandlerInfo** _retval)
+{
+ *found = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/unix/nsExternalSharingAppService.h b/uriloader/exthandler/unix/nsExternalSharingAppService.h
new file mode 100644
index 000000000..3b1794b5e
--- /dev/null
+++ b/uriloader/exthandler/unix/nsExternalSharingAppService.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_EXTERNAL_SHARING_APP_SERVICE_H
+#define NS_EXTERNAL_SHARING_APP_SERVICE_H
+
+#include "nsIExternalSharingAppService.h"
+#include "nsAutoPtr.h"
+
+class ShareUiInterface;
+
+#define NS_EXTERNALSHARINGAPPSERVICE_CID \
+ {0xea782c90, 0xc6ec, 0x11df, \
+ {0xbd, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66, 0x74}}
+
+class nsExternalSharingAppService : public nsIExternalSharingAppService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALSHARINGAPPSERVICE
+
+ nsExternalSharingAppService();
+
+private:
+ ~nsExternalSharingAppService();
+
+protected:
+ nsAutoPtr<ShareUiInterface> mShareUi;
+};
+#endif
diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.cpp b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
new file mode 100644
index 000000000..bf71ae913
--- /dev/null
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGNOMERegistry.h"
+#include "nsString.h"
+#include "nsIComponentManager.h"
+#include "nsMIMEInfoUnix.h"
+#include "nsAutoPtr.h"
+#include "nsIGIOService.h"
+
+/* static */ bool
+nsGNOMERegistry::HandlerExists(const char *aProtocolScheme)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIGIOMimeApp> app;
+ return NS_SUCCEEDED(giovfs->GetAppForURIScheme(nsDependentCString(aProtocolScheme),
+ getter_AddRefs(app)));
+}
+
+// XXX Check HandlerExists() before calling LoadURL.
+
+/* static */ nsresult
+nsGNOMERegistry::LoadURL(nsIURI *aURL)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return giovfs->ShowURI(aURL);
+}
+
+/* static */ void
+nsGNOMERegistry::GetAppDescForScheme(const nsACString& aScheme,
+ nsAString& aDesc)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs)
+ return;
+
+ nsAutoCString name;
+ nsCOMPtr<nsIGIOMimeApp> app;
+ if (NS_FAILED(giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(app))))
+ return;
+
+ app->GetName(name);
+
+ CopyUTF8toUTF16(name, aDesc);
+}
+
+
+/* static */ already_AddRefed<nsMIMEInfoBase>
+nsGNOMERegistry::GetFromExtension(const nsACString& aFileExt)
+{
+ nsAutoCString mimeType;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return nullptr;
+ }
+
+ // Get the MIME type from the extension, then call GetFromType to
+ // fill in the MIMEInfo.
+ if (NS_FAILED(giovfs->GetMimeTypeFromExtension(aFileExt, mimeType)) ||
+ mimeType.EqualsLiteral("application/octet-stream")) {
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoBase> mi = GetFromType(mimeType);
+ if (mi) {
+ mi->AppendExtension(aFileExt);
+ }
+
+ return mi.forget();
+}
+
+/* static */ already_AddRefed<nsMIMEInfoBase>
+nsGNOMERegistry::GetFromType(const nsACString& aMIMEType)
+{
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
+ NS_ENSURE_TRUE(mimeInfo, nullptr);
+
+ nsAutoCString name;
+ nsAutoCString description;
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGIOMimeApp> gioHandlerApp;
+ if (NS_FAILED(giovfs->GetAppForMimeType(aMIMEType, getter_AddRefs(gioHandlerApp))) ||
+ !gioHandlerApp) {
+ return nullptr;
+ }
+ gioHandlerApp->GetName(name);
+ giovfs->GetDescriptionForMimeType(aMIMEType, description);
+
+ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(name));
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
+
+ return mimeInfo.forget();
+}
diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.h b/uriloader/exthandler/unix/nsGNOMERegistry.h
new file mode 100644
index 000000000..fe42ee622
--- /dev/null
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGNOMERegistry_h
+#define nsGNOMERegistry_h
+
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+
+class nsMIMEInfoBase;
+
+class nsGNOMERegistry
+{
+ public:
+ static bool HandlerExists(const char *aProtocolScheme);
+
+ static nsresult LoadURL(nsIURI *aURL);
+
+ static void GetAppDescForScheme(const nsACString& aScheme,
+ nsAString& aDesc);
+
+ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
+
+ static already_AddRefed<nsMIMEInfoBase> GetFromType(const nsACString& aMIMEType);
+};
+
+#endif // nsGNOMERegistry_h
diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
new file mode 100644
index 000000000..e51660887
--- /dev/null
+++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoUnix.h"
+#include "nsGNOMERegistry.h"
+#include "nsIGIOService.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsAutoPtr.h"
+#ifdef MOZ_ENABLE_DBUS
+#include "nsDBusHandlerApp.h"
+#endif
+
+nsresult
+nsMIMEInfoUnix::LoadUriInternal(nsIURI * aURI)
+{
+ return nsGNOMERegistry::LoadURL(aURI);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoUnix::GetHasDefaultHandler(bool *_retval)
+{
+ // if mDefaultApplication is set, it means the application has been set from
+ // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to
+ // give the GNOME answer.
+ if (mDefaultApplication)
+ return nsMIMEInfoImpl::GetHasDefaultHandler(_retval);
+
+ *_retval = false;
+
+ if (mClass == eProtocolInfo) {
+ *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get());
+ } else {
+ RefPtr<nsMIMEInfoBase> mimeInfo = nsGNOMERegistry::GetFromType(mSchemeOrType);
+ if (!mimeInfo) {
+ nsAutoCString ext;
+ nsresult rv = GetPrimaryExtension(ext);
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo = nsGNOMERegistry::GetFromExtension(ext);
+ }
+ }
+ if (mimeInfo)
+ *_retval = true;
+ }
+
+ if (*_retval)
+ return NS_OK;
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForFile(QUrl(), QString(mSchemeOrType.get()));
+ if (action.isValid()) {
+ *_retval = true;
+ return NS_OK;
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile *aFile)
+{
+ // if mDefaultApplication is set, it means the application has been set from
+ // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to
+ // give the GNOME answer.
+ if (mDefaultApplication)
+ return nsMIMEInfoImpl::LaunchDefaultWithFile(aFile);
+
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(nativePath.get()));
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForFile(uri, QString(mSchemeOrType.get()));
+ if (action.isValid()) {
+ action.trigger();
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+#endif
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // nsGIOMimeApp->Launch wants a URI string instead of local file
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioservice = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = ioservice->NewFileURI(aFile, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString uriSpec;
+ uri->GetSpec(uriSpec);
+
+ nsCOMPtr<nsIGIOMimeApp> app;
+ if (NS_FAILED(giovfs->GetAppForMimeType(mSchemeOrType, getter_AddRefs(app))) || !app) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return app->Launch(uriSpec);
+}
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+NS_IMETHODIMP
+nsMIMEInfoUnix::GetPossibleApplicationHandlers(nsIMutableArray ** aPossibleAppHandlers)
+{
+ if (!mPossibleApplications) {
+ mPossibleApplications = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mPossibleApplications)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ QList<ContentAction::Action> actions =
+ ContentAction::Action::actionsForFile(QUrl(), QString(mSchemeOrType.get()));
+
+ for (int i = 0; i < actions.size(); ++i) {
+ nsContentHandlerApp* app =
+ new nsContentHandlerApp(nsString((char16_t*)actions[i].name().data()),
+ mSchemeOrType, actions[i]);
+ mPossibleApplications->AppendElement(app, false);
+ }
+ }
+
+ *aPossibleAppHandlers = mPossibleApplications;
+ NS_ADDREF(*aPossibleAppHandlers);
+ return NS_OK;
+}
+#endif
+
diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.h b/uriloader/exthandler/unix/nsMIMEInfoUnix.h
new file mode 100644
index 000000000..65f6d996d
--- /dev/null
+++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoUnix_h_
+#define nsMIMEInfoUnix_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoUnix : public nsMIMEInfoImpl
+{
+public:
+ explicit nsMIMEInfoUnix(const char *aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
+ explicit nsMIMEInfoUnix(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
+ nsMIMEInfoUnix(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoImpl(aType, aClass) {}
+ static bool HandlerExists(const char *aProtocolScheme);
+
+protected:
+ NS_IMETHOD GetHasDefaultHandler(bool *_retval);
+
+ virtual nsresult LoadUriInternal(nsIURI *aURI);
+
+ virtual nsresult LaunchDefaultWithFile(nsIFile *aFile);
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ NS_IMETHOD GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleAppHandlers);
+#endif
+};
+
+#endif // nsMIMEInfoUnix_h_
diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
new file mode 100644
index 000000000..84e1cf5b0
--- /dev/null
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
@@ -0,0 +1,1537 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+#include <contentaction/contentaction.h>
+#include <QString>
+#endif
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoUnix.h"
+#ifdef MOZ_WIDGET_GTK
+#include "nsGNOMERegistry.h"
+#endif
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIFileStreams.h"
+#include "nsILineInputStream.h"
+#include "nsIFile.h"
+#include "nsIProcess.h"
+#include "nsNetCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "prenv.h" // for PR_GetEnv()
+#include "nsAutoPtr.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#define LOG(args) MOZ_LOG(mLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(mLog, mozilla::LogLevel::Debug)
+
+static nsresult
+FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
+ const nsAString::const_iterator& aEnd_iter);
+static nsresult
+ParseMIMEType(const nsAString::const_iterator& aStart_iter,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ const nsAString::const_iterator& aEnd_iter);
+
+inline bool
+IsNetscapeFormat(const nsACString& aBuffer);
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+ mode_t mask = umask(0777);
+ umask(mask);
+ mPermissions = 0666 & ~mask;
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+/*
+ * Take a command with all the mailcap escapes in it and unescape it
+ * Ideally this needs the mime type, mime type options, and location of the
+ * temporary file, but this last can't be got from here
+ */
+// static
+nsresult
+nsOSHelperAppService::UnescapeCommand(const nsAString& aEscapedCommand,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsACString& aUnEscapedCommand) {
+ LOG(("-- UnescapeCommand"));
+ LOG(("Command to escape: '%s'\n",
+ NS_LossyConvertUTF16toASCII(aEscapedCommand).get()));
+ // XXX This function will need to get the mime type and various stuff like that being passed in to work properly
+
+ LOG(("UnescapeCommand really needs some work -- it should actually do some unescaping\n"));
+
+ CopyUTF16toUTF8(aEscapedCommand, aUnEscapedCommand);
+ LOG(("Escaped command: '%s'\n",
+ PromiseFlatCString(aUnEscapedCommand).get()));
+ return NS_OK;
+}
+
+/* Put aSemicolon_iter at the first non-escaped semicolon after
+ * aStart_iter but before aEnd_iter
+ */
+
+static nsresult
+FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
+ const nsAString::const_iterator& aEnd_iter) {
+ bool semicolonFound = false;
+ while (aSemicolon_iter != aEnd_iter && !semicolonFound) {
+ switch(*aSemicolon_iter) {
+ case '\\':
+ aSemicolon_iter.advance(2);
+ break;
+ case ';':
+ semicolonFound = true;
+ break;
+ default:
+ ++aSemicolon_iter;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+static nsresult
+ParseMIMEType(const nsAString::const_iterator& aStart_iter,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ const nsAString::const_iterator& aEnd_iter) {
+ nsAString::const_iterator iter(aStart_iter);
+
+ // skip leading whitespace
+ while (iter != aEnd_iter && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMajorTypeStart = iter;
+
+ // find major/minor separator ('/')
+ while (iter != aEnd_iter && *iter != '/') {
+ ++iter;
+ }
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMajorTypeEnd = iter;
+
+ // skip '/'
+ ++iter;
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMinorTypeStart = iter;
+
+ // find end of minor type, delimited by whitespace or ';'
+ while (iter != aEnd_iter && !nsCRT::IsAsciiSpace(*iter) && *iter != ';') {
+ ++iter;
+ }
+
+ aMinorTypeEnd = iter;
+
+ return NS_OK;
+}
+
+// static
+nsresult
+nsOSHelperAppService::GetFileLocation(const char* aPrefName,
+ const char* aEnvVarName,
+ nsAString& aFileLocation) {
+ LOG(("-- GetFileLocation. Pref: '%s' EnvVar: '%s'\n",
+ aPrefName,
+ aEnvVarName));
+ NS_PRECONDITION(aPrefName, "Null pref name passed; don't do that!");
+
+ aFileLocation.Truncate();
+ /* The lookup order is:
+ 1) user pref
+ 2) env var
+ 3) pref
+ */
+ NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
+
+ /*
+ If we have an env var we should check whether the pref is a user
+ pref. If we do not, we don't care.
+ */
+ if (Preferences::HasUserValue(aPrefName) &&
+ NS_SUCCEEDED(Preferences::GetString(aPrefName, &aFileLocation))) {
+ return NS_OK;
+ }
+
+ if (aEnvVarName && *aEnvVarName) {
+ char* prefValue = PR_GetEnv(aEnvVarName);
+ if (prefValue && *prefValue) {
+ // the pref is in the system charset and it's a filepath... The
+ // natural way to do the charset conversion is by just initing
+ // an nsIFile with the native path and asking it for the Unicode
+ // version.
+ nsresult rv;
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->InitWithNativePath(nsDependentCString(prefValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->GetPath(aFileLocation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+ }
+
+ return Preferences::GetString(aPrefName, &aFileLocation);
+}
+
+
+/* Get the mime.types file names from prefs and look up info in them
+ based on extension */
+// static
+nsresult
+nsOSHelperAppService::LookUpTypeAndDescription(const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription,
+ bool aUserData) {
+ LOG(("-- LookUpTypeAndDescription for extension '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFileExtension).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mimeFileName;
+
+ const char* filenamePref = aUserData ?
+ "helpers.private_mime_types_file" : "helpers.global_mime_types_file";
+
+ rv = GetFileLocation(filenamePref, nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetTypeAndDescriptionFromMimetypesFile(mimeFileName,
+ aFileExtension,
+ aMajorType,
+ aMinorType,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return rv;
+}
+
+inline bool
+IsNetscapeFormat(const nsACString& aBuffer) {
+ return StringBeginsWith(aBuffer, NS_LITERAL_CSTRING("#--Netscape Communications Corporation MIME Information")) ||
+ StringBeginsWith(aBuffer, NS_LITERAL_CSTRING("#--MCOM MIME Information"));
+}
+
+/*
+ * Create a file stream and line input stream for the filename.
+ * Leaves the first line of the file in aBuffer and sets the format to
+ * true for netscape files and false for normail ones
+ */
+// static
+nsresult
+nsOSHelperAppService::CreateInputStream(const nsAString& aFilename,
+ nsIFileInputStream ** aFileInputStream,
+ nsILineInputStream ** aLineInputStream,
+ nsACString& aBuffer,
+ bool * aNetscapeFormat,
+ bool * aMore) {
+ LOG(("-- CreateInputStream"));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = file->InitWithPath(aFilename);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFileInputStream> fileStream(do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = fileStream->Init(file, -1, -1, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Interface trouble in stream land!"));
+ return rv;
+ }
+
+ rv = lineStream->ReadLine(aBuffer, aMore);
+ if (NS_FAILED(rv)) {
+ fileStream->Close();
+ return rv;
+ }
+
+ *aNetscapeFormat = IsNetscapeFormat(aBuffer);
+
+ *aFileInputStream = fileStream;
+ NS_ADDREF(*aFileInputStream);
+ *aLineInputStream = lineStream;
+ NS_ADDREF(*aLineInputStream);
+
+ return NS_OK;
+}
+
+/* Open the file, read the first line, decide what type of file it is,
+ then get info based on extension */
+// static
+nsresult
+nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription) {
+ LOG(("-- GetTypeAndDescriptionFromMimetypesFile\n"));
+ LOG(("Getting type and description from types file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using extension '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFileExtension).get()));
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFileInputStream> mimeFile;
+ nsCOMPtr<nsILineInputStream> mimeTypes;
+ bool netscapeFormat;
+ nsAutoString buf;
+ nsAutoCString cBuf;
+ bool more = false;
+ rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
+ cBuf, &netscapeFormat, &more);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString extensions;
+ nsString entry;
+ entry.SetCapacity(100);
+ nsAString::const_iterator majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ descriptionStart, descriptionEnd;
+
+ do {
+ CopyASCIItoUTF16(cBuf, buf);
+ // read through, building up an entry. If we finish an entry, check for
+ // a match and return out of the loop if we match
+
+ // skip comments and empty lines
+ if (!buf.IsEmpty() && buf.First() != '#') {
+ entry.Append(buf);
+ if (entry.Last() == '\\') {
+ entry.Truncate(entry.Length() - 1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+ if (netscapeFormat) {
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like RealPlayer appending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 106381.
+ LOG(("Bogus entry; trying 'normal' mode\n"));
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ } else {
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like StarOffice prepending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 136670.
+ LOG(("Bogus entry; trying 'Netscape' mode\n"));
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) { // entry parses
+ nsAString::const_iterator start, end;
+ extensions.BeginReading(start);
+ extensions.EndReading(end);
+ nsAString::const_iterator iter(start);
+
+ while (start != end) {
+ FindCharInReadable(',', iter, end);
+ if (Substring(start, iter).Equals(aFileExtension,
+ nsCaseInsensitiveStringComparator())) {
+ // it's a match. Assign the type and description and run
+ aMajorType.Assign(Substring(majorTypeStart, majorTypeEnd));
+ aMinorType.Assign(Substring(minorTypeStart, minorTypeEnd));
+ aDescription.Assign(Substring(descriptionStart, descriptionEnd));
+ mimeFile->Close();
+ return NS_OK;
+ }
+ if (iter != end) {
+ ++iter;
+ }
+ start = iter;
+ }
+ } else {
+ LOG(("Failed to parse entry: %s\n", NS_LossyConvertUTF16toASCII(entry).get()));
+ }
+ // truncate the entry for the next iteration
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ // read the next line
+ rv = mimeTypes->ReadLine(cBuf, &more);
+ } while (NS_SUCCEEDED(rv));
+
+ mimeFile->Close();
+ return rv;
+}
+
+/* Get the mime.types file names from prefs and look up info in them
+ based on mimetype */
+// static
+nsresult
+nsOSHelperAppService::LookUpExtensionsAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription) {
+ LOG(("-- LookUpExtensionsAndDescription for type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mimeFileName;
+
+ rv = GetFileLocation("helpers.private_mime_types_file", nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetExtensionsAndDescriptionFromMimetypesFile(mimeFileName,
+ aMajorType,
+ aMinorType,
+ aFileExtensions,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(rv) || aFileExtensions.IsEmpty()) {
+ rv = GetFileLocation("helpers.global_mime_types_file",
+ nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetExtensionsAndDescriptionFromMimetypesFile(mimeFileName,
+ aMajorType,
+ aMinorType,
+ aFileExtensions,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ return rv;
+}
+
+/* Open the file, read the first line, decide what type of file it is,
+ then get info based on extension */
+// static
+nsresult
+nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription) {
+ LOG(("-- GetExtensionsAndDescriptionFromMimetypesFile\n"));
+ LOG(("Getting extensions and description from types file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFileInputStream> mimeFile;
+ nsCOMPtr<nsILineInputStream> mimeTypes;
+ bool netscapeFormat;
+ nsAutoCString cBuf;
+ nsAutoString buf;
+ bool more = false;
+ rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
+ cBuf, &netscapeFormat, &more);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString extensions;
+ nsString entry;
+ entry.SetCapacity(100);
+ nsAString::const_iterator majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ descriptionStart, descriptionEnd;
+
+ do {
+ CopyASCIItoUTF16(cBuf, buf);
+ // read through, building up an entry. If we finish an entry, check for
+ // a match and return out of the loop if we match
+
+ // skip comments and empty lines
+ if (!buf.IsEmpty() && buf.First() != '#') {
+ entry.Append(buf);
+ if (entry.Last() == '\\') {
+ entry.Truncate(entry.Length() - 1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+ if (netscapeFormat) {
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like RealPlayer appending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 106381.
+ LOG(("Bogus entry; trying 'normal' mode\n"));
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ } else {
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart,
+ minorTypeEnd, extensions,
+ descriptionStart, descriptionEnd);
+
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like StarOffice prepending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 136670.
+ LOG(("Bogus entry; trying 'Netscape' mode\n"));
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) &&
+ Substring(majorTypeStart,
+ majorTypeEnd).Equals(aMajorType,
+ nsCaseInsensitiveStringComparator())&&
+ Substring(minorTypeStart,
+ minorTypeEnd).Equals(aMinorType,
+ nsCaseInsensitiveStringComparator())) {
+ // it's a match
+ aFileExtensions.Assign(extensions);
+ aDescription.Assign(Substring(descriptionStart, descriptionEnd));
+ mimeFile->Close();
+ return NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("Failed to parse entry: %s\n", NS_LossyConvertUTF16toASCII(entry).get()));
+ }
+
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ // read the next line
+ rv = mimeTypes->ReadLine(cBuf, &more);
+ } while (NS_SUCCEEDED(rv));
+
+ mimeFile->Close();
+ return rv;
+}
+
+/*
+ * This parses a Netscape format mime.types entry. There are two
+ * possible formats:
+ *
+ * type=foo/bar; options exts="baz" description="Some type"
+ *
+ * and
+ *
+ * type=foo/bar; options description="Some type" exts="baz"
+ */
+// static
+nsresult
+nsOSHelperAppService::ParseNetscapeMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd) {
+ LOG(("-- ParseNetscapeMIMETypesEntry\n"));
+ NS_ASSERTION(!aEntry.IsEmpty(), "Empty Netscape MIME types entry being parsed.");
+
+ nsAString::const_iterator start_iter, end_iter, match_start, match_end;
+
+ aEntry.BeginReading(start_iter);
+ aEntry.EndReading(end_iter);
+
+ // skip trailing whitespace
+ do {
+ --end_iter;
+ } while (end_iter != start_iter &&
+ nsCRT::IsAsciiSpace(*end_iter));
+ // if we're pointing to a quote, don't advance -- we don't want to
+ // include the quote....
+ if (*end_iter != '"')
+ ++end_iter;
+ match_start = start_iter;
+ match_end = end_iter;
+
+ // Get the major and minor types
+ // First the major type
+ if (! FindInReadable(NS_LITERAL_STRING("type="), match_start, match_end)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ match_start = match_end;
+
+ while (match_end != end_iter &&
+ *match_end != '/') {
+ ++match_end;
+ }
+ if (match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMajorTypeStart = match_start;
+ aMajorTypeEnd = match_end;
+
+ // now the minor type
+ if (++match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ match_start = match_end;
+
+ while (match_end != end_iter &&
+ !nsCRT::IsAsciiSpace(*match_end) &&
+ *match_end != ';') {
+ ++match_end;
+ }
+ if (match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMinorTypeStart = match_start;
+ aMinorTypeEnd = match_end;
+
+ // ignore everything up to the end of the mime type from here on
+ start_iter = match_end;
+
+ // get the extensions
+ match_start = match_end;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
+ nsAString::const_iterator extStart, extEnd;
+
+ if (match_end == end_iter ||
+ (*match_end == '"' && ++match_end == end_iter)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ extStart = match_end;
+ match_start = extStart;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
+ // exts= before desc=, so we have to find the actual end of the extensions
+ extEnd = match_start;
+ if (extEnd == extStart) {
+ return NS_ERROR_FAILURE;
+ }
+
+ do {
+ --extEnd;
+ } while (extEnd != extStart &&
+ nsCRT::IsAsciiSpace(*extEnd));
+
+ if (extEnd != extStart && *extEnd == '"') {
+ --extEnd;
+ }
+ } else {
+ // desc= before exts=, so we can use end_iter as the end of the extensions
+ extEnd = end_iter;
+ }
+ aExtensions = Substring(extStart, extEnd);
+ } else {
+ // no extensions
+ aExtensions.Truncate();
+ }
+
+ // get the description
+ match_start = start_iter;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
+ aDescriptionStart = match_end;
+ match_start = aDescriptionStart;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
+ // exts= after desc=, so have to find actual end of description
+ aDescriptionEnd = match_start;
+ if (aDescriptionEnd == aDescriptionStart) {
+ return NS_ERROR_FAILURE;
+ }
+
+ do {
+ --aDescriptionEnd;
+ } while (aDescriptionEnd != aDescriptionStart &&
+ nsCRT::IsAsciiSpace(*aDescriptionEnd));
+
+ if (aDescriptionStart != aDescriptionStart && *aDescriptionEnd == '"') {
+ --aDescriptionEnd;
+ }
+ } else {
+ // desc= after exts=, so use end_iter for the description end
+ aDescriptionEnd = end_iter;
+ }
+ } else {
+ // no description
+ aDescriptionStart = start_iter;
+ aDescriptionEnd = start_iter;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * This parses a normal format mime.types entry. The format is:
+ *
+ * major/minor ext1 ext2 ext3
+ */
+// static
+nsresult
+nsOSHelperAppService::ParseNormalMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd) {
+ LOG(("-- ParseNormalMIMETypesEntry\n"));
+ NS_ASSERTION(!aEntry.IsEmpty(), "Empty Normal MIME types entry being parsed.");
+
+ nsAString::const_iterator start_iter, end_iter, iter;
+
+ aEntry.BeginReading(start_iter);
+ aEntry.EndReading(end_iter);
+
+ // no description
+ aDescriptionStart = start_iter;
+ aDescriptionEnd = start_iter;
+
+ // skip leading whitespace
+ while (start_iter != end_iter && nsCRT::IsAsciiSpace(*start_iter)) {
+ ++start_iter;
+ }
+ if (start_iter == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+ // skip trailing whitespace
+ do {
+ --end_iter;
+ } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
+
+ ++end_iter; // point to first whitespace char (or to end of string)
+ iter = start_iter;
+
+ // get the major type
+ if (! FindCharInReadable('/', iter, end_iter))
+ return NS_ERROR_FAILURE;
+
+ nsAString::const_iterator equals_sign_iter(start_iter);
+ if (FindCharInReadable('=', equals_sign_iter, iter))
+ return NS_ERROR_FAILURE; // see bug 136670
+
+ aMajorTypeStart = start_iter;
+ aMajorTypeEnd = iter;
+
+ // get the minor type
+ if (++iter == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+ start_iter = iter;
+
+ while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+ aMinorTypeStart = start_iter;
+ aMinorTypeEnd = iter;
+
+ // get the extensions
+ aExtensions.Truncate();
+ while (iter != end_iter) {
+ while (iter != end_iter && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ start_iter = iter;
+ while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+ aExtensions.Append(Substring(start_iter, iter));
+ if (iter != end_iter) { // not the last extension
+ aExtensions.Append(char16_t(','));
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+nsOSHelperAppService::LookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags) {
+
+ // The mailcap lookup is two-pass to handle the case of mailcap files
+ // that have something like:
+ //
+ // text/*; emacs %s
+ // text/rtf; soffice %s
+ //
+ // in that order. We want to pick up "soffice" for text/rtf in such cases
+ nsresult rv = DoLookUpHandlerAndDescription(aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ true);
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ false);
+ }
+
+ // maybe we have an entry for "aMajorType/*"?
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ NS_LITERAL_STRING("*"),
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ true);
+ }
+
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ NS_LITERAL_STRING("*"),
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ false);
+ }
+
+ return rv;
+}
+
+// static
+nsresult
+nsOSHelperAppService::DoLookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags,
+ bool aUserData) {
+ LOG(("-- LookUpHandlerAndDescription for type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mailcapFileName;
+
+ const char * filenamePref = aUserData ?
+ "helpers.private_mailcap_file" : "helpers.global_mailcap_file";
+ const char * filenameEnvVar = aUserData ?
+ "PERSONAL_MAILCAP" : "MAILCAP";
+
+ rv = GetFileLocation(filenamePref, filenameEnvVar, mailcapFileName);
+ if (NS_SUCCEEDED(rv) && !mailcapFileName.IsEmpty()) {
+ rv = GetHandlerAndDescriptionFromMailcapFile(mailcapFileName,
+ aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return rv;
+}
+
+// static
+nsresult
+nsOSHelperAppService::GetHandlerAndDescriptionFromMailcapFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags) {
+
+ LOG(("-- GetHandlerAndDescriptionFromMailcapFile\n"));
+ LOG(("Getting handler and description from mailcap file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+
+ nsresult rv = NS_OK;
+ bool more = false;
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = file->InitWithPath(aFilename);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFileInputStream> mailcapFile(do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = mailcapFile->Init(file, -1, -1, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsILineInputStream> mailcap (do_QueryInterface(mailcapFile, &rv));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Interface trouble in stream land!"));
+ return rv;
+ }
+
+ nsString entry, buffer;
+ nsAutoCString cBuffer;
+ entry.SetCapacity(128);
+ cBuffer.SetCapacity(80);
+ rv = mailcap->ReadLine(cBuffer, &more);
+ if (NS_FAILED(rv)) {
+ mailcapFile->Close();
+ return rv;
+ }
+
+ do { // return on end-of-file in the loop
+
+ CopyASCIItoUTF16(cBuffer, buffer);
+ if (!buffer.IsEmpty() && buffer.First() != '#') {
+ entry.Append(buffer);
+ if (entry.Last() == '\\') { // entry continues on next line
+ entry.Truncate(entry.Length()-1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry in entry. Check it for the type
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+
+ nsAString::const_iterator semicolon_iter,
+ start_iter, end_iter,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd;
+ entry.BeginReading(start_iter);
+ entry.EndReading(end_iter);
+ semicolon_iter = start_iter;
+ FindSemicolon(semicolon_iter, end_iter);
+ if (semicolon_iter != end_iter) { // we have something resembling a valid entry
+ rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd, semicolon_iter);
+ if (NS_SUCCEEDED(rv) &&
+ Substring(majorTypeStart,
+ majorTypeEnd).Equals(aMajorType,
+ nsCaseInsensitiveStringComparator()) &&
+ Substring(minorTypeStart,
+ minorTypeEnd).Equals(aMinorType,
+ nsCaseInsensitiveStringComparator())) { // we have a match
+ bool match = true;
+ ++semicolon_iter; // point at the first char past the semicolon
+ start_iter = semicolon_iter; // handler string starts here
+ FindSemicolon(semicolon_iter, end_iter);
+ while (start_iter != semicolon_iter &&
+ nsCRT::IsAsciiSpace(*start_iter)) {
+ ++start_iter;
+ }
+
+ LOG(("The real handler is: '%s'\n",
+ NS_LossyConvertUTF16toASCII(Substring(start_iter,
+ semicolon_iter)).get()));
+
+ // XXX ugly hack. Just grab the executable name
+ nsAString::const_iterator end_handler_iter = semicolon_iter;
+ nsAString::const_iterator end_executable_iter = start_iter;
+ while (end_executable_iter != end_handler_iter &&
+ !nsCRT::IsAsciiSpace(*end_executable_iter)) {
+ ++end_executable_iter;
+ }
+ // XXX End ugly hack
+
+ aHandler = Substring(start_iter, end_executable_iter);
+
+ nsAString::const_iterator start_option_iter, end_optionname_iter, equal_sign_iter;
+ bool equalSignFound;
+ while (match &&
+ semicolon_iter != end_iter &&
+ ++semicolon_iter != end_iter) { // there are options left and we still match
+ start_option_iter = semicolon_iter;
+ // skip over leading whitespace
+ while (start_option_iter != end_iter &&
+ nsCRT::IsAsciiSpace(*start_option_iter)) {
+ ++start_option_iter;
+ }
+ if (start_option_iter == end_iter) { // nothing actually here
+ break;
+ }
+ semicolon_iter = start_option_iter;
+ FindSemicolon(semicolon_iter, end_iter);
+ equal_sign_iter = start_option_iter;
+ equalSignFound = false;
+ while (equal_sign_iter != semicolon_iter && !equalSignFound) {
+ switch(*equal_sign_iter) {
+ case '\\':
+ equal_sign_iter.advance(2);
+ break;
+ case '=':
+ equalSignFound = true;
+ break;
+ default:
+ ++equal_sign_iter;
+ break;
+ }
+ }
+ end_optionname_iter = start_option_iter;
+ // find end of option name
+ while (end_optionname_iter != equal_sign_iter &&
+ !nsCRT::IsAsciiSpace(*end_optionname_iter)) {
+ ++end_optionname_iter;
+ }
+ nsDependentSubstring optionName(start_option_iter, end_optionname_iter);
+ if (equalSignFound) {
+ // This is an option that has a name and value
+ if (optionName.EqualsLiteral("description")) {
+ aDescription = Substring(++equal_sign_iter, semicolon_iter);
+ } else if (optionName.EqualsLiteral("x-mozilla-flags")) {
+ aMozillaFlags = Substring(++equal_sign_iter, semicolon_iter);
+ } else if (optionName.EqualsLiteral("test")) {
+ nsAutoCString testCommand;
+ rv = UnescapeCommand(Substring(++equal_sign_iter, semicolon_iter),
+ aMajorType,
+ aMinorType,
+ testCommand);
+ if (NS_FAILED(rv))
+ continue;
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ continue;
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ continue;
+ rv = file->InitWithNativePath(NS_LITERAL_CSTRING("/bin/sh"));
+ if (NS_FAILED(rv))
+ continue;
+ rv = process->Init(file);
+ if (NS_FAILED(rv))
+ continue;
+ const char *args[] = { "-c", testCommand.get() };
+ LOG(("Running Test: %s\n", testCommand.get()));
+ rv = process->Run(true, args, 2);
+ if (NS_FAILED(rv))
+ continue;
+ int32_t exitValue;
+ rv = process->GetExitValue(&exitValue);
+ if (NS_FAILED(rv))
+ continue;
+ LOG(("Exit code: %d\n", exitValue));
+ if (exitValue) {
+ match = false;
+ }
+ }
+ } else {
+ // This is an option that just has a name but no value (eg "copiousoutput")
+ if (optionName.EqualsLiteral("needsterminal")) {
+ match = false;
+ }
+ }
+ }
+
+
+ if (match) { // we did not fail any test clauses; all is good
+ // get out of here
+ mailcapFile->Close();
+ return NS_OK;
+ } else { // pretend that this match never happened
+ aDescription.Truncate();
+ aMozillaFlags.Truncate();
+ aHandler.Truncate();
+ }
+ }
+ }
+ // zero out the entry for the next cycle
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ rv = mailcap->ReadLine(cBuffer, &more);
+ } while (NS_SUCCEEDED(rv));
+ mailcapFile->Close();
+ return rv;
+}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ LOG(("-- nsOSHelperAppService::OSProtocolHandlerExists for '%s'\n",
+ aProtocolScheme));
+ *aHandlerExists = false;
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ // libcontentaction requires character ':' after scheme
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForScheme(QString(aProtocolScheme) + ':');
+
+ if (action.isValid())
+ *aHandlerExists = true;
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+ *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+#ifdef MOZ_WIDGET_GTK
+ nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
+ return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile)
+{
+ LOG(("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n",
+ NS_LossyConvertUTF16toASCII(platformAppPath).get()));
+ if (! *platformAppPath) { // empty filename--return error
+ NS_WARNING("Empty filename passed in.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // first check if the base class implementation finds anything
+ nsresult rv = nsExternalHelperAppService::GetFileTokenForPath(platformAppPath, aFile);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ // If the reason for failure was that the file doesn't exist, return too
+ // (because it means the path was absolute, and so that we shouldn't search in
+ // the path)
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return rv;
+
+ // If we get here, we really should have a relative path.
+ NS_ASSERTION(*platformAppPath != char16_t('/'), "Unexpected absolute path");
+
+ nsCOMPtr<nsIFile> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+
+ if (!localFile) return NS_ERROR_NOT_INITIALIZED;
+
+ bool exists = false;
+ // ugly hack. Walk the PATH variable...
+ char* unixpath = PR_GetEnv("PATH");
+ nsAutoCString path(unixpath);
+
+ const char* start_iter = path.BeginReading(start_iter);
+ const char* colon_iter = start_iter;
+ const char* end_iter = path.EndReading(end_iter);
+
+ while (start_iter != end_iter && !exists) {
+ while (colon_iter != end_iter && *colon_iter != ':') {
+ ++colon_iter;
+ }
+ localFile->InitWithNativePath(Substring(start_iter, colon_iter));
+ rv = localFile->AppendRelativePath(nsDependentString(platformAppPath));
+ // Failing AppendRelativePath is a bad thing - it should basically always
+ // succeed given a relative path. Show a warning if it does fail.
+ // To prevent infinite loops when it does fail, return at this point.
+ NS_ENSURE_SUCCESS(rv, rv);
+ localFile->Exists(&exists);
+ if (!exists) {
+ if (colon_iter == end_iter) {
+ break;
+ }
+ ++colon_iter;
+ start_iter = colon_iter;
+ }
+ }
+
+ if (exists) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aFile = localFile;
+ NS_IF_ADDREF(*aFile);
+
+ return rv;
+}
+
+already_AddRefed<nsMIMEInfoBase>
+nsOSHelperAppService::GetFromExtension(const nsCString& aFileExt) {
+ // if the extension is empty, return immediately
+ if (aFileExt.IsEmpty())
+ return nullptr;
+
+ LOG(("Here we do an extension lookup for '%s'\n", aFileExt.get()));
+
+ nsAutoString majorType, minorType,
+ mime_types_description, mailcap_description,
+ handler, mozillaFlags;
+
+ nsresult rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt),
+ majorType,
+ minorType,
+ mime_types_description,
+ true);
+
+ if (NS_FAILED(rv) || majorType.IsEmpty()) {
+
+#ifdef MOZ_WIDGET_GTK
+ LOG(("Looking in GNOME registry\n"));
+ RefPtr<nsMIMEInfoBase> gnomeInfo =
+ nsGNOMERegistry::GetFromExtension(aFileExt);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry\n"));
+ return gnomeInfo.forget();
+ }
+#endif
+
+ rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt),
+ majorType,
+ minorType,
+ mime_types_description,
+ false);
+ }
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ NS_LossyConvertUTF16toASCII asciiMajorType(majorType);
+ NS_LossyConvertUTF16toASCII asciiMinorType(minorType);
+
+ LOG(("Type/Description results: majorType='%s', minorType='%s', description='%s'\n",
+ asciiMajorType.get(),
+ asciiMinorType.get(),
+ NS_LossyConvertUTF16toASCII(mime_types_description).get()));
+
+ if (majorType.IsEmpty() && minorType.IsEmpty()) {
+ // we didn't get a type mapping, so we can't do anything useful
+ return nullptr;
+ }
+
+ nsAutoCString mimeType(asciiMajorType + NS_LITERAL_CSTRING("/") + asciiMinorType);
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimeType);
+
+ mimeInfo->AppendExtension(aFileExt);
+ rv = LookUpHandlerAndDescription(majorType, minorType,
+ handler, mailcap_description,
+ mozillaFlags);
+ LOG(("Handler/Description results: handler='%s', description='%s', mozillaFlags='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get(),
+ NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
+ mailcap_description.Trim(" \t\"");
+ mozillaFlags.Trim(" \t");
+ if (!mime_types_description.IsEmpty()) {
+ mimeInfo->SetDescription(mime_types_description);
+ } else {
+ mimeInfo->SetDescription(mailcap_description);
+ }
+
+ if (NS_SUCCEEDED(rv) && handler.IsEmpty()) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> handlerFile;
+ rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
+
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo->SetDefaultApplication(handlerFile);
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDefaultDescription(handler);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ return mimeInfo.forget();
+}
+
+already_AddRefed<nsMIMEInfoBase>
+nsOSHelperAppService::GetFromType(const nsCString& aMIMEType) {
+ // if the type is empty, return immediately
+ if (aMIMEType.IsEmpty())
+ return nullptr;
+
+ LOG(("Here we do a mimetype lookup for '%s'\n", aMIMEType.get()));
+
+ // extract the major and minor types
+ NS_ConvertASCIItoUTF16 mimeType(aMIMEType);
+ nsAString::const_iterator start_iter, end_iter,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd;
+
+ mimeType.BeginReading(start_iter);
+ mimeType.EndReading(end_iter);
+
+ // XXX FIXME: add typeOptions parsing in here
+ nsresult rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd, end_iter);
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsDependentSubstring majorType(majorTypeStart, majorTypeEnd);
+ nsDependentSubstring minorType(minorTypeStart, minorTypeEnd);
+
+ // First check the user's private mailcap file
+ nsAutoString mailcap_description, handler, mozillaFlags;
+ DoLookUpHandlerAndDescription(majorType,
+ minorType,
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ true);
+
+ LOG(("Private Handler/Description results: handler='%s', description='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get()));
+
+ // Now look up our extensions
+ nsAutoString extensions, mime_types_description;
+ LookUpExtensionsAndDescription(majorType,
+ minorType,
+ extensions,
+ mime_types_description);
+
+#ifdef MOZ_WIDGET_GTK
+ if (handler.IsEmpty()) {
+ RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry without extensions; setting them "
+ "to %s\n", NS_LossyConvertUTF16toASCII(extensions).get()));
+
+ NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?");
+ gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
+ return gnomeInfo.forget();
+ }
+ }
+#endif
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ minorType,
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ false);
+ }
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ NS_LITERAL_STRING("*"),
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ true);
+ }
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ NS_LITERAL_STRING("*"),
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ false);
+ }
+
+ LOG(("Handler/Description results: handler='%s', description='%s', mozillaFlags='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get(),
+ NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
+
+ mailcap_description.Trim(" \t\"");
+ mozillaFlags.Trim(" \t");
+
+ if (handler.IsEmpty() && extensions.IsEmpty() &&
+ mailcap_description.IsEmpty() && mime_types_description.IsEmpty()) {
+ // No real useful info
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
+
+ mimeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
+ if (! mime_types_description.IsEmpty()) {
+ mimeInfo->SetDescription(mime_types_description);
+ } else {
+ mimeInfo->SetDescription(mailcap_description);
+ }
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+ nsCOMPtr<nsIFile> handlerFile;
+ if (!handler.IsEmpty()) {
+ rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo->SetDefaultApplication(handlerFile);
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDefaultDescription(handler);
+ } else {
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ return mimeInfo.forget();
+}
+
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aType,
+ const nsACString& aFileExt,
+ bool *aFound) {
+ *aFound = true;
+ RefPtr<nsMIMEInfoBase> retval = GetFromType(PromiseFlatCString(aType));
+ bool hasDefault = false;
+ if (retval)
+ retval->GetHasDefaultHandler(&hasDefault);
+ if (!retval || !hasDefault) {
+ RefPtr<nsMIMEInfoBase> miByExt = GetFromExtension(PromiseFlatCString(aFileExt));
+ // If we had no extension match, but a type match, use that
+ if (!miByExt && retval)
+ return retval.forget();
+ // If we had an extension match but no type match, set the mimetype and use
+ // it
+ if (!retval && miByExt) {
+ if (!aType.IsEmpty())
+ miByExt->SetMIMEType(aType);
+ miByExt.swap(retval);
+
+ return retval.forget();
+ }
+ // If we got nothing, make a new mimeinfo
+ if (!retval) {
+ *aFound = false;
+ retval = new nsMIMEInfoUnix(aType);
+ if (retval) {
+ if (!aFileExt.IsEmpty())
+ retval->AppendExtension(aFileExt);
+ }
+
+ return retval.forget();
+ }
+
+ // Copy the attributes of retval (mimeinfo from type) onto miByExt, to
+ // return it
+ // but reset to just collected mDefaultAppDescription (from ext)
+ nsAutoString byExtDefault;
+ miByExt->GetDefaultDescription(byExtDefault);
+ retval->SetDefaultDescription(byExtDefault);
+ retval->CopyBasicDataTo(miByExt);
+
+ miByExt.swap(retval);
+ }
+ return retval.forget();
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoUnix *handlerInfo =
+ new nsMIMEInfoUnix(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ GetApplicationDescription(aScheme, desc);
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.h b/uriloader/exthandler/unix/nsOSHelperAppService.h
new file mode 100644
index 000000000..33eb934ed
--- /dev/null
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+
+class nsILineInputStream;
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // method overrides for mime.types and mime.info look up steps
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMimeType,
+ const nsACString& aFileExt,
+ bool *aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ // override nsIExternalProtocolService methods
+ nsresult OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists);
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got out of the
+ // rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual nsresult GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile);
+
+protected:
+ already_AddRefed<nsMIMEInfoBase> GetFromType(const nsCString& aMimeType);
+ already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsCString& aFileExt);
+
+private:
+ uint32_t mPermissions;
+
+ // Helper methods which have to access static members
+ static nsresult UnescapeCommand(const nsAString& aEscapedCommand,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsACString& aUnEscapedCommand);
+ static nsresult GetFileLocation(const char* aPrefName,
+ const char* aEnvVarName,
+ nsAString& aFileLocation);
+ static nsresult LookUpTypeAndDescription(const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription,
+ bool aUserData);
+ static nsresult CreateInputStream(const nsAString& aFilename,
+ nsIFileInputStream ** aFileInputStream,
+ nsILineInputStream ** aLineInputStream,
+ nsACString& aBuffer,
+ bool * aNetscapeFormat,
+ bool * aMore);
+
+ static nsresult GetTypeAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription);
+
+ static nsresult LookUpExtensionsAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription);
+
+ static nsresult GetExtensionsAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription);
+
+ static nsresult ParseNetscapeMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd);
+
+ static nsresult ParseNormalMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd);
+
+ static nsresult LookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags);
+
+ static nsresult DoLookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags,
+ bool aUserData);
+
+ static nsresult GetHandlerAndDescriptionFromMailcapFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags);
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.cpp b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
new file mode 100644
index 000000000..2c7171c87
--- /dev/null
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -0,0 +1,883 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsLocalFile.h"
+#include "nsMIMEInfoWin.h"
+#include "nsNetUtil.h"
+#include <windows.h>
+#include <shellapi.h>
+#include "nsAutoPtr.h"
+#include "nsIMutableArray.h"
+#include "nsTArray.h"
+#include "shlobj.h"
+#include "windows.h"
+#include "nsIWindowsRegKey.h"
+#include "nsIProcess.h"
+#include "nsUnicharUtils.h"
+#include "nsITextToSubURI.h"
+#include "nsVariant.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#define RUNDLL32_EXE L"\\rundll32.exe"
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMIMEInfoWin, nsMIMEInfoBase, nsIPropertyBag)
+
+nsMIMEInfoWin::~nsMIMEInfoWin()
+{
+}
+
+nsresult
+nsMIMEInfoWin::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ // Launch the file, unless it is an executable.
+ bool executable = true;
+ aFile->IsExecutable(&executable);
+ if (executable)
+ return NS_ERROR_FAILURE;
+
+ return aFile->Launch();
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::LaunchWithFile(nsIFile* aFile)
+{
+ nsresult rv;
+
+ // it doesn't make any sense to call this on protocol handlers
+ NS_ASSERTION(mClass == eMIMEInfo,
+ "nsMIMEInfoBase should have mClass == eMIMEInfo");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LaunchDefaultWithFile(aFile);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // at the moment, we only know how to hand files off to local handlers
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ aFile->GetPath(path);
+
+ // Deal with local dll based handlers
+ nsCString filename;
+ executable->GetNativeLeafName(filename);
+ if (filename.Length() > 4) {
+ nsCString extension(Substring(filename, filename.Length() - 4, 4));
+
+ if (extension.LowerCaseEqualsLiteral(".dll")) {
+ nsAutoString args;
+
+ // executable is rundll32, everything else is a list of parameters,
+ // including the dll handler.
+ if (!GetDllLaunchInfo(executable, aFile, args, false))
+ return NS_ERROR_INVALID_ARG;
+
+ WCHAR rundll32Path[MAX_PATH + sizeof(RUNDLL32_EXE) / sizeof(WCHAR) + 1] = {L'\0'};
+ if (!GetSystemDirectoryW(rundll32Path, MAX_PATH)) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ lstrcatW(rundll32Path, RUNDLL32_EXE);
+
+ SHELLEXECUTEINFOW seinfo;
+ memset(&seinfo, 0, sizeof(seinfo));
+ seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ seinfo.fMask = 0;
+ seinfo.hwnd = nullptr;
+ seinfo.lpVerb = nullptr;
+ seinfo.lpFile = rundll32Path;
+ seinfo.lpParameters = args.get();
+ seinfo.lpDirectory = nullptr;
+ seinfo.nShow = SW_SHOWNORMAL;
+ if (ShellExecuteExW(&seinfo))
+ return NS_OK;
+
+ switch ((LONG_PTR)seinfo.hInstApp) {
+ case 0:
+ case SE_ERR_OOM:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case SE_ERR_ACCESSDENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case SE_ERR_ASSOCINCOMPLETE:
+ case SE_ERR_NOASSOC:
+ return NS_ERROR_UNEXPECTED;
+ case SE_ERR_DDEBUSY:
+ case SE_ERR_DDEFAIL:
+ case SE_ERR_DDETIMEOUT:
+ return NS_ERROR_NOT_AVAILABLE;
+ case SE_ERR_DLLNOTFOUND:
+ return NS_ERROR_FAILURE;
+ case SE_ERR_SHARE:
+ return NS_ERROR_FILE_IS_LOCKED;
+ default:
+ switch(GetLastError()) {
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_FILE_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND:
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ case ERROR_BAD_FORMAT:
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ }
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+ }
+ return LaunchWithIProcess(executable, path);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetHasDefaultHandler(bool * _retval)
+{
+ // We have a default application if we have a description
+ // We can ShellExecute anything; however, callers are probably interested if
+ // there is really an application associated with this type of file
+ *_retval = !mDefaultAppDescription.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetEnumerator(nsISimpleEnumerator* *_retval)
+{
+ nsCOMArray<nsIVariant> properties;
+
+ nsCOMPtr<nsIVariant> variant;
+ GetProperty(NS_LITERAL_STRING("defaultApplicationIconURL"), getter_AddRefs(variant));
+ if (variant)
+ properties.AppendObject(variant);
+
+ GetProperty(NS_LITERAL_STRING("customApplicationIconURL"), getter_AddRefs(variant));
+ if (variant)
+ properties.AppendObject(variant);
+
+ return NS_NewArrayEnumerator(_retval, properties);
+}
+
+static nsresult GetIconURLVariant(nsIFile* aApplication, nsIVariant* *_retval)
+{
+ nsAutoCString fileURLSpec;
+ NS_GetURLSpecFromFile(aApplication, fileURLSpec);
+ nsAutoCString iconURLSpec; iconURLSpec.AssignLiteral("moz-icon://");
+ iconURLSpec += fileURLSpec;
+ RefPtr<nsVariant> writable(new nsVariant());
+ writable->SetAsAUTF8String(iconURLSpec);
+ writable.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetProperty(const nsAString& aName, nsIVariant* *_retval)
+{
+ nsresult rv;
+ if (mDefaultApplication && aName.EqualsLiteral(PROPERTY_DEFAULT_APP_ICON_URL)) {
+ rv = GetIconURLVariant(mDefaultApplication, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mPreferredApplication &&
+ aName.EqualsLiteral(PROPERTY_CUSTOM_APP_ICON_URL)) {
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetIconURLVariant(executable, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// this implementation was pretty much copied verbatime from
+// Tony Robinson's code in nsExternalProtocolWin.cpp
+nsresult
+nsMIMEInfoWin::LoadUriInternal(nsIURI * aURL)
+{
+ nsresult rv = NS_OK;
+
+ // 1. Find the default app for this protocol
+ // 2. Set up the command line
+ // 3. Launch the app.
+
+ // For now, we'll just cheat essentially, check for the command line
+ // then just call ShellExecute()!
+
+ if (aURL)
+ {
+ // extract the url spec from the url
+ nsAutoCString urlSpec;
+ aURL->GetAsciiSpec(urlSpec);
+
+ // Unescape non-ASCII characters in the URL
+ nsAutoCString urlCharset;
+ nsAutoString utf16Spec;
+ rv = aURL->GetOriginCharset(urlCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(textToSubURI->UnEscapeNonAsciiURI(urlCharset, urlSpec, utf16Spec))) {
+ CopyASCIItoUTF16(urlSpec, utf16Spec);
+ }
+
+ static const wchar_t cmdVerb[] = L"open";
+ SHELLEXECUTEINFOW sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
+ SEE_MASK_FLAG_NO_UI;
+ sinfo.hwnd = nullptr;
+ sinfo.lpVerb = (LPWSTR)&cmdVerb;
+ sinfo.nShow = SW_SHOWNORMAL;
+
+ LPITEMIDLIST pidl = nullptr;
+ SFGAOF sfgao;
+
+ // Bug 394974
+ if (SUCCEEDED(SHParseDisplayName(utf16Spec.get(), nullptr,
+ &pidl, 0, &sfgao))) {
+ sinfo.lpIDList = pidl;
+ sinfo.fMask |= SEE_MASK_INVOKEIDLIST;
+ } else {
+ // SHParseDisplayName failed. Bailing out as work around for
+ // Microsoft Security Bulletin MS07-061
+ rv = NS_ERROR_FAILURE;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ BOOL result = ShellExecuteExW(&sinfo);
+ if (!result || ((LONG_PTR)sinfo.hInstApp) < 32)
+ rv = NS_ERROR_FAILURE;
+ }
+ if (pidl)
+ CoTaskMemFree(pidl);
+ }
+
+ return rv;
+}
+
+// Given a path to a local file, return its nsILocalHandlerApp instance.
+bool nsMIMEInfoWin::GetLocalHandlerApp(const nsAString& aCommandHandler,
+ nsCOMPtr<nsILocalHandlerApp>& aApp)
+{
+ nsCOMPtr<nsIFile> locfile;
+ nsresult rv =
+ NS_NewLocalFile(aCommandHandler, true, getter_AddRefs(locfile));
+ if (NS_FAILED(rv))
+ return false;
+
+ aApp = do_CreateInstance("@mozilla.org/uriloader/local-handler-app;1");
+ if (!aApp)
+ return false;
+
+ aApp->SetExecutable(locfile);
+ return true;
+}
+
+// Return the cleaned up file path associated with a command verb
+// located in root/Applications.
+bool nsMIMEInfoWin::GetAppsVerbCommandHandler(const nsAString& appExeName,
+ nsAString& applicationPath,
+ bool edit)
+{
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe
+ nsAutoString applicationsPath;
+ applicationsPath.AppendLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ // Check for the NoOpenWith flag, if it exists
+ uint32_t value;
+ if (NS_SUCCEEDED(appKey->ReadIntValue(
+ NS_LITERAL_STRING("NoOpenWith"), &value)) &&
+ value == 1)
+ return false;
+
+ nsAutoString dummy;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(
+ NS_LITERAL_STRING("NoOpenWith"), dummy)))
+ return false;
+
+ appKey->Close();
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command
+ applicationsPath.AssignLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+ if (!edit)
+ applicationsPath.AppendLiteral("\\shell\\open\\command");
+ else
+ applicationsPath.AppendLiteral("\\shell\\edit\\command");
+
+
+ rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(),
+ appFilesystemCommand))) {
+
+ // Expand environment vars, clean up any misc.
+ if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
+ return false;
+
+ applicationPath = appFilesystemCommand;
+ return true;
+ }
+ return false;
+}
+
+// Return a fully populated command string based on
+// passing information. Used in launchWithFile to trace
+// back to the full handler path based on the dll.
+// (dll, targetfile, return args, open/edit)
+bool nsMIMEInfoWin::GetDllLaunchInfo(nsIFile * aDll,
+ nsIFile * aFile,
+ nsAString& args,
+ bool edit)
+{
+ if (!aDll || !aFile)
+ return false;
+
+ nsString appExeName;
+ aDll->GetLeafName(appExeName);
+
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe
+ nsAutoString applicationsPath;
+ applicationsPath.AppendLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ // Check for the NoOpenWith flag, if it exists
+ uint32_t value;
+ rv = appKey->ReadIntValue(NS_LITERAL_STRING("NoOpenWith"), &value);
+ if (NS_SUCCEEDED(rv) && value == 1)
+ return false;
+
+ nsAutoString dummy;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(NS_LITERAL_STRING("NoOpenWith"),
+ dummy)))
+ return false;
+
+ appKey->Close();
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command
+ applicationsPath.AssignLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+ if (!edit)
+ applicationsPath.AppendLiteral("\\shell\\open\\command");
+ else
+ applicationsPath.AppendLiteral("\\shell\\edit\\command");
+
+ rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(),
+ appFilesystemCommand))) {
+ // Replace embedded environment variables.
+ uint32_t bufLength =
+ ::ExpandEnvironmentStringsW(appFilesystemCommand.get(),
+ L"", 0);
+ if (bufLength == 0) // Error
+ return false;
+
+ auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
+ if (!destination)
+ return false;
+ if (!::ExpandEnvironmentStringsW(appFilesystemCommand.get(),
+ destination.get(),
+ bufLength))
+ return false;
+
+ appFilesystemCommand.Assign(destination.get());
+
+ // C:\Windows\System32\rundll32.exe "C:\Program Files\Windows
+ // Photo Gallery\PhotoViewer.dll", ImageView_Fullscreen %1
+ nsAutoString params;
+ NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe ");
+ int32_t index = appFilesystemCommand.Find(rundllSegment);
+ if (index > kNotFound) {
+ params.Append(Substring(appFilesystemCommand,
+ index + rundllSegment.Length()));
+ } else {
+ params.Append(appFilesystemCommand);
+ }
+
+ // check to make sure we have a %1 and fill it
+ NS_NAMED_LITERAL_STRING(percentOneParam, "%1");
+ index = params.Find(percentOneParam);
+ if (index == kNotFound) // no parameter
+ return false;
+
+ nsString target;
+ aFile->GetTarget(target);
+ params.Replace(index, 2, target);
+
+ args = params;
+
+ return true;
+ }
+ return false;
+}
+
+// Return the cleaned up file path associated with a progid command
+// verb located in root.
+bool nsMIMEInfoWin::GetProgIDVerbCommandHandler(const nsAString& appProgIDName,
+ nsAString& applicationPath,
+ bool edit)
+{
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ nsAutoString appProgId(appProgIDName);
+
+ // HKEY_CLASSES_ROOT\Windows.XPSReachViewer\shell\open\command
+ if (!edit)
+ appProgId.AppendLiteral("\\shell\\open\\command");
+ else
+ appProgId.AppendLiteral("\\shell\\edit\\command");
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ appProgId,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(), appFilesystemCommand))) {
+
+ // Expand environment vars, clean up any misc.
+ if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
+ return false;
+
+ applicationPath = appFilesystemCommand;
+ return true;
+ }
+ return false;
+}
+
+// Helper routine used in tracking app lists. Converts path
+// entries to lower case and stores them in the trackList array.
+void nsMIMEInfoWin::ProcessPath(nsCOMPtr<nsIMutableArray>& appList,
+ nsTArray<nsString>& trackList,
+ const nsAString& appFilesystemCommand)
+{
+ nsAutoString lower(appFilesystemCommand);
+ ToLowerCase(lower);
+
+ // Don't include firefox.exe in the list
+ WCHAR exe[MAX_PATH+1];
+ uint32_t len = GetModuleFileNameW(nullptr, exe, MAX_PATH);
+ if (len < MAX_PATH && len != 0) {
+ uint32_t index = lower.Find(exe);
+ if (index != -1)
+ return;
+ }
+
+ nsCOMPtr<nsILocalHandlerApp> aApp;
+ if (!GetLocalHandlerApp(appFilesystemCommand, aApp))
+ return;
+
+ // Save in our main tracking arrays
+ appList->AppendElement(aApp, false);
+ trackList.AppendElement(lower);
+}
+
+// Helper routine that handles a compare between a path
+// and an array of paths.
+static bool IsPathInList(nsAString& appPath,
+ nsTArray<nsString>& trackList)
+{
+ // trackList data is always lowercase, see ProcessPath
+ // above.
+ nsAutoString tmp(appPath);
+ ToLowerCase(tmp);
+
+ for (uint32_t i = 0; i < trackList.Length(); i++) {
+ if (tmp.Equals(trackList[i]))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns a list of nsILocalHandlerApp objects containing local
+ * handlers associated with this mimeinfo. Implemented per
+ * platform using information in this object to generate the
+ * best list. Typically used for an "open with" style user
+ * option.
+ *
+ * @return nsIArray of nsILocalHandlerApp
+ */
+NS_IMETHODIMP
+nsMIMEInfoWin::GetPossibleLocalHandlers(nsIArray **_retval)
+{
+ nsresult rv;
+
+ *_retval = nullptr;
+
+ nsCOMPtr<nsIMutableArray> appList =
+ do_CreateInstance("@mozilla.org/array;1");
+
+ if (!appList)
+ return NS_ERROR_FAILURE;
+
+ nsTArray<nsString> trackList;
+
+ nsAutoCString fileExt;
+ GetPrimaryExtension(fileExt);
+
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString workingRegistryPath;
+
+ bool extKnown = false;
+ if (fileExt.IsEmpty()) {
+ extKnown = true;
+ // Mime type discovery is possible in some cases, through
+ // HKEY_CLASSES_ROOT\MIME\Database\Content Type, however, a number
+ // of file extensions related to mime type are simply not defined,
+ // (application/rss+xml & application/atom+xml are good examples)
+ // in which case we can only provide a generic list.
+ nsAutoCString mimeType;
+ GetMIMEType(mimeType);
+ if (!mimeType.IsEmpty()) {
+ workingRegistryPath.AppendLiteral("MIME\\Database\\Content Type\\");
+ workingRegistryPath.Append(NS_ConvertASCIItoUTF16(mimeType));
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if(NS_SUCCEEDED(rv)) {
+ nsAutoString mimeFileExt;
+ if (NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(), mimeFileExt))) {
+ CopyUTF16toUTF8(mimeFileExt, fileExt);
+ extKnown = false;
+ }
+ }
+ }
+ }
+
+ nsAutoString fileExtToUse;
+ if (fileExt.First() != '.')
+ fileExtToUse = char16_t('.');
+ fileExtToUse.Append(NS_ConvertUTF8toUTF16(fileExt));
+
+ // Note, the order in which these occur has an effect on the
+ // validity of the resulting display list.
+
+ if (!extKnown) {
+ // 1) Get the default handler if it exists
+ workingRegistryPath = fileExtToUse;
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString appProgId;
+ if (NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(), appProgId))) {
+ // Bug 358297 - ignore the embedded internet explorer handler
+ if (appProgId != NS_LITERAL_STRING("XPSViewer.Document")) {
+ nsAutoString appFilesystemCommand;
+ if (GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) &&
+ !IsPathInList(appFilesystemCommand, trackList)) {
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 2) list HKEY_CLASSES_ROOT\.ext\OpenWithList
+
+ workingRegistryPath = fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 3) List HKEY_CLASSES_ROOT\.ext\OpenWithProgids, with the
+ // different step of resolving the progids for the command handler.
+
+ workingRegistryPath = fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithProgids");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ // HKEY_CLASSES_ROOT\.ext\OpenWithProgids\Windows.XPSReachViewer
+ nsAutoString appProgId;
+ if (NS_FAILED(regKey->GetValueName(index, appProgId)))
+ continue;
+
+ nsAutoString appFilesystemCommand;
+ if (!GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 4) Add any non configured applications located in the MRU list
+
+ // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
+ // \Explorer\FileExts\.ext\OpenWithList
+ workingRegistryPath =
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\");
+ workingRegistryPath += fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName, appValue;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+ if (appName.EqualsLiteral("MRUList"))
+ continue;
+ if (NS_FAILED(regKey->ReadStringValue(appName, appValue)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appValue,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+
+
+ // 5) Add any non configured progids in the MRU list, with the
+ // different step of resolving the progids for the command handler.
+
+ // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
+ // \Explorer\FileExts\.ext\OpenWithProgids
+ workingRegistryPath =
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\");
+ workingRegistryPath += fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithProgids");
+
+ regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appIndex, appProgId;
+ if (NS_FAILED(regKey->GetValueName(index, appProgId)))
+ continue;
+
+ nsAutoString appFilesystemCommand;
+ if (!GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 6) Check the perceived type value, and use this to lookup the perceivedtype
+ // open with list.
+ // http://msdn2.microsoft.com/en-us/library/aa969373.aspx
+
+ workingRegistryPath = fileExtToUse;
+
+ regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString perceivedType;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("PerceivedType"),
+ perceivedType);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString openWithListPath(NS_LITERAL_STRING("SystemFileAssociations\\"));
+ openWithListPath.Append(perceivedType); // no period
+ openWithListPath.AppendLiteral("\\OpenWithList");
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ openWithListPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+ }
+ }
+ } // extKnown == false
+
+
+ // 7) list global HKEY_CLASSES_ROOT\*\OpenWithList
+ // Listing general purpose handlers, not specific to a mime type or file extension
+
+ workingRegistryPath = NS_LITERAL_STRING("*\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 8) General application's list - not file extension specific on windows
+ workingRegistryPath = NS_LITERAL_STRING("Applications");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS|
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetChildCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetChildName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+
+ // Return to the caller
+ *_retval = appList;
+ NS_ADDREF(*_retval);
+
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.h b/uriloader/exthandler/win/nsMIMEInfoWin.h
new file mode 100644
index 000000000..0a5b678e8
--- /dev/null
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoWin_h_
+#define nsMIMEInfoWin_h_
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIPropertyBag.h"
+#include "nsIMutableArray.h"
+#include "nsTArray.h"
+
+class nsMIMEInfoWin : public nsMIMEInfoBase, public nsIPropertyBag {
+ virtual ~nsMIMEInfoWin();
+
+ public:
+ nsMIMEInfoWin(const char* aType = "") : nsMIMEInfoBase(aType) {}
+ nsMIMEInfoWin(const nsACString& aMIMEType) : nsMIMEInfoBase(aMIMEType) {}
+ nsMIMEInfoWin(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoBase(aType, aClass) {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+ NS_IMETHOD GetHasDefaultHandler(bool * _retval);
+ NS_IMETHOD GetPossibleLocalHandlers(nsIArray **_retval);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROPERTYBAG
+
+ void SetDefaultApplicationHandler(nsIFile* aDefaultApplication)
+ {
+ mDefaultApplication = aDefaultApplication;
+ }
+
+ protected:
+ virtual nsresult LoadUriInternal(nsIURI *aURI);
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile);
+
+ private:
+ nsCOMPtr<nsIFile> mDefaultApplication;
+
+ // Given a path to a local handler, return its
+ // nsILocalHandlerApp instance.
+ bool GetLocalHandlerApp(const nsAString& aCommandHandler,
+ nsCOMPtr<nsILocalHandlerApp>& aApp);
+
+ // Return the cleaned up file path associated
+ // with a command verb located in root/Applications.
+ bool GetAppsVerbCommandHandler(const nsAString& appExeName,
+ nsAString& applicationPath,
+ bool bEdit);
+
+ // Return the cleaned up file path associated
+ // with a progid command verb located in root.
+ bool GetProgIDVerbCommandHandler(const nsAString& appProgIDName,
+ nsAString& applicationPath,
+ bool bEdit);
+
+ // Lookup a rundll command handler and return
+ // a populated command template for use with rundll32.exe.
+ bool GetDllLaunchInfo(nsIFile * aDll,
+ nsIFile * aFile,
+ nsAString& args, bool bEdit);
+
+ // Helper routine used in tracking app lists
+ void ProcessPath(nsCOMPtr<nsIMutableArray>& appList,
+ nsTArray<nsString>& trackList,
+ const nsAString& appFilesystemCommand);
+
+};
+
+#endif
diff --git a/uriloader/exthandler/win/nsOSHelperAppService.cpp b/uriloader/exthandler/win/nsOSHelperAppService.cpp
new file mode 100644
index 000000000..f01f3b49b
--- /dev/null
+++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sts=2 sw=2 et cin:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIMIMEInfo.h"
+#include "nsMIMEInfoWin.h"
+#include "nsMimeTypes.h"
+#include "nsIProcess.h"
+#include "plstr.h"
+#include "nsAutoPtr.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsLocalFile.h"
+#include "nsIWindowsRegKey.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WindowsVersion.h"
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+#include <shlwapi.h>
+
+#define LOG(args) MOZ_LOG(mLog, mozilla::LogLevel::Debug, args)
+
+// helper methods: forward declarations...
+static nsresult GetExtensionFrom4xRegistryInfo(const nsACString& aMimeType,
+ nsString& aFileExtension);
+static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
+ nsString& aFileExtension);
+
+nsOSHelperAppService::nsOSHelperAppService() :
+ nsExternalHelperAppService()
+ , mAppAssoc(nullptr)
+{
+ CoInitialize(nullptr);
+ CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr,
+ CLSCTX_INPROC, IID_IApplicationAssociationRegistration,
+ (void**)&mAppAssoc);
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+ if (mAppAssoc)
+ mAppAssoc->Release();
+ mAppAssoc = nullptr;
+ CoUninitialize();
+}
+
+// The windows registry provides a mime database key which lists a set of mime types and corresponding "Extension" values.
+// we can use this to look up our mime type to see if there is a preferred extension for the mime type.
+static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
+ nsString& aFileExtension)
+{
+ nsAutoString mimeDatabaseKey;
+ mimeDatabaseKey.AssignLiteral("MIME\\Database\\Content Type\\");
+
+ AppendASCIItoUTF16(aMimeType, mimeDatabaseKey);
+
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ mimeDatabaseKey,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+
+ if (NS_SUCCEEDED(rv))
+ regKey->ReadStringValue(NS_LITERAL_STRING("Extension"), aFileExtension);
+
+ return NS_OK;
+}
+
+// We have a serious problem!! I have this content type and the windows registry only gives me
+// helper apps based on extension. Right now, we really don't have a good place to go for
+// trying to figure out the extension for a particular mime type....One short term hack is to look
+// this information in 4.x (it's stored in the windows regsitry).
+static nsresult GetExtensionFrom4xRegistryInfo(const nsACString& aMimeType,
+ nsString& aFileExtension)
+{
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = regKey->
+ Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Software\\Netscape\\Netscape Navigator\\Suffixes"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->ReadStringValue(NS_ConvertASCIItoUTF16(aMimeType),
+ aFileExtension);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ aFileExtension.Insert(char16_t('.'), 0);
+
+ // this may be a comma separated list of extensions...just take the
+ // first one for now...
+
+ int32_t pos = aFileExtension.FindChar(char16_t(','));
+ if (pos > 0) {
+ // we have a comma separated list of types...
+ // truncate everything after the first comma (including the comma)
+ aFileExtension.Truncate(pos);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ // look up the protocol scheme in the windows registry....if we find a match then we have a handler for it...
+ *aHandlerExists = false;
+ if (aProtocolScheme && *aProtocolScheme)
+ {
+ // Vista: use new application association interface
+ if (mAppAssoc) {
+ wchar_t * pResult = nullptr;
+ NS_ConvertASCIItoUTF16 scheme(aProtocolScheme);
+ // We are responsible for freeing returned strings.
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(scheme.get(),
+ AT_URLPROTOCOL, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ CoTaskMemFree(pResult);
+ *aHandlerExists = true;
+ }
+ return NS_OK;
+ }
+
+ HKEY hKey;
+ LONG err = ::RegOpenKeyExW(HKEY_CLASSES_ROOT,
+ NS_ConvertASCIItoUTF16(aProtocolScheme).get(),
+ 0,
+ KEY_QUERY_VALUE,
+ &hKey);
+ if (err == ERROR_SUCCESS)
+ {
+ err = ::RegQueryValueExW(hKey, L"URL Protocol",
+ nullptr, nullptr, nullptr, nullptr);
+ *aHandlerExists = (err == ERROR_SUCCESS);
+ // close the key
+ ::RegCloseKey(hKey);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ConvertASCIItoUTF16 buf(aScheme);
+
+ if (mozilla::IsWin8OrLater()) {
+ wchar_t result[1024];
+ DWORD resultSize = 1024;
+ HRESULT hr = AssocQueryString(0x1000 /* ASSOCF_IS_PROTOCOL */,
+ ASSOCSTR_FRIENDLYAPPNAME,
+ buf.get(),
+ NULL,
+ result,
+ &resultSize);
+ if (SUCCEEDED(hr)) {
+ _retval = result;
+ return NS_OK;
+ }
+ }
+
+ if (mAppAssoc) {
+ // Vista: use new application association interface
+ wchar_t * pResult = nullptr;
+ // We are responsible for freeing returned strings.
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(buf.get(),
+ AT_URLPROTOCOL, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ nsCOMPtr<nsIFile> app;
+ nsAutoString appInfo(pResult);
+ CoTaskMemFree(pResult);
+ if (NS_SUCCEEDED(GetDefaultAppInfo(appInfo, _retval, getter_AddRefs(app))))
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIFile> app;
+ GetDefaultAppInfo(buf, _retval, getter_AddRefs(app));
+
+ if (!_retval.Equals(buf))
+ return NS_OK;
+
+ // Fall back to full path
+ buf.AppendLiteral("\\shell\\open\\command");
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ buf,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->ReadStringValue(EmptyString(), _retval);
+
+ return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+// GetMIMEInfoFromRegistry: This function obtains the values of some of the nsIMIMEInfo
+// attributes for the mimeType/extension associated with the input registry key. The default
+// entry for that key is the name of a registry key under HKEY_CLASSES_ROOT. The default
+// value for *that* key is the descriptive name of the type. The EditFlags value is a binary
+// value; the low order bit of the third byte of which indicates that the user does not need
+// to be prompted.
+//
+// This function sets only the Description attribute of the input nsIMIMEInfo.
+/* static */
+nsresult nsOSHelperAppService::GetMIMEInfoFromRegistry(const nsAFlatString& fileType, nsIMIMEInfo *pInfo)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG(pInfo);
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileType, nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // OK, the default value here is the description of the type.
+ nsAutoString description;
+ rv = regKey->ReadStringValue(EmptyString(), description);
+ if (NS_SUCCEEDED(rv))
+ pInfo->SetDescription(description);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// method overrides used to gather information from the windows registry for
+// various mime types.
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Looks up the type for the extension aExt and compares it to aType
+/* static */ bool
+nsOSHelperAppService::typeFromExtEquals(const char16_t* aExt, const char *aType)
+{
+ if (!aType)
+ return false;
+ nsAutoString fileExtToUse;
+ if (aExt[0] != char16_t('.'))
+ fileExtToUse = char16_t('.');
+
+ fileExtToUse.Append(aExt);
+
+ bool eq = false;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return eq;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return eq;
+
+ nsAutoString type;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("Content Type"), type);
+ if (NS_SUCCEEDED(rv))
+ eq = type.EqualsASCII(aType);
+
+ return eq;
+}
+
+// The "real" name of a given helper app (as specified by the path to the
+// executable file held in various registry keys) is stored n the VERSIONINFO
+// block in the file's resources. We need to find the path to the executable
+// and then retrieve the "FileDescription" field value from the file.
+nsresult
+nsOSHelperAppService::GetDefaultAppInfo(const nsAString& aAppInfo,
+ nsAString& aDefaultDescription,
+ nsIFile** aDefaultApplication)
+{
+ nsAutoString handlerCommand;
+
+ // If all else fails, use the file type key name, which will be
+ // something like "pngfile" for .pngs, "WMVFile" for .wmvs, etc.
+ aDefaultDescription = aAppInfo;
+ *aDefaultApplication = nullptr;
+
+ if (aAppInfo.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // aAppInfo may be a file, file path, program id, or
+ // Applications reference -
+ // c:\dir\app.exe
+ // Applications\appfile.exe/dll (shell\open...)
+ // ProgID.progid (shell\open...)
+
+ nsAutoString handlerKeyName(aAppInfo);
+
+ nsCOMPtr<nsIWindowsRegKey> chkKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!chkKey)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ handlerKeyName,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv)) {
+ // It's a file system path to a handler
+ handlerCommand.Assign(aAppInfo);
+ }
+ else {
+ handlerKeyName.AppendLiteral("\\shell\\open\\command");
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ handlerKeyName,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // OK, the default value here is the description of the type.
+ rv = regKey->ReadStringValue(EmptyString(), handlerCommand);
+ if (NS_FAILED(rv)) {
+
+ // Check if there is a DelegateExecute string
+ nsAutoString delegateExecute;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("DelegateExecute"), delegateExecute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Look for InProcServer32
+ nsAutoString delegateExecuteRegPath;
+ delegateExecuteRegPath.AssignLiteral("CLSID\\");
+ delegateExecuteRegPath.Append(delegateExecute);
+ delegateExecuteRegPath.AppendLiteral("\\InProcServer32");
+ rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ delegateExecuteRegPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ rv = chkKey->ReadStringValue(EmptyString(), handlerCommand);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Look for LocalServer32
+ delegateExecuteRegPath.AssignLiteral("CLSID\\");
+ delegateExecuteRegPath.Append(delegateExecute);
+ delegateExecuteRegPath.AppendLiteral("\\LocalServer32");
+ rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ delegateExecuteRegPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = chkKey->ReadStringValue(EmptyString(), handlerCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // XXX FIXME: If this fails, the UI will display the full command
+ // string.
+ // There are some rare cases this can happen - ["url.dll" -foo]
+ // for example won't resolve correctly to the system dir. The
+ // subsequent launch of the helper app will work though.
+ nsCOMPtr<nsILocalFileWin> lf = new nsLocalFile();
+ rv = lf->InitWithCommandLine(handlerCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The "FileDescription" field contains the actual name of the application.
+ lf->GetVersionInfoField("FileDescription", aDefaultDescription);
+ lf.forget(aDefaultApplication);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsMIMEInfoWin> nsOSHelperAppService::GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint)
+{
+ if (aFileExt.IsEmpty())
+ return nullptr;
+
+ // Determine the mime type.
+ nsAutoCString typeToUse;
+ if (aTypeHint && *aTypeHint) {
+ typeToUse.Assign(aTypeHint);
+ } else if (!GetMIMETypeFromOSForExtension(NS_ConvertUTF16toUTF8(aFileExt), typeToUse)) {
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoWin> mimeInfo = new nsMIMEInfoWin(typeToUse);
+
+ // windows registry assumes your file extension is going to include the '.',
+ // but our APIs expect it to not be there, so make sure we normalize that bit.
+ nsAutoString fileExtToUse;
+ if (aFileExt.First() != char16_t('.'))
+ fileExtToUse = char16_t('.');
+
+ fileExtToUse.Append(aFileExt);
+
+ // don't append the '.' for our APIs.
+ mimeInfo->AppendExtension(NS_ConvertUTF16toUTF8(Substring(fileExtToUse, 1)));
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+
+ nsAutoString appInfo;
+ bool found;
+
+ // Retrieve the default application for this extension
+ if (mAppAssoc) {
+ // Vista: use the new application association COM interfaces
+ // for resolving helpers.
+ nsString assocType(fileExtToUse);
+ wchar_t * pResult = nullptr;
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(assocType.get(),
+ AT_FILEEXTENSION, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ found = true;
+ appInfo.Assign(pResult);
+ CoTaskMemFree(pResult);
+ }
+ else {
+ found = false;
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return nullptr;
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ found = NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(),
+ appInfo));
+ }
+ }
+
+ // Bug 358297 - ignore the default handler, force the user to choose app
+ if (appInfo.EqualsLiteral("XPSViewer.Document"))
+ found = false;
+
+ if (!found) {
+ return nullptr;
+ }
+
+ // Get other nsIMIMEInfo fields from registry, if possible.
+ nsAutoString defaultDescription;
+ nsCOMPtr<nsIFile> defaultApplication;
+
+ if (NS_FAILED(GetDefaultAppInfo(appInfo, defaultDescription,
+ getter_AddRefs(defaultApplication)))) {
+ return nullptr;
+ }
+
+ mimeInfo->SetDefaultDescription(defaultDescription);
+ mimeInfo->SetDefaultApplicationHandler(defaultApplication);
+
+ // Grab the general description
+ GetMIMEInfoFromRegistry(appInfo, mimeInfo);
+
+ return mimeInfo.forget();
+}
+
+already_AddRefed<nsIMIMEInfo> nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool *aFound)
+{
+ *aFound = true;
+
+ const nsCString& flatType = PromiseFlatCString(aMIMEType);
+ const nsCString& flatExt = PromiseFlatCString(aFileExt);
+
+ nsAutoString fileExtension;
+ /* XXX The Equals is a gross hack to wallpaper over the most common Win32
+ * extension issues caused by the fix for bug 116938. See bug
+ * 120327, comment 271 for why this is needed. Not even sure we
+ * want to remove this once we have fixed all this stuff to work
+ * right; any info we get from the OS on this type is pretty much
+ * useless....
+ * We'll do extension-based lookup for this type later in this function.
+ */
+ if (!aMIMEType.LowerCaseEqualsLiteral(APPLICATION_OCTET_STREAM)) {
+ // (1) try to use the windows mime database to see if there is a mapping to a file extension
+ // (2) try to see if we have some left over 4.x registry info we can peek at...
+ GetExtensionFromWindowsMimeDatabase(aMIMEType, fileExtension);
+ LOG(("Windows mime database: extension '%s'\n", fileExtension.get()));
+ if (fileExtension.IsEmpty()) {
+ GetExtensionFrom4xRegistryInfo(aMIMEType, fileExtension);
+ LOG(("4.x Registry: extension '%s'\n", fileExtension.get()));
+ }
+ }
+ // If we found an extension for the type, do the lookup
+ RefPtr<nsMIMEInfoWin> mi;
+ if (!fileExtension.IsEmpty())
+ mi = GetByExtension(fileExtension, flatType.get());
+ LOG(("Extension lookup on '%s' found: 0x%p\n", fileExtension.get(), mi.get()));
+
+ bool hasDefault = false;
+ if (mi) {
+ mi->GetHasDefaultHandler(&hasDefault);
+ // OK. We might have the case that |aFileExt| is a valid extension for the
+ // mimetype we were given. In that case, we do want to append aFileExt
+ // to the mimeinfo that we have. (E.g.: We are asked for video/mpeg and
+ // .mpg, but the primary extension for video/mpeg is .mpeg. But because
+ // .mpg is an extension for video/mpeg content, we want to append it)
+ if (!aFileExt.IsEmpty() && typeFromExtEquals(NS_ConvertUTF8toUTF16(flatExt).get(), flatType.get())) {
+ LOG(("Appending extension '%s' to mimeinfo, because its mimetype is '%s'\n",
+ flatExt.get(), flatType.get()));
+ bool extExist = false;
+ mi->ExtensionExists(aFileExt, &extExist);
+ if (!extExist)
+ mi->AppendExtension(aFileExt);
+ }
+ }
+ if (!mi || !hasDefault) {
+ RefPtr<nsMIMEInfoWin> miByExt =
+ GetByExtension(NS_ConvertUTF8toUTF16(aFileExt), flatType.get());
+ LOG(("Ext. lookup for '%s' found 0x%p\n", flatExt.get(), miByExt.get()));
+ if (!miByExt && mi)
+ return mi.forget();
+ if (miByExt && !mi) {
+ return miByExt.forget();
+ }
+ if (!miByExt && !mi) {
+ *aFound = false;
+ mi = new nsMIMEInfoWin(flatType);
+ if (!aFileExt.IsEmpty()) {
+ mi->AppendExtension(aFileExt);
+ }
+
+ return mi.forget();
+ }
+
+ // if we get here, mi has no default app. copy from extension lookup.
+ nsCOMPtr<nsIFile> defaultApp;
+ nsAutoString desc;
+ miByExt->GetDefaultDescription(desc);
+
+ mi->SetDefaultDescription(desc);
+ }
+ return mi.forget();
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoWin *handlerInfo =
+ new nsMIMEInfoWin(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ GetApplicationDescription(aScheme, desc);
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
+
+bool
+nsOSHelperAppService::GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType)
+{
+ if (aExtension.IsEmpty())
+ return false;
+
+ // windows registry assumes your file extension is going to include the '.'.
+ // so make sure it's there...
+ nsAutoString fileExtToUse;
+ if (aExtension.First() != '.')
+ fileExtToUse = char16_t('.');
+
+ AppendUTF8toUTF16(aExtension, fileExtToUse);
+
+ // Try to get an entry from the windows registry.
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return false;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString mimeType;
+ if (NS_FAILED(regKey->ReadStringValue(NS_LITERAL_STRING("Content Type"),
+ mimeType)) || mimeType.IsEmpty()) {
+ return false;
+ }
+ // Content-Type is always in ASCII
+ aMIMEType.Truncate();
+ LossyAppendUTF16toASCII(mimeType, aMIMEType);
+ return true;
+}
diff --git a/uriloader/exthandler/win/nsOSHelperAppService.h b/uriloader/exthandler/win/nsOSHelperAppService.h
new file mode 100644
index 000000000..b00529433
--- /dev/null
+++ b/uriloader/exthandler/win/nsOSHelperAppService.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+#include <windows.h>
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#include <shlobj.h>
+
+class nsMIMEInfoWin;
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ nsresult OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists);
+ nsresult LoadUriInternal(nsIURI * aURL);
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // method overrides for windows registry look up steps....
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool *aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+ virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType) override;
+
+ /** Get the string value of a registry value and store it in result.
+ * @return true on success, false on failure
+ */
+ static bool GetValueString(HKEY hKey, const char16_t* pValueName, nsAString& result);
+
+protected:
+ nsresult GetDefaultAppInfo(const nsAString& aTypeName, nsAString& aDefaultDescription, nsIFile** aDefaultApplication);
+ // Lookup a mime info by extension, using an optional type hint
+ already_AddRefed<nsMIMEInfoWin> GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint = nullptr);
+ nsresult FindOSMimeInfoForType(const char * aMimeContentType, nsIURI * aURI, char ** aFileExtension, nsIMIMEInfo ** aMIMEInfo);
+
+ static nsresult GetMIMEInfoFromRegistry(const nsAFlatString& fileType, nsIMIMEInfo *pInfo);
+ /// Looks up the type for the extension aExt and compares it to aType
+ static bool typeFromExtEquals(const char16_t* aExt, const char *aType);
+
+private:
+ IApplicationAssociationRegistration* mAppAssoc;
+};
+
+#endif // nsOSHelperAppService_h__