fune/toolkit/components/downloads/DownloadPlatform.cpp
Nika Layzell 98304d1200 Bug 1793463 - Part 5: Stop using contractids to fetch protocol handlers, r=necko-reviewers,xpcom-reviewers,webdriver-reviewers,whimboo,valentin,kmag
This patch replaces the previous ContractID-based lookup system for protocol
handlers, and replaces it with a new custom system in nsIOService. It will be
pre-populated with non-overridable static protocol handlers using the
StaticComponents infrastructure added in the previous part, and callers can
also dynamically register new protocol handlers at runtime.

This new system is intended to provide access to the default port and
non-dynamic protocol flags off-main-thread, by requiring these values to be
provided up-front as constants, rather than getting them from the xpcom
interface. The data is then guarded by an RWLock.

Callers which look up specific handlers by their contractID are not changed, as
the contract IDs for existing handlers have not been changed, so the lookup
will still succeed.

This change as-implemented breaks the nsGIOProtocolHandler on Linux, as it
removes the special code which would try to use that handler for some
protocols. This will be fixed in a later part by making the
nsGIOProtocolHandler use the dynamic registration APIs to register and
un-register protocol handlers at runtime in response to the GIO pref.

Differential Revision: https://phabricator.services.mozilla.com/D162804
2022-12-01 15:43:19 +00:00

319 lines
10 KiB
C++

/* 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 "DownloadPlatform.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsINestedURI.h"
#include "nsIProtocolHandler.h"
#include "nsIURI.h"
#include "nsIFile.h"
#include "xpcpublic.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Preferences.h"
#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
#ifdef XP_WIN
# include <shlobj.h>
# include <urlmon.h>
# include "nsILocalFileWin.h"
# include "WinTaskbar.h"
#endif
#ifdef XP_MACOSX
# include <CoreFoundation/CoreFoundation.h>
# include "../../../xpcom/io/CocoaFileUtils.h"
#endif
#ifdef MOZ_WIDGET_GTK
# include <gtk/gtk.h>
#endif
using namespace mozilla;
using dom::Promise;
DownloadPlatform* DownloadPlatform::gDownloadPlatformService = nullptr;
NS_IMPL_ISUPPORTS(DownloadPlatform, mozIDownloadPlatform);
DownloadPlatform* DownloadPlatform::GetDownloadPlatform() {
if (!gDownloadPlatformService) {
gDownloadPlatformService = new DownloadPlatform();
}
NS_ADDREF(gDownloadPlatformService);
return gDownloadPlatformService;
}
#ifdef MOZ_WIDGET_GTK
static void gio_set_metadata_done(GObject* source_obj, GAsyncResult* res,
gpointer user_data) {
GError* err = nullptr;
g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
if (err) {
# ifdef DEBUG
NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message,
__FILE__, __LINE__);
# endif
g_error_free(err);
}
}
#endif
#ifdef XP_MACOSX
// Caller is responsible for freeing any result (CF Create Rule)
CFURLRef CreateCFURLFromNSIURI(nsIURI* aURI) {
nsAutoCString spec;
if (aURI) {
aURI->GetSpec(spec);
}
CFStringRef urlStr = ::CFStringCreateWithCString(
kCFAllocatorDefault, spec.get(), kCFStringEncodingUTF8);
if (!urlStr) {
return NULL;
}
CFURLRef url = ::CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL);
::CFRelease(urlStr);
return url;
}
#endif
#ifdef XP_WIN
static void AddToRecentDocs(nsIFile* aTarget, nsAutoString& aPath) {
nsString modelId;
if (mozilla::widget::WinTaskbar::GetAppUserModelID(modelId)) {
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(uri), aTarget)) && uri) {
nsCString spec;
if (NS_SUCCEEDED(uri->GetSpec(spec))) {
IShellItem2* psi = nullptr;
if (SUCCEEDED(
SHCreateItemFromParsingName(NS_ConvertASCIItoUTF16(spec).get(),
nullptr, IID_PPV_ARGS(&psi)))) {
SHARDAPPIDINFO info = {psi, modelId.get()};
::SHAddToRecentDocs(SHARD_APPIDINFO, &info);
psi->Release();
return;
}
}
}
}
::SHAddToRecentDocs(SHARD_PATHW, aPath.get());
}
#endif
nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer,
nsIFile* aTarget,
const nsACString& aContentType,
bool aIsPrivate, JSContext* aCx,
Promise** aPromise) {
nsIGlobalObject* globalObject =
xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
nsresult rv = NS_OK;
bool pendingAsyncOperations = false;
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || \
defined(MOZ_WIDGET_GTK)
nsAutoString path;
if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
# if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
# ifndef MOZ_WIDGET_ANDROID
bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
if (addToRecentDocs && !aIsPrivate) {
# ifdef XP_WIN
AddToRecentDocs(aTarget, path);
# elif defined(MOZ_WIDGET_GTK)
GtkRecentManager* manager = gtk_recent_manager_get_default();
gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
nullptr, nullptr);
if (uri) {
gtk_recent_manager_add_item(manager, uri);
g_free(uri);
}
# endif
}
# endif
# ifdef MOZ_WIDGET_GTK
// Private window should not leak URI to the system (Bug 1535950)
if (!aIsPrivate) {
// Use GIO to store the source URI for later display in the file
// manager.
GFile* gio_file =
g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
nsCString source_uri;
nsresult rv = aSource->GetSpec(source_uri);
NS_ENSURE_SUCCESS(rv, rv);
GFileInfo* file_info = g_file_info_new();
g_file_info_set_attribute_string(file_info, "metadata::download-uri",
source_uri.get());
g_file_set_attributes_async(gio_file, file_info, G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT, nullptr,
gio_set_metadata_done, nullptr);
g_object_unref(file_info);
g_object_unref(gio_file);
}
# endif
}
# endif
# ifdef XP_MACOSX
// On OS X, make the downloads stack bounce.
CFStringRef observedObject = ::CFStringCreateWithCString(
kCFAllocatorDefault, NS_ConvertUTF16toUTF8(path).get(),
kCFStringEncodingUTF8);
CFNotificationCenterRef center =
::CFNotificationCenterGetDistributedCenter();
::CFNotificationCenterPostNotification(
center, CFSTR("com.apple.DownloadFileFinished"), observedObject,
nullptr, TRUE);
::CFRelease(observedObject);
// Add OS X origin and referrer file metadata
CFStringRef pathCFStr = NULL;
if (!path.IsEmpty()) {
pathCFStr = ::CFStringCreateWithCharacters(
kCFAllocatorDefault, (const UniChar*)path.get(), path.Length());
}
if (pathCFStr && !aIsPrivate) {
bool isFromWeb = IsURLPossiblyFromWeb(aSource);
nsCOMPtr<nsIURI> source(aSource);
nsCOMPtr<nsIURI> referrer(aReferrer);
rv = NS_DispatchBackgroundTask(
NS_NewRunnableFunction(
"DownloadPlatform::DownloadDone",
[pathCFStr, isFromWeb, source, referrer, promise]() mutable {
CFURLRef sourceCFURL = CreateCFURLFromNSIURI(source);
CFURLRef referrerCFURL = CreateCFURLFromNSIURI(referrer);
CocoaFileUtils::AddOriginMetadataToFile(pathCFStr, sourceCFURL,
referrerCFURL);
CocoaFileUtils::AddQuarantineMetadataToFile(
pathCFStr, sourceCFURL, referrerCFURL, isFromWeb);
::CFRelease(pathCFStr);
if (sourceCFURL) {
::CFRelease(sourceCFURL);
}
if (referrerCFURL) {
::CFRelease(referrerCFURL);
}
DebugOnly<nsresult> rv =
NS_DispatchToMainThread(NS_NewRunnableFunction(
"DownloadPlatform::DownloadDoneResolve",
[promise = std::move(promise)]() {
promise->MaybeResolveWithUndefined();
}));
MOZ_ASSERT(NS_SUCCEEDED(rv));
// In non-debug builds, if we've for some reason failed to
// dispatch a runnable to the main thread to resolve the
// Promise, then it's unlikely we can reject it either. At that
// point, the Promise is going to remain in pending limbo until
// its global goes away.
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
if (NS_SUCCEEDED(rv)) {
pendingAsyncOperations = true;
}
}
# endif
}
#endif
if (!pendingAsyncOperations) {
promise->MaybeResolveWithUndefined();
}
promise.forget(aPromise);
return rv;
}
nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL,
uint32_t* aZone) {
#ifdef XP_WIN
RefPtr<IInternetSecurityManager> inetSecMgr;
if (FAILED(CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_ALL,
IID_IInternetSecurityManager,
getter_AddRefs(inetSecMgr)))) {
return NS_ERROR_UNEXPECTED;
}
DWORD zone;
if (inetSecMgr->MapUrlToZone(PromiseFlatString(aURL).get(), &zone, 0) !=
S_OK) {
return NS_ERROR_UNEXPECTED;
} else {
*aZone = zone;
}
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
// Check if a URI is likely to be web-based, by checking its URI flags.
// If in doubt (e.g. if anything fails during the check) claims things
// are from the web.
bool DownloadPlatform::IsURLPossiblyFromWeb(nsIURI* aURI) {
nsCOMPtr<nsIIOService> ios = do_GetIOService();
nsCOMPtr<nsIURI> uri = aURI;
if (!ios) {
return true;
}
while (uri) {
// We're not using NS_URIChainHasFlags because we're checking for *any* of 3
// flags to be present on *all* of the nested URIs, which it can't do.
uint32_t flags;
nsresult rv = ios->GetDynamicProtocolFlags(uri, &flags);
if (NS_FAILED(rv)) {
return true;
}
// If not dangerous to load, not a UI resource and not a local file,
// assume this is from the web:
if (!(flags & nsIProtocolHandler::URI_DANGEROUS_TO_LOAD) &&
!(flags & nsIProtocolHandler::URI_IS_UI_RESOURCE) &&
!(flags & nsIProtocolHandler::URI_IS_LOCAL_FILE)) {
return true;
}
// Otherwise, check if the URI is nested, and if so go through
// the loop again:
nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
uri = nullptr;
if (nestedURI) {
rv = nestedURI->GetInnerURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return true;
}
}
}
return false;
}