forked from mirrors/gecko-dev
		
	 9a1499e1fe
			
		
	
	
		9a1499e1fe
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D3833 --HG-- extra : rebase_source : 753d62c62ef0b27f1d89e88028bee4528c375619
		
			
				
	
	
		
			334 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			334 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 "nsAutoPtr.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsString.h"
 | |
| #include "nsINestedURI.h"
 | |
| #include "nsIProtocolHandler.h"
 | |
| #include "nsIURI.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsISupportsPrimitives.h"
 | |
| #include "nsDirectoryServiceDefs.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "xpcpublic.h"
 | |
| 
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/LazyIdleThread.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/Services.h"
 | |
| 
 | |
| #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
 | |
| 
 | |
| // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes.
 | |
| #define DEFAULT_THREAD_TIMEOUT_MS 10000
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| #include <shlobj.h>
 | |
| #include <urlmon.h>
 | |
| #include "nsILocalFileWin.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
| #include <CoreFoundation/CoreFoundation.h>
 | |
| #include "../../../xpcom/io/CocoaFileUtils.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_WIDGET_ANDROID
 | |
| #include "FennecJNIWrappers.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);
 | |
| 
 | |
| #if defined(MOZ_WIDGET_GTK)
 | |
|   g_type_init();
 | |
| #endif
 | |
| 
 | |
|   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
 | |
| 
 | |
| DownloadPlatform::DownloadPlatform()
 | |
| {
 | |
|   mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
 | |
|                                  NS_LITERAL_CSTRING("DownloadPlatform"));
 | |
| }
 | |
| 
 | |
| 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.
 | |
|     {
 | |
|       bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
 | |
| #ifdef MOZ_WIDGET_ANDROID
 | |
|       if (jni::IsFennec() && addToRecentDocs) {
 | |
|         java::DownloadsIntegration::ScanMedia(path, aContentType);
 | |
|       }
 | |
| #else
 | |
|       if (addToRecentDocs && !aIsPrivate) {
 | |
| #ifdef XP_WIN
 | |
|         ::SHAddToRecentDocs(SHARD_PATHW, path.get());
 | |
| #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
 | |
|       // 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 = mIOThread->Dispatch(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.
 | |
|         }
 | |
|       ));
 | |
| 
 | |
|       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 nsIIOService::ProtocolHasFlags because it doesn't
 | |
|     // take per-URI flags into account. We're also 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.
 | |
|     nsAutoCString scheme;
 | |
|     nsresult rv = uri->GetScheme(scheme);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return true;
 | |
|     }
 | |
|     nsCOMPtr<nsIProtocolHandler> ph;
 | |
|     rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return true;
 | |
|     }
 | |
|     uint32_t flags;
 | |
|     rv = ph->DoGetProtocolFlags(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;
 | |
| }
 |