Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/events/DataTransferItem.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "DataTransferItem.h"
8
#include "DataTransferItemList.h"
9
10
#include "mozilla/ContentEvents.h"
11
#include "mozilla/EventForwards.h"
12
#include "mozilla/dom/DataTransferItemBinding.h"
13
#include "mozilla/dom/Directory.h"
14
#include "mozilla/dom/Event.h"
15
#include "mozilla/dom/FileSystem.h"
16
#include "mozilla/dom/FileSystemDirectoryEntry.h"
17
#include "mozilla/dom/FileSystemFileEntry.h"
18
#include "nsIClipboard.h"
19
#include "nsISupportsPrimitives.h"
20
#include "nsIScriptObjectPrincipal.h"
21
#include "nsNetUtil.h"
22
#include "nsQueryObject.h"
23
#include "nsContentUtils.h"
24
#include "nsVariant.h"
25
26
namespace {
27
28
struct FileMimeNameData
29
{
30
  const char* mMimeName;
31
  const char* mFileName;
32
};
33
34
FileMimeNameData kFileMimeNameMap[] = {
35
  { kFileMime, "GenericFileName" },
36
  { kPNGImageMime, "GenericImageNamePNG" },
37
};
38
39
} // anonymous namespace
40
41
namespace mozilla {
42
namespace dom {
43
44
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData,
45
                                      mPrincipal, mDataTransfer, mCachedFile)
46
NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem)
47
NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem)
48
49
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem)
50
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
51
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
52
0
NS_INTERFACE_MAP_END
53
54
JSObject*
55
DataTransferItem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
56
0
{
57
0
  return DataTransferItem_Binding::Wrap(aCx, this, aGivenProto);
58
0
}
59
60
already_AddRefed<DataTransferItem>
61
DataTransferItem::Clone(DataTransfer* aDataTransfer) const
62
0
{
63
0
  MOZ_ASSERT(aDataTransfer);
64
0
65
0
  RefPtr<DataTransferItem> it = new DataTransferItem(aDataTransfer, mType);
66
0
67
0
  // Copy over all of the fields
68
0
  it->mKind = mKind;
69
0
  it->mIndex = mIndex;
70
0
  it->mData = mData;
71
0
  it->mPrincipal = mPrincipal;
72
0
  it->mChromeOnly = mChromeOnly;
73
0
74
0
  return it.forget();
75
0
}
76
77
void
78
DataTransferItem::SetData(nsIVariant* aData)
79
0
{
80
0
  // Invalidate our file cache, we will regenerate it with the new data
81
0
  mCachedFile = nullptr;
82
0
83
0
  if (!aData) {
84
0
    // We are holding a temporary null which will later be filled.
85
0
    // These are provided by the system, and have guaranteed properties about
86
0
    // their kind based on their type.
87
0
    MOZ_ASSERT(!mType.IsEmpty());
88
0
89
0
    mKind = KIND_STRING;
90
0
    for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
91
0
      if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
92
0
        mKind = KIND_FILE;
93
0
        break;
94
0
      }
95
0
    }
96
0
97
0
    mData = nullptr;
98
0
    return;
99
0
  }
100
0
101
0
  mData = aData;
102
0
  mKind = KindFromData(mData);
103
0
}
104
105
/* static */ DataTransferItem::eKind
106
DataTransferItem::KindFromData(nsIVariant* aData)
107
0
{
108
0
  nsCOMPtr<nsISupports> supports;
109
0
  nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
110
0
  if (NS_SUCCEEDED(rv) && supports) {
111
0
    // Check if we have one of the supported file data formats
112
0
    if (RefPtr<Blob>(do_QueryObject(supports)) ||
113
0
        nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
114
0
        nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
115
0
      return KIND_FILE;
116
0
    }
117
0
  }
118
0
119
0
  nsAutoString string;
120
0
  // If we can't get the data type as a string, that means that the object
121
0
  // should be considered to be of the "other" type. This is impossible
122
0
  // through the APIs defined by the spec, but we provide extra Moz* APIs,
123
0
  // which allow setting of non-string data. We determine whether we can
124
0
  // consider it a string, by calling GetAsAString, and checking for success.
125
0
  rv = aData->GetAsAString(string);
126
0
  if (NS_SUCCEEDED(rv)) {
127
0
    return KIND_STRING;
128
0
  }
129
0
130
0
  return KIND_OTHER;
131
0
}
132
133
void
134
DataTransferItem::FillInExternalData()
135
0
{
136
0
  if (mData) {
137
0
    return;
138
0
  }
139
0
140
0
  NS_ConvertUTF16toUTF8 utf8format(mType);
141
0
  const char* format = utf8format.get();
142
0
  if (strcmp(format, "text/plain") == 0) {
143
0
    format = kUnicodeMime;
144
0
  } else if (strcmp(format, "text/uri-list") == 0) {
145
0
    format = kURLDataMime;
146
0
  }
147
0
148
0
  nsCOMPtr<nsITransferable> trans =
149
0
    do_CreateInstance("@mozilla.org/widget/transferable;1");
150
0
  if (NS_WARN_IF(!trans)) {
151
0
    return;
152
0
  }
153
0
154
0
  trans->Init(nullptr);
155
0
  trans->AddDataFlavor(format);
156
0
157
0
  if (mDataTransfer->GetEventMessage() == ePaste) {
158
0
    MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
159
0
160
0
    nsCOMPtr<nsIClipboard> clipboard =
161
0
      do_GetService("@mozilla.org/widget/clipboard;1");
162
0
    if (!clipboard || mDataTransfer->ClipboardType() < 0) {
163
0
      return;
164
0
    }
165
0
166
0
    nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType());
167
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
168
0
      return;
169
0
    }
170
0
  } else {
171
0
    nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
172
0
    if (!dragSession) {
173
0
      return;
174
0
    }
175
0
176
0
    nsresult rv = dragSession->GetData(trans, mIndex);
177
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
178
0
      return;
179
0
    }
180
0
  }
181
0
182
0
  uint32_t length = 0;
183
0
  nsCOMPtr<nsISupports> data;
184
0
  nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length);
185
0
  if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
186
0
    return;
187
0
  }
188
0
189
0
  // Fill the variant
190
0
  RefPtr<nsVariantCC> variant = new nsVariantCC();
191
0
192
0
  eKind oldKind = Kind();
193
0
  if (oldKind == KIND_FILE) {
194
0
    // Because this is an external piece of data, mType is one of kFileMime,
195
0
    // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
196
0
    // are passed in as a nsIInputStream which must be converted to a
197
0
    // dom::File before storing.
198
0
    if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
199
0
      RefPtr<File> file = CreateFileFromInputStream(istream);
200
0
      if (NS_WARN_IF(!file)) {
201
0
        return;
202
0
      }
203
0
      data = do_QueryObject(file);
204
0
    }
205
0
206
0
    variant->SetAsISupports(data);
207
0
  } else {
208
0
    // We have an external piece of string data. Extract it and store it in the variant
209
0
    MOZ_ASSERT(oldKind == KIND_STRING);
210
0
211
0
    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
212
0
    if (supportsstr) {
213
0
      nsAutoString str;
214
0
      supportsstr->GetData(str);
215
0
      variant->SetAsAString(str);
216
0
    } else {
217
0
      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
218
0
      if (supportscstr) {
219
0
        nsAutoCString str;
220
0
        supportscstr->GetData(str);
221
0
        variant->SetAsACString(str);
222
0
      }
223
0
    }
224
0
  }
225
0
226
0
  SetData(variant);
227
0
228
0
  if (oldKind != Kind()) {
229
0
    NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
230
0
    mDataTransfer->TypesListMayHaveChanged();
231
0
  }
232
0
}
233
234
void
235
DataTransferItem::GetType(nsAString& aType)
236
0
{
237
0
  // If we don't have a File, we can just put whatever our recorded internal
238
0
  // type is.
239
0
  if (Kind() != KIND_FILE) {
240
0
    aType = mType;
241
0
    return;
242
0
  }
243
0
244
0
  // If we do have a File, then we need to look at our File object to discover
245
0
  // what its mime type is. We can use the System Principal here, as this
246
0
  // information should be avaliable even if the data is currently inaccessible
247
0
  // (for example during a dragover).
248
0
  //
249
0
  // XXX: This seems inefficient, as it seems like we should be able to get this
250
0
  // data without getting the entire File object, which may require talking to
251
0
  // the OS.
252
0
  ErrorResult rv;
253
0
  RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv);
254
0
  MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal");
255
0
256
0
  // If we don't actually have a file, fall back to returning the internal type.
257
0
  if (NS_WARN_IF(!file)) {
258
0
    aType = mType;
259
0
    return;
260
0
  }
261
0
262
0
  file->GetType(aType);
263
0
}
264
265
already_AddRefed<File>
266
DataTransferItem::GetAsFile(nsIPrincipal& aSubjectPrincipal,
267
                            ErrorResult& aRv)
268
0
{
269
0
  // This is done even if we have an mCachedFile, as it performs the necessary
270
0
  // permissions checks to ensure that we are allowed to access this type.
271
0
  nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
272
0
  if (NS_WARN_IF(!data || aRv.Failed())) {
273
0
    return nullptr;
274
0
  }
275
0
276
0
  // We have to check our kind after getting the data, because if we have
277
0
  // external data and the OS lied to us (which unfortunately does happen
278
0
  // sometimes), then we might not have the same type of data as we did coming
279
0
  // into this function.
280
0
  if (NS_WARN_IF(mKind != KIND_FILE)) {
281
0
    return nullptr;
282
0
  }
283
0
284
0
  // Generate the dom::File from the stored data, caching it so that the
285
0
  // same object is returned in the future.
286
0
  if (!mCachedFile) {
287
0
    nsCOMPtr<nsISupports> supports;
288
0
    aRv = data->GetAsISupports(getter_AddRefs(supports));
289
0
    MOZ_ASSERT(!aRv.Failed() && supports,
290
0
               "File objects should be stored as nsISupports variants");
291
0
    if (aRv.Failed() || !supports) {
292
0
      return nullptr;
293
0
    }
294
0
295
0
    if (RefPtr<Blob> blob = do_QueryObject(supports)) {
296
0
      mCachedFile = blob->ToFile();
297
0
    } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
298
0
      MOZ_ASSERT(blobImpl->IsFile());
299
0
      mCachedFile = File::Create(mDataTransfer, blobImpl);
300
0
    } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
301
0
      mCachedFile = File::CreateFromFile(mDataTransfer, ifile);
302
0
    } else {
303
0
      MOZ_ASSERT(false, "One of the above code paths should be taken");
304
0
      return nullptr;
305
0
    }
306
0
  }
307
0
308
0
  RefPtr<File> file = mCachedFile;
309
0
  return file.forget();
310
0
}
311
312
already_AddRefed<FileSystemEntry>
313
DataTransferItem::GetAsEntry(nsIPrincipal& aSubjectPrincipal,
314
                             ErrorResult& aRv)
315
0
{
316
0
  RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv);
317
0
  if (NS_WARN_IF(aRv.Failed()) || !file) {
318
0
    return nullptr;
319
0
  }
320
0
321
0
  nsCOMPtr<nsIGlobalObject> global;
322
0
  // This is annoying, but DataTransfer may have various things as parent.
323
0
  nsCOMPtr<EventTarget> target =
324
0
    do_QueryInterface(mDataTransfer->GetParentObject());
325
0
  if (target) {
326
0
    global = target->GetOwnerGlobal();
327
0
  } else {
328
0
    RefPtr<Event> event = do_QueryObject(mDataTransfer->GetParentObject());
329
0
    if (event) {
330
0
      global = event->GetParentObject();
331
0
    }
332
0
  }
333
0
334
0
  if (!global) {
335
0
    return nullptr;
336
0
  }
337
0
338
0
  RefPtr<FileSystem> fs = FileSystem::Create(global);
339
0
  RefPtr<FileSystemEntry> entry;
340
0
  BlobImpl* impl = file->Impl();
341
0
  MOZ_ASSERT(impl);
342
0
343
0
  if (impl->IsDirectory()) {
344
0
    nsAutoString fullpath;
345
0
    impl->GetMozFullPathInternal(fullpath, aRv);
346
0
    if (aRv.Failed()) {
347
0
      aRv.SuppressException();
348
0
      return nullptr;
349
0
    }
350
0
351
0
    nsCOMPtr<nsIFile> directoryFile;
352
0
    // fullPath is already in unicode, we don't have to use
353
0
    // NS_NewNativeLocalFile.
354
0
    nsresult rv = NS_NewLocalFile(fullpath, true,
355
0
                                  getter_AddRefs(directoryFile));
356
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
357
0
      return nullptr;
358
0
    }
359
0
360
0
    RefPtr<Directory> directory = Directory::Create(global, directoryFile);
361
0
    entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs);
362
0
  } else {
363
0
    entry = new FileSystemFileEntry(global, file, nullptr, fs);
364
0
  }
365
0
366
0
  Sequence<RefPtr<FileSystemEntry>> entries;
367
0
  if (!entries.AppendElement(entry, fallible)) {
368
0
    return nullptr;
369
0
  }
370
0
371
0
  fs->CreateRoot(entries);
372
0
  return entry.forget();
373
0
}
374
375
already_AddRefed<File>
376
DataTransferItem::CreateFileFromInputStream(nsIInputStream* aStream)
377
0
{
378
0
  const char* key = nullptr;
379
0
  for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
380
0
    if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
381
0
      key = kFileMimeNameMap[i].mFileName;
382
0
      break;
383
0
    }
384
0
  }
385
0
  if (!key) {
386
0
    MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
387
0
    key = "GenericFileName";
388
0
  }
389
0
390
0
  nsAutoString fileName;
391
0
  nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
392
0
                                                   key, fileName);
393
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
394
0
    return nullptr;
395
0
  }
396
0
397
0
  uint64_t available;
398
0
  void* data = nullptr;
399
0
  rv = NS_ReadInputStreamToBuffer(aStream, &data, -1, &available);
400
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
401
0
    return nullptr;
402
0
  }
403
0
404
0
  return File::CreateMemoryFile(mDataTransfer, data, available, fileName,
405
0
                                mType, PR_Now());
406
0
}
407
408
void
409
DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
410
                              nsIPrincipal& aSubjectPrincipal,
411
                              ErrorResult& aRv)
412
0
{
413
0
  if (!aCallback) {
414
0
    return;
415
0
  }
416
0
417
0
  // Theoretically this should be done inside of the runnable, as it might be an
418
0
  // expensive operation on some systems, however we wouldn't get access to the
419
0
  // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
420
0
  nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
421
0
  if (NS_WARN_IF(!data || aRv.Failed())) {
422
0
    return;
423
0
  }
424
0
425
0
  // We have to check our kind after getting the data, because if we have
426
0
  // external data and the OS lied to us (which unfortunately does happen
427
0
  // sometimes), then we might not have the same type of data as we did coming
428
0
  // into this function.
429
0
  if (NS_WARN_IF(mKind != KIND_STRING)) {
430
0
    return;
431
0
  }
432
0
433
0
  nsAutoString stringData;
434
0
  nsresult rv = data->GetAsAString(stringData);
435
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
436
0
    return;
437
0
  }
438
0
439
0
  // Dispatch the callback to the main thread
440
0
  class GASRunnable final : public Runnable
441
0
  {
442
0
  public:
443
0
    GASRunnable(FunctionStringCallback* aCallback, const nsAString& aStringData)
444
0
      : mozilla::Runnable("GASRunnable")
445
0
      , mCallback(aCallback)
446
0
      , mStringData(aStringData)
447
0
    {}
448
0
449
0
    NS_IMETHOD Run() override
450
0
    {
451
0
      ErrorResult rv;
452
0
      mCallback->Call(mStringData, rv);
453
0
      NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
454
0
      return rv.StealNSResult();
455
0
    }
456
0
  private:
457
0
    RefPtr<FunctionStringCallback> mCallback;
458
0
    nsString mStringData;
459
0
  };
460
0
461
0
  RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
462
0
463
0
  // DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
464
0
  // nsPIDOMWindowOuter, null
465
0
  nsISupports* parent = mDataTransfer->GetParentObject();
466
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(parent);
467
0
  if (parent && !global) {
468
0
    if (nsCOMPtr<dom::EventTarget> target = do_QueryInterface(parent)) {
469
0
      global = target->GetOwnerGlobal();
470
0
    } else if (RefPtr<Event> event = do_QueryObject(parent)) {
471
0
      global = event->GetParentObject();
472
0
    }
473
0
  }
474
0
  if (global) {
475
0
    rv = global->Dispatch(TaskCategory::Other, runnable.forget());
476
0
  } else {
477
0
    rv = NS_DispatchToMainThread(runnable);
478
0
  }
479
0
  if (NS_FAILED(rv)) {
480
0
    NS_WARNING("Dispatch to main thread Failed in "
481
0
               "DataTransferItem::GetAsString!");
482
0
  }
483
0
}
484
485
already_AddRefed<nsIVariant>
486
DataTransferItem::DataNoSecurityCheck()
487
0
{
488
0
  if (!mData) {
489
0
    FillInExternalData();
490
0
  }
491
0
  nsCOMPtr<nsIVariant> data = mData;
492
0
  return data.forget();
493
0
}
494
495
already_AddRefed<nsIVariant>
496
DataTransferItem::Data(nsIPrincipal* aPrincipal, ErrorResult& aRv)
497
0
{
498
0
  MOZ_ASSERT(aPrincipal);
499
0
500
0
  // If the inbound principal is system, we can skip the below checks, as
501
0
  // they will trivially succeed.
502
0
  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
503
0
    return DataNoSecurityCheck();
504
0
  }
505
0
506
0
  // We should not allow raw data to be accessed from a Protected DataTransfer.
507
0
  // We don't prevent this access if the accessing document is Chrome.
508
0
  if (mDataTransfer->IsProtected()) {
509
0
    return nullptr;
510
0
  }
511
0
512
0
  nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck();
513
0
514
0
  MOZ_ASSERT(!ChromeOnly(), "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
515
0
  if (ChromeOnly()) {
516
0
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
517
0
    return nullptr;
518
0
  }
519
0
520
0
  bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() ||
521
0
    (mDataTransfer->GetEventMessage() != eDrop &&
522
0
     mDataTransfer->GetEventMessage() != ePaste);
523
0
524
0
  // Check if the caller is allowed to access the drag data. Callers with
525
0
  // chrome privileges can always read the data. During the
526
0
  // drop event, allow retrieving the data except in the case where the
527
0
  // source of the drag is in a child frame of the caller. In that case,
528
0
  // we only allow access to data of the same principal. During other events,
529
0
  // only allow access to the data with the same principal.
530
0
  //
531
0
  // We don't want to fail with an exception in this siutation, rather we want
532
0
  // to just pretend as though the stored data is "nullptr". This is consistent
533
0
  // with Chrome's behavior and is less surprising for web applications which
534
0
  // don't expect execptions to be raised when performing certain operations.
535
0
  if (Principal() && checkItemPrincipal &&
536
0
      !aPrincipal->Subsumes(Principal())) {
537
0
    return nullptr;
538
0
  }
539
0
540
0
  if (!variant) {
541
0
    return nullptr;
542
0
  }
543
0
544
0
  nsCOMPtr<nsISupports> data;
545
0
  nsresult rv = variant->GetAsISupports(getter_AddRefs(data));
546
0
  if (NS_SUCCEEDED(rv) && data) {
547
0
    nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
548
0
    if (pt) {
549
0
      nsIGlobalObject* go = pt->GetOwnerGlobal();
550
0
      if (NS_WARN_IF(!go)) {
551
0
        return nullptr;
552
0
      }
553
0
554
0
      nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
555
0
      MOZ_ASSERT(sp, "This cannot fail on the main thread.");
556
0
557
0
      nsIPrincipal* dataPrincipal = sp->GetPrincipal();
558
0
      if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) {
559
0
        return nullptr;
560
0
      }
561
0
    }
562
0
  }
563
0
564
0
  return variant.forget();
565
0
}
566
567
} // namespace dom
568
} // namespace mozilla