/src/mozilla-central/toolkit/components/downloads/DownloadPlatform.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | #include "DownloadPlatform.h" |
6 | | #include "nsAutoPtr.h" |
7 | | #include "nsNetUtil.h" |
8 | | #include "nsString.h" |
9 | | #include "nsINestedURI.h" |
10 | | #include "nsIProtocolHandler.h" |
11 | | #include "nsIURI.h" |
12 | | #include "nsIFile.h" |
13 | | #include "nsIObserverService.h" |
14 | | #include "nsISupportsPrimitives.h" |
15 | | #include "nsDirectoryServiceDefs.h" |
16 | | #include "nsThreadUtils.h" |
17 | | #include "xpcpublic.h" |
18 | | |
19 | | #include "mozilla/dom/Promise.h" |
20 | | #include "mozilla/LazyIdleThread.h" |
21 | | #include "mozilla/Preferences.h" |
22 | | #include "mozilla/Services.h" |
23 | | |
24 | 0 | #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs" |
25 | | |
26 | | // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. |
27 | 0 | #define DEFAULT_THREAD_TIMEOUT_MS 10000 |
28 | | |
29 | | #ifdef XP_WIN |
30 | | #include <shlobj.h> |
31 | | #include <urlmon.h> |
32 | | #include "nsILocalFileWin.h" |
33 | | #endif |
34 | | |
35 | | #ifdef XP_MACOSX |
36 | | #include <CoreFoundation/CoreFoundation.h> |
37 | | #include "../../../xpcom/io/CocoaFileUtils.h" |
38 | | #endif |
39 | | |
40 | | #ifdef MOZ_WIDGET_ANDROID |
41 | | #include "FennecJNIWrappers.h" |
42 | | #endif |
43 | | |
44 | | #ifdef MOZ_WIDGET_GTK |
45 | | #include <gtk/gtk.h> |
46 | | #endif |
47 | | |
48 | | using namespace mozilla; |
49 | | using dom::Promise; |
50 | | |
51 | | DownloadPlatform *DownloadPlatform::gDownloadPlatformService = nullptr; |
52 | | |
53 | | NS_IMPL_ISUPPORTS(DownloadPlatform, mozIDownloadPlatform); |
54 | | |
55 | | DownloadPlatform* DownloadPlatform::GetDownloadPlatform() |
56 | 0 | { |
57 | 0 | if (!gDownloadPlatformService) { |
58 | 0 | gDownloadPlatformService = new DownloadPlatform(); |
59 | 0 | } |
60 | 0 |
|
61 | 0 | NS_ADDREF(gDownloadPlatformService); |
62 | 0 |
|
63 | 0 | #if defined(MOZ_WIDGET_GTK) |
64 | 0 | g_type_init(); |
65 | 0 | #endif |
66 | 0 |
|
67 | 0 | return gDownloadPlatformService; |
68 | 0 | } |
69 | | |
70 | | #ifdef MOZ_WIDGET_GTK |
71 | | static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data) |
72 | 0 | { |
73 | 0 | GError *err = nullptr; |
74 | 0 | g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err); |
75 | 0 | if (err) { |
76 | | #ifdef DEBUG |
77 | | NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__); |
78 | | #endif |
79 | | g_error_free(err); |
80 | 0 | } |
81 | 0 | } |
82 | | #endif |
83 | | |
84 | | #ifdef XP_MACOSX |
85 | | // Caller is responsible for freeing any result (CF Create Rule) |
86 | | CFURLRef CreateCFURLFromNSIURI(nsIURI *aURI) { |
87 | | nsAutoCString spec; |
88 | | if (aURI) { |
89 | | aURI->GetSpec(spec); |
90 | | } |
91 | | |
92 | | CFStringRef urlStr = ::CFStringCreateWithCString(kCFAllocatorDefault, |
93 | | spec.get(), |
94 | | kCFStringEncodingUTF8); |
95 | | if (!urlStr) { |
96 | | return NULL; |
97 | | } |
98 | | |
99 | | CFURLRef url = ::CFURLCreateWithString(kCFAllocatorDefault, |
100 | | urlStr, |
101 | | NULL); |
102 | | |
103 | | ::CFRelease(urlStr); |
104 | | |
105 | | return url; |
106 | | } |
107 | | #endif |
108 | | |
109 | | DownloadPlatform::DownloadPlatform() |
110 | 0 | { |
111 | 0 | mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, |
112 | 0 | NS_LITERAL_CSTRING("DownloadPlatform")); |
113 | 0 | } |
114 | | |
115 | | nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIFile* aTarget, |
116 | | const nsACString& aContentType, bool aIsPrivate, |
117 | | JSContext* aCx, Promise** aPromise) |
118 | 0 | { |
119 | 0 |
|
120 | 0 | nsIGlobalObject* globalObject = |
121 | 0 | xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); |
122 | 0 |
|
123 | 0 | if (NS_WARN_IF(!globalObject)) { |
124 | 0 | return NS_ERROR_FAILURE; |
125 | 0 | } |
126 | 0 | |
127 | 0 | ErrorResult result; |
128 | 0 | RefPtr<Promise> promise = Promise::Create(globalObject, result); |
129 | 0 |
|
130 | 0 | if (NS_WARN_IF(result.Failed())) { |
131 | 0 | return result.StealNSResult(); |
132 | 0 | } |
133 | 0 | |
134 | 0 | nsresult rv = NS_OK; |
135 | 0 | bool pendingAsyncOperations = false; |
136 | 0 |
|
137 | 0 | #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) \ |
138 | 0 | || defined(MOZ_WIDGET_GTK) |
139 | 0 |
|
140 | 0 | nsAutoString path; |
141 | 0 | if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) { |
142 | 0 | #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID) |
143 | 0 | // On Windows and Gtk, add the download to the system's "recent documents" |
144 | 0 | // list, with a pref to disable. |
145 | 0 | { |
146 | 0 | bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS); |
147 | | #ifdef MOZ_WIDGET_ANDROID |
148 | | if (jni::IsFennec() && addToRecentDocs) { |
149 | | java::DownloadsIntegration::ScanMedia(path, aContentType); |
150 | | } |
151 | | #else |
152 | 0 | if (addToRecentDocs && !aIsPrivate) { |
153 | | #ifdef XP_WIN |
154 | | ::SHAddToRecentDocs(SHARD_PATHW, path.get()); |
155 | | #elif defined(MOZ_WIDGET_GTK) |
156 | | GtkRecentManager* manager = gtk_recent_manager_get_default(); |
157 | 0 |
|
158 | 0 | gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(), |
159 | 0 | nullptr, nullptr); |
160 | 0 | if (uri) { |
161 | 0 | gtk_recent_manager_add_item(manager, uri); |
162 | 0 | g_free(uri); |
163 | 0 | } |
164 | 0 | #endif |
165 | 0 | } |
166 | 0 | #endif |
167 | 0 | #ifdef MOZ_WIDGET_GTK |
168 | 0 | // Use GIO to store the source URI for later display in the file manager. |
169 | 0 | GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get()); |
170 | 0 | nsCString source_uri; |
171 | 0 | nsresult rv = aSource->GetSpec(source_uri); |
172 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
173 | 0 | GFileInfo *file_info = g_file_info_new(); |
174 | 0 | g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get()); |
175 | 0 | g_file_set_attributes_async(gio_file, |
176 | 0 | file_info, |
177 | 0 | G_FILE_QUERY_INFO_NONE, |
178 | 0 | G_PRIORITY_DEFAULT, |
179 | 0 | nullptr, gio_set_metadata_done, nullptr); |
180 | 0 | g_object_unref(file_info); |
181 | 0 | g_object_unref(gio_file); |
182 | 0 | #endif |
183 | 0 | } |
184 | 0 | #endif |
185 | 0 |
|
186 | | #ifdef XP_MACOSX |
187 | | // On OS X, make the downloads stack bounce. |
188 | | CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault, |
189 | | NS_ConvertUTF16toUTF8(path).get(), |
190 | | kCFStringEncodingUTF8); |
191 | | CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter(); |
192 | | ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"), |
193 | | observedObject, nullptr, TRUE); |
194 | | ::CFRelease(observedObject); |
195 | | |
196 | | // Add OS X origin and referrer file metadata |
197 | | CFStringRef pathCFStr = NULL; |
198 | | if (!path.IsEmpty()) { |
199 | | pathCFStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault, |
200 | | (const UniChar*)path.get(), |
201 | | path.Length()); |
202 | | } |
203 | | if (pathCFStr && !aIsPrivate) { |
204 | | bool isFromWeb = IsURLPossiblyFromWeb(aSource); |
205 | | nsCOMPtr<nsIURI> source(aSource); |
206 | | nsCOMPtr<nsIURI> referrer(aReferrer); |
207 | | |
208 | | rv = mIOThread->Dispatch(NS_NewRunnableFunction( |
209 | | "DownloadPlatform::DownloadDone", |
210 | | [pathCFStr, isFromWeb, source, referrer, promise]() mutable { |
211 | | CFURLRef sourceCFURL = CreateCFURLFromNSIURI(source); |
212 | | CFURLRef referrerCFURL = CreateCFURLFromNSIURI(referrer); |
213 | | |
214 | | CocoaFileUtils::AddOriginMetadataToFile(pathCFStr, |
215 | | sourceCFURL, |
216 | | referrerCFURL); |
217 | | CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr, |
218 | | sourceCFURL, |
219 | | referrerCFURL, |
220 | | isFromWeb); |
221 | | ::CFRelease(pathCFStr); |
222 | | if (sourceCFURL) { |
223 | | ::CFRelease(sourceCFURL); |
224 | | } |
225 | | if (referrerCFURL) { |
226 | | ::CFRelease(referrerCFURL); |
227 | | } |
228 | | |
229 | | DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction( |
230 | | "DownloadPlatform::DownloadDoneResolve", |
231 | | [promise = std::move(promise)]() { |
232 | | promise->MaybeResolveWithUndefined(); |
233 | | } |
234 | | )); |
235 | | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
236 | | // In non-debug builds, if we've for some reason failed to dispatch |
237 | | // a runnable to the main thread to resolve the Promise, then it's |
238 | | // unlikely we can reject it either. At that point, the Promise |
239 | | // is going to remain in pending limbo until its global goes away. |
240 | | } |
241 | | )); |
242 | | |
243 | | if (NS_SUCCEEDED(rv)) { |
244 | | pendingAsyncOperations = true; |
245 | | } |
246 | | } |
247 | | #endif |
248 | | } |
249 | 0 |
|
250 | 0 | #endif |
251 | 0 |
|
252 | 0 | if (!pendingAsyncOperations) { |
253 | 0 | promise->MaybeResolveWithUndefined(); |
254 | 0 | } |
255 | 0 | promise.forget(aPromise); |
256 | 0 | return rv; |
257 | 0 | } |
258 | | |
259 | | nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL, |
260 | | uint32_t* aZone) |
261 | 0 | { |
262 | | #ifdef XP_WIN |
263 | | RefPtr<IInternetSecurityManager> inetSecMgr; |
264 | | if (FAILED(CoCreateInstance(CLSID_InternetSecurityManager, NULL, |
265 | | CLSCTX_ALL, IID_IInternetSecurityManager, |
266 | | getter_AddRefs(inetSecMgr)))) { |
267 | | return NS_ERROR_UNEXPECTED; |
268 | | } |
269 | | |
270 | | DWORD zone; |
271 | | if (inetSecMgr->MapUrlToZone(PromiseFlatString(aURL).get(), |
272 | | &zone, 0) != S_OK) { |
273 | | return NS_ERROR_UNEXPECTED; |
274 | | } else { |
275 | | *aZone = zone; |
276 | | } |
277 | | |
278 | | return NS_OK; |
279 | | #else |
280 | | return NS_ERROR_NOT_IMPLEMENTED; |
281 | 0 | #endif |
282 | 0 | } |
283 | | |
284 | | // Check if a URI is likely to be web-based, by checking its URI flags. |
285 | | // If in doubt (e.g. if anything fails during the check) claims things |
286 | | // are from the web. |
287 | | bool DownloadPlatform::IsURLPossiblyFromWeb(nsIURI* aURI) |
288 | 0 | { |
289 | 0 | nsCOMPtr<nsIIOService> ios = do_GetIOService(); |
290 | 0 | nsCOMPtr<nsIURI> uri = aURI; |
291 | 0 | if (!ios) { |
292 | 0 | return true; |
293 | 0 | } |
294 | 0 | |
295 | 0 | while (uri) { |
296 | 0 | // We're not using nsIIOService::ProtocolHasFlags because it doesn't |
297 | 0 | // take per-URI flags into account. We're also not using |
298 | 0 | // NS_URIChainHasFlags because we're checking for *any* of 3 flags |
299 | 0 | // to be present on *all* of the nested URIs, which it can't do. |
300 | 0 | nsAutoCString scheme; |
301 | 0 | nsresult rv = uri->GetScheme(scheme); |
302 | 0 | if (NS_FAILED(rv)) { |
303 | 0 | return true; |
304 | 0 | } |
305 | 0 | nsCOMPtr<nsIProtocolHandler> ph; |
306 | 0 | rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(ph)); |
307 | 0 | if (NS_FAILED(rv)) { |
308 | 0 | return true; |
309 | 0 | } |
310 | 0 | uint32_t flags; |
311 | 0 | rv = ph->DoGetProtocolFlags(uri, &flags); |
312 | 0 | if (NS_FAILED(rv)) { |
313 | 0 | return true; |
314 | 0 | } |
315 | 0 | // If not dangerous to load, not a UI resource and not a local file, |
316 | 0 | // assume this is from the web: |
317 | 0 | if (!(flags & nsIProtocolHandler::URI_DANGEROUS_TO_LOAD) && |
318 | 0 | !(flags & nsIProtocolHandler::URI_IS_UI_RESOURCE) && |
319 | 0 | !(flags & nsIProtocolHandler::URI_IS_LOCAL_FILE)) { |
320 | 0 | return true; |
321 | 0 | } |
322 | 0 | // Otherwise, check if the URI is nested, and if so go through |
323 | 0 | // the loop again: |
324 | 0 | nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri); |
325 | 0 | uri = nullptr; |
326 | 0 | if (nestedURI) { |
327 | 0 | rv = nestedURI->GetInnerURI(getter_AddRefs(uri)); |
328 | 0 | if (NS_FAILED(rv)) { |
329 | 0 | return true; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | } |
333 | 0 | return false; |
334 | 0 | } |