Coverage Report

Created: 2018-09-25 14:53

/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
}