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