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