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