diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/downloads/DownloadPlatform.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/downloads/DownloadPlatform.cpp')
-rw-r--r-- | toolkit/components/downloads/DownloadPlatform.cpp | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/toolkit/components/downloads/DownloadPlatform.cpp b/toolkit/components/downloads/DownloadPlatform.cpp new file mode 100644 index 0000000000..ee2e0a3248 --- /dev/null +++ b/toolkit/components/downloads/DownloadPlatform.cpp @@ -0,0 +1,319 @@ +/* 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; +} |