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