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 "mozilla/ArrayUtils.h"
8 #include "mozilla/BasePrincipal.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/Span.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "DataTransfer.h"
14 
15 #include "nsISupportsPrimitives.h"
16 #include "nsIScriptSecurityManager.h"
17 #include "mozilla/dom/DOMStringList.h"
18 #include "nsArray.h"
19 #include "nsError.h"
20 #include "nsIDragService.h"
21 #include "nsIClipboard.h"
22 #include "nsIXPConnect.h"
23 #include "nsContentUtils.h"
24 #include "nsIContent.h"
25 #include "nsIObjectInputStream.h"
26 #include "nsIObjectOutputStream.h"
27 #include "nsIStorageStream.h"
28 #include "nsStringStream.h"
29 #include "nsCRT.h"
30 #include "nsIScriptObjectPrincipal.h"
31 #include "nsIScriptContext.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsIScriptGlobalObject.h"
34 #include "nsVariant.h"
35 #include "mozilla/dom/ContentChild.h"
36 #include "mozilla/dom/DataTransferBinding.h"
37 #include "mozilla/dom/DataTransferItemList.h"
38 #include "mozilla/dom/Directory.h"
39 #include "mozilla/dom/Element.h"
40 #include "mozilla/dom/FileList.h"
41 #include "mozilla/dom/BindingUtils.h"
42 #include "mozilla/dom/OSFileSystem.h"
43 #include "mozilla/dom/Promise.h"
44 #include "nsComponentManagerUtils.h"
45 #include "nsNetUtil.h"
46 #include "nsReadableUtils.h"
47 
48 namespace mozilla::dom {
49 
50 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
51 
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
53   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
54   NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems)
55   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget)
56   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage)
57   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer)
60   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
61   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems)
62   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget)
63   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
65 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer)
66 
67 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer)
68 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransfer)
69 
70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransfer)
71   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
72   NS_INTERFACE_MAP_ENTRY(mozilla::dom::DataTransfer)
73   NS_INTERFACE_MAP_ENTRY(nsISupports)
74 NS_INTERFACE_MAP_END
75 
76 // the size of the array
77 const char DataTransfer::sEffects[8][9] = {
78     "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"};
79 
80 // Used for custom clipboard types.
81 enum CustomClipboardTypeId {
82   eCustomClipboardTypeId_None,
83   eCustomClipboardTypeId_String
84 };
85 
ModeForEvent(EventMessage aEventMessage)86 static DataTransfer::Mode ModeForEvent(EventMessage aEventMessage) {
87   switch (aEventMessage) {
88     case eCut:
89     case eCopy:
90     case eDragStart:
91       // For these events, we want to be able to add data to the data transfer,
92       // Otherwise, the data is already present.
93       return DataTransfer::Mode::ReadWrite;
94     case eDrop:
95     case ePaste:
96     case ePasteNoFormatting:
97     case eEditorInput:
98       // For these events we want to be able to read the data which is stored in
99       // the DataTransfer, rather than just the type information.
100       return DataTransfer::Mode::ReadOnly;
101     default:
102       return StaticPrefs::dom_events_dataTransfer_protected_enabled()
103                  ? DataTransfer::Mode::Protected
104                  : DataTransfer::Mode::ReadOnly;
105   }
106 }
107 
DataTransfer(nsISupports * aParent,EventMessage aEventMessage,bool aIsExternal,int32_t aClipboardType)108 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
109                            bool aIsExternal, int32_t aClipboardType)
110     : mParent(aParent),
111       mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
112       mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
113       mEventMessage(aEventMessage),
114       mCursorState(false),
115       mMode(ModeForEvent(aEventMessage)),
116       mIsExternal(aIsExternal),
117       mUserCancelled(false),
118       mIsCrossDomainSubFrameDrop(false),
119       mClipboardType(aClipboardType),
120       mDragImageX(0),
121       mDragImageY(0) {
122   mItems = new DataTransferItemList(this);
123 
124   // For external usage, cache the data from the native clipboard or drag.
125   if (mIsExternal && mMode != Mode::ReadWrite) {
126     if (aEventMessage == ePasteNoFormatting) {
127       mEventMessage = ePaste;
128       CacheExternalClipboardFormats(true);
129     } else if (aEventMessage == ePaste) {
130       CacheExternalClipboardFormats(false);
131     } else if (aEventMessage >= eDragDropEventFirst &&
132                aEventMessage <= eDragDropEventLast) {
133       CacheExternalDragFormats();
134     }
135   }
136 }
137 
DataTransfer(nsISupports * aParent,EventMessage aEventMessage,nsITransferable * aTransferable)138 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
139                            nsITransferable* aTransferable)
140     : mParent(aParent),
141       mTransferable(aTransferable),
142       mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
143       mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
144       mEventMessage(aEventMessage),
145       mCursorState(false),
146       mMode(ModeForEvent(aEventMessage)),
147       mIsExternal(true),
148       mUserCancelled(false),
149       mIsCrossDomainSubFrameDrop(false),
150       mClipboardType(-1),
151       mDragImageX(0),
152       mDragImageY(0) {
153   mItems = new DataTransferItemList(this);
154 
155   // XXX Currently, we cannot make DataTransfer grabs mTransferable for long
156   //     time because nsITransferable is not cycle collectable but this may
157   //     be grabbed by JS.  Additionally, the data initializing path is too
158   //     complicated (too optimized) for D&D and clipboard.  They are cached
159   //     only formats first, then, data of all items will be filled by the
160   //     items later and by themselves.  However, we shouldn't duplicate such
161   //     path for saving the maintenance cost.  Therefore, we need to treat
162   //     that DataTransfer and its items are in external mode.  Finally,
163   //     release mTransferable and make them in internal mode.
164   CacheTransferableFormats();
165   FillAllExternalData();
166   // Now, we have all necessary data of mTransferable.  So, we can work as
167   // internal mode.
168   mIsExternal = false;
169   // Release mTransferable because it won't be referred anymore.
170   mTransferable = nullptr;
171 }
172 
DataTransfer(nsISupports * aParent,EventMessage aEventMessage,const nsAString & aString)173 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
174                            const nsAString& aString)
175     : mParent(aParent),
176       mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
177       mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
178       mEventMessage(aEventMessage),
179       mCursorState(false),
180       mMode(ModeForEvent(aEventMessage)),
181       mIsExternal(false),
182       mUserCancelled(false),
183       mIsCrossDomainSubFrameDrop(false),
184       mClipboardType(-1),
185       mDragImageX(0),
186       mDragImageY(0) {
187   mItems = new DataTransferItemList(this);
188 
189   nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
190 
191   RefPtr<nsVariantCC> variant = new nsVariantCC();
192   variant->SetAsAString(aString);
193   DebugOnly<nsresult> rvIgnored =
194       SetDataWithPrincipal(u"text/plain"_ns, variant, 0, sysPrincipal, false);
195   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
196                        "Failed to set given string to the DataTransfer object");
197 }
198 
DataTransfer(nsISupports * aParent,EventMessage aEventMessage,const uint32_t aEffectAllowed,bool aCursorState,bool aIsExternal,bool aUserCancelled,bool aIsCrossDomainSubFrameDrop,int32_t aClipboardType,DataTransferItemList * aItems,Element * aDragImage,uint32_t aDragImageX,uint32_t aDragImageY)199 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
200                            const uint32_t aEffectAllowed, bool aCursorState,
201                            bool aIsExternal, bool aUserCancelled,
202                            bool aIsCrossDomainSubFrameDrop,
203                            int32_t aClipboardType, DataTransferItemList* aItems,
204                            Element* aDragImage, uint32_t aDragImageX,
205                            uint32_t aDragImageY)
206     : mParent(aParent),
207       mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
208       mEffectAllowed(aEffectAllowed),
209       mEventMessage(aEventMessage),
210       mCursorState(aCursorState),
211       mMode(ModeForEvent(aEventMessage)),
212       mIsExternal(aIsExternal),
213       mUserCancelled(aUserCancelled),
214       mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop),
215       mClipboardType(aClipboardType),
216       mDragImage(aDragImage),
217       mDragImageX(aDragImageX),
218       mDragImageY(aDragImageY) {
219   MOZ_ASSERT(mParent);
220   MOZ_ASSERT(aItems);
221 
222   // We clone the items array after everything else, so that it has a valid
223   // mParent value
224   mItems = aItems->Clone(this);
225   // The items are copied from aItems into mItems. There is no need to copy
226   // the actual data in the items as the data transfer will be read only. The
227   // dragstart event is the only time when items are
228   // modifiable, but those events should have been using the first constructor
229   // above.
230   NS_ASSERTION(aEventMessage != eDragStart,
231                "invalid event type for DataTransfer constructor");
232 }
233 
234 DataTransfer::~DataTransfer() = default;
235 
236 // static
Constructor(const GlobalObject & aGlobal)237 already_AddRefed<DataTransfer> DataTransfer::Constructor(
238     const GlobalObject& aGlobal) {
239   RefPtr<DataTransfer> transfer =
240       new DataTransfer(aGlobal.GetAsSupports(), eCopy, /* is external */ false,
241                        /* clipboard type */ -1);
242   transfer->mEffectAllowed = nsIDragService::DRAGDROP_ACTION_NONE;
243   return transfer.forget();
244 }
245 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)246 JSObject* DataTransfer::WrapObject(JSContext* aCx,
247                                    JS::Handle<JSObject*> aGivenProto) {
248   return DataTransfer_Binding::Wrap(aCx, this, aGivenProto);
249 }
250 
SetDropEffect(const nsAString & aDropEffect)251 void DataTransfer::SetDropEffect(const nsAString& aDropEffect) {
252   // the drop effect can only be 'none', 'copy', 'move' or 'link'.
253   for (uint32_t e = 0; e <= nsIDragService::DRAGDROP_ACTION_LINK; e++) {
254     if (aDropEffect.EqualsASCII(sEffects[e])) {
255       // don't allow copyMove
256       if (e != (nsIDragService::DRAGDROP_ACTION_COPY |
257                 nsIDragService::DRAGDROP_ACTION_MOVE)) {
258         mDropEffect = e;
259       }
260       break;
261     }
262   }
263 }
264 
SetEffectAllowed(const nsAString & aEffectAllowed)265 void DataTransfer::SetEffectAllowed(const nsAString& aEffectAllowed) {
266   if (aEffectAllowed.EqualsLiteral("uninitialized")) {
267     mEffectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
268     return;
269   }
270 
271   static_assert(nsIDragService::DRAGDROP_ACTION_NONE == 0,
272                 "DRAGDROP_ACTION_NONE constant is wrong");
273   static_assert(nsIDragService::DRAGDROP_ACTION_COPY == 1,
274                 "DRAGDROP_ACTION_COPY constant is wrong");
275   static_assert(nsIDragService::DRAGDROP_ACTION_MOVE == 2,
276                 "DRAGDROP_ACTION_MOVE constant is wrong");
277   static_assert(nsIDragService::DRAGDROP_ACTION_LINK == 4,
278                 "DRAGDROP_ACTION_LINK constant is wrong");
279 
280   for (uint32_t e = 0; e < ArrayLength(sEffects); e++) {
281     if (aEffectAllowed.EqualsASCII(sEffects[e])) {
282       mEffectAllowed = e;
283       break;
284     }
285   }
286 }
287 
GetMozTriggeringPrincipalURISpec(nsAString & aPrincipalURISpec)288 void DataTransfer::GetMozTriggeringPrincipalURISpec(
289     nsAString& aPrincipalURISpec) {
290   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
291   if (!dragSession) {
292     aPrincipalURISpec.Truncate(0);
293     return;
294   }
295 
296   nsCOMPtr<nsIPrincipal> principal;
297   dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
298   if (!principal) {
299     aPrincipalURISpec.Truncate(0);
300     return;
301   }
302 
303   nsAutoCString spec;
304   principal->GetAsciiSpec(spec);
305   CopyUTF8toUTF16(spec, aPrincipalURISpec);
306 }
307 
GetMozCSP()308 nsIContentSecurityPolicy* DataTransfer::GetMozCSP() {
309   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
310   if (!dragSession) {
311     return nullptr;
312   }
313   nsCOMPtr<nsIContentSecurityPolicy> csp;
314   dragSession->GetCsp(getter_AddRefs(csp));
315   return csp;
316 }
317 
GetFiles(nsIPrincipal & aSubjectPrincipal)318 already_AddRefed<FileList> DataTransfer::GetFiles(
319     nsIPrincipal& aSubjectPrincipal) {
320   return mItems->Files(&aSubjectPrincipal);
321 }
322 
GetTypes(nsTArray<nsString> & aTypes,CallerType aCallerType) const323 void DataTransfer::GetTypes(nsTArray<nsString>& aTypes,
324                             CallerType aCallerType) const {
325   // When called from bindings, aTypes will be empty, but since we might have
326   // Gecko-internal callers too, clear it to be safe.
327   aTypes.Clear();
328 
329   return mItems->GetTypes(aTypes, aCallerType);
330 }
331 
HasType(const nsAString & aType) const332 bool DataTransfer::HasType(const nsAString& aType) const {
333   return mItems->HasType(aType);
334 }
335 
HasFile() const336 bool DataTransfer::HasFile() const { return mItems->HasFile(); }
337 
GetData(const nsAString & aFormat,nsAString & aData,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv) const338 void DataTransfer::GetData(const nsAString& aFormat, nsAString& aData,
339                            nsIPrincipal& aSubjectPrincipal,
340                            ErrorResult& aRv) const {
341   // return an empty string if data for the format was not found
342   aData.Truncate();
343 
344   nsCOMPtr<nsIVariant> data;
345   nsresult rv =
346       GetDataAtInternal(aFormat, 0, &aSubjectPrincipal, getter_AddRefs(data));
347   if (NS_FAILED(rv)) {
348     if (rv != NS_ERROR_DOM_INDEX_SIZE_ERR) {
349       aRv.Throw(rv);
350     }
351     return;
352   }
353 
354   if (data) {
355     nsAutoString stringdata;
356     data->GetAsAString(stringdata);
357 
358     // for the URL type, parse out the first URI from the list. The URIs are
359     // separated by newlines
360     nsAutoString lowercaseFormat;
361     nsContentUtils::ASCIIToLower(aFormat, lowercaseFormat);
362 
363     if (lowercaseFormat.EqualsLiteral("url")) {
364       int32_t lastidx = 0, idx;
365       int32_t length = stringdata.Length();
366       while (lastidx < length) {
367         idx = stringdata.FindChar('\n', lastidx);
368         // lines beginning with # are comments
369         if (stringdata[lastidx] == '#') {
370           if (idx == -1) {
371             break;
372           }
373         } else {
374           if (idx == -1) {
375             aData.Assign(Substring(stringdata, lastidx));
376           } else {
377             aData.Assign(Substring(stringdata, lastidx, idx - lastidx));
378           }
379           aData =
380               nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData, true);
381           return;
382         }
383         lastidx = idx + 1;
384       }
385     } else {
386       aData = stringdata;
387     }
388   }
389 }
390 
SetData(const nsAString & aFormat,const nsAString & aData,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)391 void DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData,
392                            nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
393   RefPtr<nsVariantCC> variant = new nsVariantCC();
394   variant->SetAsAString(aData);
395 
396   aRv = SetDataAtInternal(aFormat, variant, 0, &aSubjectPrincipal);
397 }
398 
ClearData(const Optional<nsAString> & aFormat,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)399 void DataTransfer::ClearData(const Optional<nsAString>& aFormat,
400                              nsIPrincipal& aSubjectPrincipal,
401                              ErrorResult& aRv) {
402   if (IsReadOnly()) {
403     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
404     return;
405   }
406 
407   if (MozItemCount() == 0) {
408     return;
409   }
410 
411   if (aFormat.WasPassed()) {
412     MozClearDataAtHelper(aFormat.Value(), 0, aSubjectPrincipal, aRv);
413   } else {
414     MozClearDataAtHelper(u""_ns, 0, aSubjectPrincipal, aRv);
415   }
416 }
417 
SetMozCursor(const nsAString & aCursorState)418 void DataTransfer::SetMozCursor(const nsAString& aCursorState) {
419   // Lock the cursor to an arrow during the drag.
420   mCursorState = aCursorState.EqualsLiteral("default");
421 }
422 
GetMozSourceNode()423 already_AddRefed<nsINode> DataTransfer::GetMozSourceNode() {
424   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
425   if (!dragSession) {
426     return nullptr;
427   }
428 
429   nsCOMPtr<nsINode> sourceNode;
430   dragSession->GetSourceNode(getter_AddRefs(sourceNode));
431   if (sourceNode && !nsContentUtils::LegacyIsCallerNativeCode() &&
432       !nsContentUtils::CanCallerAccess(sourceNode)) {
433     return nullptr;
434   }
435 
436   return sourceNode.forget();
437 }
438 
MozTypesAt(uint32_t aIndex,CallerType aCallerType,ErrorResult & aRv) const439 already_AddRefed<DOMStringList> DataTransfer::MozTypesAt(
440     uint32_t aIndex, CallerType aCallerType, ErrorResult& aRv) const {
441   // Only the first item is valid for clipboard events
442   if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
443                      mEventMessage == ePaste)) {
444     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
445     return nullptr;
446   }
447 
448   RefPtr<DOMStringList> types = new DOMStringList();
449   if (aIndex < MozItemCount()) {
450     // note that you can retrieve the types regardless of their principal
451     const nsTArray<RefPtr<DataTransferItem>>& items =
452         *mItems->MozItemsAt(aIndex);
453 
454     bool addFile = false;
455     for (uint32_t i = 0; i < items.Length(); i++) {
456       if (items[i]->ChromeOnly() && aCallerType != CallerType::System) {
457         continue;
458       }
459 
460       // NOTE: The reason why we get the internal type here is because we want
461       // kFileMime to appear in the types list for backwards compatibility
462       // reasons.
463       nsAutoString type;
464       items[i]->GetInternalType(type);
465       if (NS_WARN_IF(!types->Add(type))) {
466         aRv.Throw(NS_ERROR_FAILURE);
467         return nullptr;
468       }
469 
470       if (items[i]->Kind() == DataTransferItem::KIND_FILE) {
471         addFile = true;
472       }
473     }
474 
475     if (addFile) {
476       types->Add(u"Files"_ns);
477     }
478   }
479 
480   return types.forget();
481 }
482 
GetDataAtNoSecurityCheck(const nsAString & aFormat,uint32_t aIndex,nsIVariant ** aData) const483 nsresult DataTransfer::GetDataAtNoSecurityCheck(const nsAString& aFormat,
484                                                 uint32_t aIndex,
485                                                 nsIVariant** aData) const {
486   return GetDataAtInternal(aFormat, aIndex,
487                            nsContentUtils::GetSystemPrincipal(), aData);
488 }
489 
GetDataAtInternal(const nsAString & aFormat,uint32_t aIndex,nsIPrincipal * aSubjectPrincipal,nsIVariant ** aData) const490 nsresult DataTransfer::GetDataAtInternal(const nsAString& aFormat,
491                                          uint32_t aIndex,
492                                          nsIPrincipal* aSubjectPrincipal,
493                                          nsIVariant** aData) const {
494   *aData = nullptr;
495 
496   if (aFormat.IsEmpty()) {
497     return NS_OK;
498   }
499 
500   if (aIndex >= MozItemCount()) {
501     return NS_ERROR_DOM_INDEX_SIZE_ERR;
502   }
503 
504   // Only the first item is valid for clipboard events
505   if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
506                      mEventMessage == ePaste)) {
507     return NS_ERROR_DOM_INDEX_SIZE_ERR;
508   }
509 
510   nsAutoString format;
511   GetRealFormat(aFormat, format);
512 
513   MOZ_ASSERT(aSubjectPrincipal);
514 
515   RefPtr<DataTransferItem> item = mItems->MozItemByTypeAt(format, aIndex);
516   if (!item) {
517     // The index exists but there's no data for the specified format, in this
518     // case we just return undefined
519     return NS_OK;
520   }
521 
522   // If we have chrome only content, and we aren't chrome, don't allow access
523   if (!aSubjectPrincipal->IsSystemPrincipal() && item->ChromeOnly()) {
524     return NS_OK;
525   }
526 
527   // DataTransferItem::Data() handles the principal checks
528   ErrorResult result;
529   nsCOMPtr<nsIVariant> data = item->Data(aSubjectPrincipal, result);
530   if (NS_WARN_IF(!data || result.Failed())) {
531     return result.StealNSResult();
532   }
533 
534   data.forget(aData);
535   return NS_OK;
536 }
537 
MozGetDataAt(JSContext * aCx,const nsAString & aFormat,uint32_t aIndex,JS::MutableHandle<JS::Value> aRetval,nsIPrincipal & aSubjectPrincipal,mozilla::ErrorResult & aRv)538 void DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
539                                 uint32_t aIndex,
540                                 JS::MutableHandle<JS::Value> aRetval,
541                                 nsIPrincipal& aSubjectPrincipal,
542                                 mozilla::ErrorResult& aRv) {
543   nsCOMPtr<nsIVariant> data;
544   aRv = GetDataAtInternal(aFormat, aIndex, &aSubjectPrincipal,
545                           getter_AddRefs(data));
546   if (aRv.Failed()) {
547     return;
548   }
549 
550   if (!data) {
551     aRetval.setNull();
552     return;
553   }
554 
555   JS::Rooted<JS::Value> result(aCx);
556   if (!VariantToJsval(aCx, data, aRetval)) {
557     aRv = NS_ERROR_FAILURE;
558     return;
559   }
560 }
561 
562 /* static */
PrincipalMaySetData(const nsAString & aType,nsIVariant * aData,nsIPrincipal * aPrincipal)563 bool DataTransfer::PrincipalMaySetData(const nsAString& aType,
564                                        nsIVariant* aData,
565                                        nsIPrincipal* aPrincipal) {
566   if (!aPrincipal->IsSystemPrincipal()) {
567     DataTransferItem::eKind kind = DataTransferItem::KindFromData(aData);
568     if (kind == DataTransferItem::KIND_OTHER) {
569       NS_WARNING("Disallowing adding non string/file types to DataTransfer");
570       return false;
571     }
572 
573     // Don't allow adding internal types of the form */x-moz-*, but
574     // special-case the url types as they are simple variations of urls.
575     // In addition, allow x-moz-place flavors to be added by WebExtensions.
576     if (FindInReadable(kInternal_Mimetype_Prefix, aType) &&
577         !StringBeginsWith(aType, u"text/x-moz-url"_ns)) {
578       auto principal = BasePrincipal::Cast(aPrincipal);
579       if (!principal->AddonPolicy() ||
580           !StringBeginsWith(aType, u"text/x-moz-place"_ns)) {
581         NS_WARNING("Disallowing adding this type to DataTransfer");
582         return false;
583       }
584     }
585   }
586 
587   return true;
588 }
589 
TypesListMayHaveChanged()590 void DataTransfer::TypesListMayHaveChanged() {
591   DataTransfer_Binding::ClearCachedTypesValue(this);
592 }
593 
MozCloneForEvent(const nsAString & aEvent,ErrorResult & aRv)594 already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent(
595     const nsAString& aEvent, ErrorResult& aRv) {
596   RefPtr<nsAtom> atomEvt = NS_Atomize(aEvent);
597   if (!atomEvt) {
598     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
599     return nullptr;
600   }
601   EventMessage eventMessage = nsContentUtils::GetEventMessage(atomEvt);
602 
603   RefPtr<DataTransfer> dt;
604   nsresult rv = Clone(mParent, eventMessage, false, false, getter_AddRefs(dt));
605   if (NS_FAILED(rv)) {
606     aRv.Throw(rv);
607     return nullptr;
608   }
609   return dt.forget();
610 }
611 
612 /* static */
GetExternalClipboardFormats(const int32_t & aWhichClipboard,const bool & aPlainTextOnly,nsTArray<nsCString> * aResult)613 void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard,
614                                                const bool& aPlainTextOnly,
615                                                nsTArray<nsCString>* aResult) {
616   MOZ_ASSERT(aResult);
617 
618   // NOTE: When you change this method, you may need to change
619   //       GetExternalTransferableFormats() too since those methods should
620   //       work similarly.
621 
622   nsCOMPtr<nsIClipboard> clipboard =
623       do_GetService("@mozilla.org/widget/clipboard;1");
624   if (!clipboard || aWhichClipboard < 0) {
625     return;
626   }
627 
628   if (aPlainTextOnly) {
629     bool hasType;
630     AutoTArray<nsCString, 1> unicodeMime = {nsDependentCString(kUnicodeMime)};
631     nsresult rv = clipboard->HasDataMatchingFlavors(unicodeMime,
632                                                     aWhichClipboard, &hasType);
633     NS_SUCCEEDED(rv);
634     if (hasType) {
635       aResult->AppendElement(kUnicodeMime);
636     }
637     return;
638   }
639 
640   // If not plain text only, then instead check all the other types
641   static const char* formats[] = {kCustomTypesMime, kFileMime,    kHTMLMime,
642                                   kRTFMime,         kURLMime,     kURLDataMime,
643                                   kUnicodeMime,     kPNGImageMime};
644 
645   for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
646     bool hasType;
647     AutoTArray<nsCString, 1> format = {nsDependentCString(formats[f])};
648     nsresult rv =
649         clipboard->HasDataMatchingFlavors(format, aWhichClipboard, &hasType);
650     NS_SUCCEEDED(rv);
651     if (hasType) {
652       aResult->AppendElement(formats[f]);
653     }
654   }
655 }
656 
657 /* static */
GetExternalTransferableFormats(nsITransferable * aTransferable,bool aPlainTextOnly,nsTArray<nsCString> * aResult)658 void DataTransfer::GetExternalTransferableFormats(
659     nsITransferable* aTransferable, bool aPlainTextOnly,
660     nsTArray<nsCString>* aResult) {
661   MOZ_ASSERT(aTransferable);
662   MOZ_ASSERT(aResult);
663 
664   aResult->Clear();
665 
666   // NOTE: When you change this method, you may need to change
667   //       GetExternalClipboardFormats() too since those methods should
668   //       work similarly.
669 
670   AutoTArray<nsCString, 10> flavors;
671   aTransferable->FlavorsTransferableCanExport(flavors);
672 
673   if (aPlainTextOnly) {
674     auto index = flavors.IndexOf(nsLiteralCString(kUnicodeMime));
675     if (index != flavors.NoIndex) {
676       aResult->AppendElement(nsLiteralCString(kUnicodeMime));
677     }
678     return;
679   }
680 
681   // If not plain text only, then instead check all the other types
682   static const char* formats[] = {kCustomTypesMime, kFileMime,    kHTMLMime,
683                                   kRTFMime,         kURLMime,     kURLDataMime,
684                                   kUnicodeMime,     kPNGImageMime};
685 
686   for (const char* format : formats) {
687     auto index = flavors.IndexOf(nsCString(format));
688     if (index != flavors.NoIndex) {
689       aResult->AppendElement(nsCString(format));
690     }
691   }
692 }
693 
SetDataAtInternal(const nsAString & aFormat,nsIVariant * aData,uint32_t aIndex,nsIPrincipal * aSubjectPrincipal)694 nsresult DataTransfer::SetDataAtInternal(const nsAString& aFormat,
695                                          nsIVariant* aData, uint32_t aIndex,
696                                          nsIPrincipal* aSubjectPrincipal) {
697   if (aFormat.IsEmpty()) {
698     return NS_OK;
699   }
700 
701   if (IsReadOnly()) {
702     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
703   }
704 
705   // Specifying an index less than the current length will replace an existing
706   // item. Specifying an index equal to the current length will add a new item.
707   if (aIndex > MozItemCount()) {
708     return NS_ERROR_DOM_INDEX_SIZE_ERR;
709   }
710 
711   // Only the first item is valid for clipboard events
712   if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
713                      mEventMessage == ePaste)) {
714     return NS_ERROR_DOM_INDEX_SIZE_ERR;
715   }
716 
717   // Don't allow the custom type to be assigned.
718   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
719     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
720   }
721 
722   if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
723     return NS_ERROR_DOM_SECURITY_ERR;
724   }
725 
726   return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
727 }
728 
MozSetDataAt(JSContext * aCx,const nsAString & aFormat,JS::Handle<JS::Value> aData,uint32_t aIndex,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)729 void DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
730                                 JS::Handle<JS::Value> aData, uint32_t aIndex,
731                                 nsIPrincipal& aSubjectPrincipal,
732                                 ErrorResult& aRv) {
733   nsCOMPtr<nsIVariant> data;
734   aRv = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData,
735                                                     getter_AddRefs(data));
736   if (!aRv.Failed()) {
737     aRv = SetDataAtInternal(aFormat, data, aIndex, &aSubjectPrincipal);
738   }
739 }
740 
MozClearDataAt(const nsAString & aFormat,uint32_t aIndex,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)741 void DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
742                                   nsIPrincipal& aSubjectPrincipal,
743                                   ErrorResult& aRv) {
744   if (IsReadOnly()) {
745     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
746     return;
747   }
748 
749   if (aIndex >= MozItemCount()) {
750     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
751     return;
752   }
753 
754   // Only the first item is valid for clipboard events
755   if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
756                      mEventMessage == ePaste)) {
757     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
758     return;
759   }
760 
761   MozClearDataAtHelper(aFormat, aIndex, aSubjectPrincipal, aRv);
762 
763   // If we just cleared the 0-th index, and there are still more than 1 indexes
764   // remaining, MozClearDataAt should cause the 1st index to become the 0th
765   // index. This should _only_ happen when the MozClearDataAt function is
766   // explicitly called by script, as this behavior is inconsistent with spec.
767   // (however, so is the MozClearDataAt API)
768 
769   if (aIndex == 0 && mItems->MozItemCount() > 1 &&
770       mItems->MozItemsAt(0)->Length() == 0) {
771     mItems->PopIndexZero();
772   }
773 }
774 
MozClearDataAtHelper(const nsAString & aFormat,uint32_t aIndex,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)775 void DataTransfer::MozClearDataAtHelper(const nsAString& aFormat,
776                                         uint32_t aIndex,
777                                         nsIPrincipal& aSubjectPrincipal,
778                                         ErrorResult& aRv) {
779   MOZ_ASSERT(!IsReadOnly());
780   MOZ_ASSERT(aIndex < MozItemCount());
781   MOZ_ASSERT(aIndex == 0 || (mEventMessage != eCut && mEventMessage != eCopy &&
782                              mEventMessage != ePaste));
783 
784   nsAutoString format;
785   GetRealFormat(aFormat, format);
786 
787   mItems->MozRemoveByTypeAt(format, aIndex, aSubjectPrincipal, aRv);
788 }
789 
SetDragImage(Element & aImage,int32_t aX,int32_t aY)790 void DataTransfer::SetDragImage(Element& aImage, int32_t aX, int32_t aY) {
791   if (!IsReadOnly()) {
792     mDragImage = &aImage;
793     mDragImageX = aX;
794     mDragImageY = aY;
795   }
796 }
797 
UpdateDragImage(Element & aImage,int32_t aX,int32_t aY)798 void DataTransfer::UpdateDragImage(Element& aImage, int32_t aX, int32_t aY) {
799   if (mEventMessage < eDragDropEventFirst ||
800       mEventMessage > eDragDropEventLast) {
801     return;
802   }
803 
804   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
805   if (dragSession) {
806     dragSession->UpdateDragImage(&aImage, aX, aY);
807   }
808 }
809 
GetFilesAndDirectories(nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)810 already_AddRefed<Promise> DataTransfer::GetFilesAndDirectories(
811     nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
812   nsCOMPtr<nsINode> parentNode = do_QueryInterface(mParent);
813   if (!parentNode) {
814     aRv.Throw(NS_ERROR_FAILURE);
815     return nullptr;
816   }
817 
818   nsCOMPtr<nsIGlobalObject> global = parentNode->OwnerDoc()->GetScopeObject();
819   MOZ_ASSERT(global);
820   if (!global) {
821     aRv.Throw(NS_ERROR_FAILURE);
822     return nullptr;
823   }
824 
825   RefPtr<Promise> p = Promise::Create(global, aRv);
826   if (NS_WARN_IF(aRv.Failed())) {
827     return nullptr;
828   }
829 
830   RefPtr<FileList> files = mItems->Files(&aSubjectPrincipal);
831   if (NS_WARN_IF(!files)) {
832     return nullptr;
833   }
834 
835   Sequence<RefPtr<File>> filesSeq;
836   files->ToSequence(filesSeq, aRv);
837   if (NS_WARN_IF(aRv.Failed())) {
838     return nullptr;
839   }
840 
841   p->MaybeResolve(filesSeq);
842 
843   return p.forget();
844 }
845 
GetFiles(bool aRecursiveFlag,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)846 already_AddRefed<Promise> DataTransfer::GetFiles(
847     bool aRecursiveFlag, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
848   // Currently we don't support directories.
849   return GetFilesAndDirectories(aSubjectPrincipal, aRv);
850 }
851 
AddElement(Element & aElement,ErrorResult & aRv)852 void DataTransfer::AddElement(Element& aElement, ErrorResult& aRv) {
853   if (IsReadOnly()) {
854     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
855     return;
856   }
857 
858   mDragTarget = &aElement;
859 }
860 
Clone(nsISupports * aParent,EventMessage aEventMessage,bool aUserCancelled,bool aIsCrossDomainSubFrameDrop,DataTransfer ** aNewDataTransfer)861 nsresult DataTransfer::Clone(nsISupports* aParent, EventMessage aEventMessage,
862                              bool aUserCancelled,
863                              bool aIsCrossDomainSubFrameDrop,
864                              DataTransfer** aNewDataTransfer) {
865   RefPtr<DataTransfer> newDataTransfer = new DataTransfer(
866       aParent, aEventMessage, mEffectAllowed, mCursorState, mIsExternal,
867       aUserCancelled, aIsCrossDomainSubFrameDrop, mClipboardType, mItems,
868       mDragImage, mDragImageX, mDragImageY);
869 
870   newDataTransfer.forget(aNewDataTransfer);
871   return NS_OK;
872 }
873 
GetTransferables(nsINode * aDragTarget)874 already_AddRefed<nsIArray> DataTransfer::GetTransferables(
875     nsINode* aDragTarget) {
876   MOZ_ASSERT(aDragTarget);
877 
878   Document* doc = aDragTarget->GetComposedDoc();
879   if (!doc) {
880     return nullptr;
881   }
882 
883   return GetTransferables(doc->GetLoadContext());
884 }
885 
GetTransferables(nsILoadContext * aLoadContext)886 already_AddRefed<nsIArray> DataTransfer::GetTransferables(
887     nsILoadContext* aLoadContext) {
888   nsCOMPtr<nsIMutableArray> transArray = nsArray::Create();
889   if (!transArray) {
890     return nullptr;
891   }
892 
893   uint32_t count = MozItemCount();
894   for (uint32_t i = 0; i < count; i++) {
895     nsCOMPtr<nsITransferable> transferable = GetTransferable(i, aLoadContext);
896     if (transferable) {
897       transArray->AppendElement(transferable);
898     }
899   }
900 
901   return transArray.forget();
902 }
903 
GetTransferable(uint32_t aIndex,nsILoadContext * aLoadContext)904 already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
905     uint32_t aIndex, nsILoadContext* aLoadContext) {
906   if (aIndex >= MozItemCount()) {
907     return nullptr;
908   }
909 
910   const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex);
911   uint32_t count = item.Length();
912   if (!count) {
913     return nullptr;
914   }
915 
916   nsCOMPtr<nsITransferable> transferable =
917       do_CreateInstance("@mozilla.org/widget/transferable;1");
918   if (!transferable) {
919     return nullptr;
920   }
921   transferable->Init(aLoadContext);
922 
923   nsCOMPtr<nsIStorageStream> storageStream;
924   nsCOMPtr<nsIObjectOutputStream> stream;
925 
926   bool added = false;
927   bool handlingCustomFormats = true;
928 
929   // When writing the custom data, we need to ensure that there is sufficient
930   // space for a (uint32_t) data ending type, and the null byte character at
931   // the end of the nsCString. We claim that space upfront and store it in
932   // baseLength. This value will be set to zero if a write error occurs
933   // indicating that the data and length are no longer valid.
934   const uint32_t baseLength = sizeof(uint32_t) + 1;
935   uint32_t totalCustomLength = baseLength;
936 
937   const char* knownFormats[] = {kTextMime,
938                                 kHTMLMime,
939                                 kNativeHTMLMime,
940                                 kRTFMime,
941                                 kURLMime,
942                                 kURLDataMime,
943                                 kURLDescriptionMime,
944                                 kURLPrivateMime,
945                                 kPNGImageMime,
946                                 kJPEGImageMime,
947                                 kGIFImageMime,
948                                 kNativeImageMime,
949                                 kFileMime,
950                                 kFilePromiseMime,
951                                 kFilePromiseURLMime,
952                                 kFilePromiseDestFilename,
953                                 kFilePromiseDirectoryMime,
954                                 kMozTextInternal,
955                                 kHTMLContext,
956                                 kHTMLInfo,
957                                 kImageRequestMime};
958 
959   /*
960    * Two passes are made here to iterate over all of the types. First, look for
961    * any types that are not in the list of known types. For this pass,
962    * handlingCustomFormats will be true. Data that corresponds to unknown types
963    * will be pulled out and inserted into a single type (kCustomTypesMime) by
964    * writing the data into a stream.
965    *
966    * The second pass will iterate over the formats looking for known types.
967    * These are added as is. The unknown types are all then inserted as a single
968    * type (kCustomTypesMime) in the same position of the first custom type. This
969    * model is used to maintain the format order as best as possible.
970    *
971    * The format of the kCustomTypesMime type is one or more of the following
972    * stored sequentially:
973    *   <32-bit> type (only none or string is supported)
974    *   <32-bit> length of format
975    *   <wide string> format
976    *   <32-bit> length of data
977    *   <wide string> data
978    * A type of eCustomClipboardTypeId_None ends the list, without any following
979    * data.
980    */
981   do {
982     for (uint32_t f = 0; f < count; f++) {
983       RefPtr<DataTransferItem> formatitem = item[f];
984       nsCOMPtr<nsIVariant> variant = formatitem->DataNoSecurityCheck();
985       if (!variant) {  // skip empty items
986         continue;
987       }
988 
989       nsAutoString type;
990       formatitem->GetInternalType(type);
991 
992       // If the data is of one of the well-known formats, use it directly.
993       bool isCustomFormat = true;
994       for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) {
995         if (type.EqualsASCII(knownFormats[f])) {
996           isCustomFormat = false;
997           break;
998         }
999       }
1000 
1001       uint32_t lengthInBytes;
1002       nsCOMPtr<nsISupports> convertedData;
1003 
1004       if (handlingCustomFormats) {
1005         if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
1006                                 &lengthInBytes)) {
1007           continue;
1008         }
1009 
1010         // When handling custom types, add the data to the stream if this is a
1011         // custom type. If totalCustomLength is 0, then a write error occurred
1012         // on a previous item, so ignore any others.
1013         if (isCustomFormat && totalCustomLength > 0) {
1014           // If it isn't a string, just ignore it. The dataTransfer is cached in
1015           // the drag sesion during drag-and-drop, so non-strings will be
1016           // available when dragging locally.
1017           nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
1018           if (str) {
1019             nsAutoString data;
1020             str->GetData(data);
1021 
1022             if (!stream) {
1023               // Create a storage stream to write to.
1024               NS_NewStorageStream(1024, UINT32_MAX,
1025                                   getter_AddRefs(storageStream));
1026 
1027               nsCOMPtr<nsIOutputStream> outputStream;
1028               storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
1029 
1030               stream = NS_NewObjectOutputStream(outputStream);
1031             }
1032 
1033             CheckedInt<uint32_t> formatLength =
1034                 CheckedInt<uint32_t>(type.Length()) *
1035                 sizeof(nsString::char_type);
1036 
1037             // The total size of the stream is the format length, the data
1038             // length, two integers to hold the lengths and one integer for
1039             // the string flag. Guard against large data by ignoring any that
1040             // don't fit.
1041             CheckedInt<uint32_t> newSize = formatLength + totalCustomLength +
1042                                            lengthInBytes +
1043                                            (sizeof(uint32_t) * 3);
1044             if (newSize.isValid()) {
1045               // If a write error occurs, set totalCustomLength to 0 so that
1046               // further processing gets ignored.
1047               nsresult rv = stream->Write32(eCustomClipboardTypeId_String);
1048               if (NS_WARN_IF(NS_FAILED(rv))) {
1049                 totalCustomLength = 0;
1050                 continue;
1051               }
1052               rv = stream->Write32(formatLength.value());
1053               if (NS_WARN_IF(NS_FAILED(rv))) {
1054                 totalCustomLength = 0;
1055                 continue;
1056               }
1057               MOZ_ASSERT(formatLength.isValid() &&
1058                              formatLength.value() ==
1059                                  type.Length() * sizeof(nsString::char_type),
1060                          "Why is formatLength off?");
1061               rv = stream->WriteBytes(
1062                   AsBytes(Span(type.BeginReading(), type.Length())));
1063               if (NS_WARN_IF(NS_FAILED(rv))) {
1064                 totalCustomLength = 0;
1065                 continue;
1066               }
1067               rv = stream->Write32(lengthInBytes);
1068               if (NS_WARN_IF(NS_FAILED(rv))) {
1069                 totalCustomLength = 0;
1070                 continue;
1071               }
1072               // XXXbz it's not obvious to me that lengthInBytes is the actual
1073               // length of "data" if the variant contained an nsISupportsString
1074               // as VTYPE_INTERFACE, say.  We used lengthInBytes above for
1075               // sizing, so just keep doing that.
1076               rv = stream->WriteBytes(
1077                   Span(reinterpret_cast<const uint8_t*>(data.BeginReading()),
1078                        lengthInBytes));
1079               if (NS_WARN_IF(NS_FAILED(rv))) {
1080                 totalCustomLength = 0;
1081                 continue;
1082               }
1083 
1084               totalCustomLength = newSize.value();
1085             }
1086           }
1087         }
1088       } else if (isCustomFormat && stream) {
1089         // This is the second pass of the loop (handlingCustomFormats is false).
1090         // When encountering the first custom format, append all of the stream
1091         // at this position. If totalCustomLength is 0 indicating a write error
1092         // occurred, or no data has been added to it, don't output anything,
1093         if (totalCustomLength > baseLength) {
1094           // Write out an end of data terminator.
1095           nsresult rv = stream->Write32(eCustomClipboardTypeId_None);
1096           if (NS_SUCCEEDED(rv)) {
1097             nsCOMPtr<nsIInputStream> inputStream;
1098             storageStream->NewInputStream(0, getter_AddRefs(inputStream));
1099 
1100             RefPtr<nsStringBuffer> stringBuffer =
1101                 nsStringBuffer::Alloc(totalCustomLength);
1102 
1103             // Subtract off the null terminator when reading.
1104             totalCustomLength--;
1105 
1106             // Read the data from the stream and add a null-terminator as
1107             // ToString needs it.
1108             uint32_t amountRead;
1109             rv = inputStream->Read(static_cast<char*>(stringBuffer->Data()),
1110                                    totalCustomLength, &amountRead);
1111             if (NS_SUCCEEDED(rv)) {
1112               static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
1113 
1114               nsCString str;
1115               stringBuffer->ToString(totalCustomLength, str);
1116               nsCOMPtr<nsISupportsCString> strSupports(
1117                   do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
1118               strSupports->SetData(str);
1119 
1120               nsresult rv =
1121                   transferable->SetTransferData(kCustomTypesMime, strSupports);
1122               if (NS_FAILED(rv)) {
1123                 return nullptr;
1124               }
1125 
1126               added = true;
1127             }
1128           }
1129         }
1130 
1131         // Clear the stream so it doesn't get used again.
1132         stream = nullptr;
1133       } else {
1134         // This is the second pass of the loop and a known type is encountered.
1135         // Add it as is.
1136         if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
1137                                 &lengthInBytes)) {
1138           continue;
1139         }
1140 
1141         // The underlying drag code uses text/unicode, so use that instead of
1142         // text/plain
1143         const char* format;
1144         NS_ConvertUTF16toUTF8 utf8format(type);
1145         if (utf8format.EqualsLiteral(kTextMime)) {
1146           format = kUnicodeMime;
1147         } else {
1148           format = utf8format.get();
1149         }
1150 
1151         // If a converter is set for a format, set the converter for the
1152         // transferable and don't add the item
1153         nsCOMPtr<nsIFormatConverter> converter =
1154             do_QueryInterface(convertedData);
1155         if (converter) {
1156           transferable->AddDataFlavor(format);
1157           transferable->SetConverter(converter);
1158           continue;
1159         }
1160 
1161         nsresult rv = transferable->SetTransferData(format, convertedData);
1162         if (NS_FAILED(rv)) {
1163           return nullptr;
1164         }
1165 
1166         added = true;
1167       }
1168     }
1169 
1170     handlingCustomFormats = !handlingCustomFormats;
1171   } while (!handlingCustomFormats);
1172 
1173   // only return the transferable if data was successfully added to it
1174   if (added) {
1175     return transferable.forget();
1176   }
1177 
1178   return nullptr;
1179 }
1180 
ConvertFromVariant(nsIVariant * aVariant,nsISupports ** aSupports,uint32_t * aLength) const1181 bool DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
1182                                       nsISupports** aSupports,
1183                                       uint32_t* aLength) const {
1184   *aSupports = nullptr;
1185   *aLength = 0;
1186 
1187   uint16_t type = aVariant->GetDataType();
1188   if (type == nsIDataType::VTYPE_INTERFACE ||
1189       type == nsIDataType::VTYPE_INTERFACE_IS) {
1190     nsCOMPtr<nsISupports> data;
1191     if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) {
1192       return false;
1193     }
1194 
1195     nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
1196     if (fdp) {
1197       // For flavour data providers, use 0 as the length.
1198       fdp.forget(aSupports);
1199       *aLength = 0;
1200     } else {
1201       data.forget(aSupports);
1202       *aLength = sizeof(nsISupports*);
1203     }
1204 
1205     return true;
1206   }
1207 
1208   nsAutoString str;
1209   nsresult rv = aVariant->GetAsAString(str);
1210   if (NS_FAILED(rv)) {
1211     return false;
1212   }
1213 
1214   nsCOMPtr<nsISupportsString> strSupports(
1215       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
1216   if (!strSupports) {
1217     return false;
1218   }
1219 
1220   strSupports->SetData(str);
1221 
1222   strSupports.forget(aSupports);
1223 
1224   // each character is two bytes
1225   *aLength = str.Length() * 2;
1226 
1227   return true;
1228 }
1229 
Disconnect()1230 void DataTransfer::Disconnect() {
1231   SetMode(Mode::Protected);
1232   if (StaticPrefs::dom_events_dataTransfer_protected_enabled()) {
1233     ClearAll();
1234   }
1235 }
1236 
ClearAll()1237 void DataTransfer::ClearAll() { mItems->ClearAllItems(); }
1238 
MozItemCount() const1239 uint32_t DataTransfer::MozItemCount() const { return mItems->MozItemCount(); }
1240 
SetDataWithPrincipal(const nsAString & aFormat,nsIVariant * aData,uint32_t aIndex,nsIPrincipal * aPrincipal,bool aHidden)1241 nsresult DataTransfer::SetDataWithPrincipal(const nsAString& aFormat,
1242                                             nsIVariant* aData, uint32_t aIndex,
1243                                             nsIPrincipal* aPrincipal,
1244                                             bool aHidden) {
1245   nsAutoString format;
1246   GetRealFormat(aFormat, format);
1247 
1248   ErrorResult rv;
1249   RefPtr<DataTransferItem> item =
1250       mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
1251                                    /* aInsertOnly = */ false, aHidden, rv);
1252   return rv.StealNSResult();
1253 }
1254 
SetDataWithPrincipalFromOtherProcess(const nsAString & aFormat,nsIVariant * aData,uint32_t aIndex,nsIPrincipal * aPrincipal,bool aHidden)1255 void DataTransfer::SetDataWithPrincipalFromOtherProcess(
1256     const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex,
1257     nsIPrincipal* aPrincipal, bool aHidden) {
1258   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
1259     FillInExternalCustomTypes(aData, aIndex, aPrincipal);
1260   } else {
1261     nsAutoString format;
1262     GetRealFormat(aFormat, format);
1263 
1264     ErrorResult rv;
1265     RefPtr<DataTransferItem> item =
1266         mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
1267                                      /* aInsertOnly = */ false, aHidden, rv);
1268     if (NS_WARN_IF(rv.Failed())) {
1269       rv.SuppressException();
1270     }
1271   }
1272 }
1273 
GetRealFormat(const nsAString & aInFormat,nsAString & aOutFormat) const1274 void DataTransfer::GetRealFormat(const nsAString& aInFormat,
1275                                  nsAString& aOutFormat) const {
1276   // treat text/unicode as equivalent to text/plain
1277   nsAutoString lowercaseFormat;
1278   nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
1279   if (lowercaseFormat.EqualsLiteral("text") ||
1280       lowercaseFormat.EqualsLiteral("text/unicode")) {
1281     aOutFormat.AssignLiteral("text/plain");
1282     return;
1283   }
1284 
1285   if (lowercaseFormat.EqualsLiteral("url")) {
1286     aOutFormat.AssignLiteral("text/uri-list");
1287     return;
1288   }
1289 
1290   aOutFormat.Assign(lowercaseFormat);
1291 }
1292 
CacheExternalData(const char * aFormat,uint32_t aIndex,nsIPrincipal * aPrincipal,bool aHidden)1293 nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
1294                                          nsIPrincipal* aPrincipal,
1295                                          bool aHidden) {
1296   ErrorResult rv;
1297   RefPtr<DataTransferItem> item;
1298 
1299   if (strcmp(aFormat, kUnicodeMime) == 0) {
1300     item = mItems->SetDataWithPrincipal(u"text/plain"_ns, nullptr, aIndex,
1301                                         aPrincipal, false, aHidden, rv);
1302     if (NS_WARN_IF(rv.Failed())) {
1303       return rv.StealNSResult();
1304     }
1305     return NS_OK;
1306   }
1307 
1308   if (strcmp(aFormat, kURLDataMime) == 0) {
1309     item = mItems->SetDataWithPrincipal(u"text/uri-list"_ns, nullptr, aIndex,
1310                                         aPrincipal, false, aHidden, rv);
1311     if (NS_WARN_IF(rv.Failed())) {
1312       return rv.StealNSResult();
1313     }
1314     return NS_OK;
1315   }
1316 
1317   nsAutoString format;
1318   GetRealFormat(NS_ConvertUTF8toUTF16(aFormat), format);
1319   item = mItems->SetDataWithPrincipal(format, nullptr, aIndex, aPrincipal,
1320                                       false, aHidden, rv);
1321   if (NS_WARN_IF(rv.Failed())) {
1322     return rv.StealNSResult();
1323   }
1324   return NS_OK;
1325 }
1326 
CacheExternalDragFormats()1327 void DataTransfer::CacheExternalDragFormats() {
1328   // Called during the constructor to cache the formats available from an
1329   // external drag. The data associated with each format will be set to null.
1330   // This data will instead only be retrieved in FillInExternalDragData when
1331   // asked for, as it may be time consuming for the source application to
1332   // generate it.
1333 
1334   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1335   if (!dragSession) {
1336     return;
1337   }
1338 
1339   // make sure that the system principal is used for external drags
1340   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1341   nsCOMPtr<nsIPrincipal> sysPrincipal;
1342   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
1343 
1344   // there isn't a way to get a list of the formats that might be available on
1345   // all platforms, so just check for the types that can actually be imported
1346   // XXXndeakin there are some other formats but those are platform specific.
1347   // NOTE: kFileMime must have index 0
1348   const char* formats[] = {kFileMime,    kHTMLMime,    kURLMime,
1349                            kURLDataMime, kUnicodeMime, kPNGImageMime};
1350 
1351   uint32_t count;
1352   dragSession->GetNumDropItems(&count);
1353   for (uint32_t c = 0; c < count; c++) {
1354     bool hasFileData = false;
1355     dragSession->IsDataFlavorSupported(kFileMime, &hasFileData);
1356 
1357     // First, check for the special format that holds custom types.
1358     bool supported;
1359     dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported);
1360     if (supported) {
1361       FillInExternalCustomTypes(c, sysPrincipal);
1362     }
1363 
1364     for (uint32_t f = 0; f < ArrayLength(formats); f++) {
1365       // IsDataFlavorSupported doesn't take an index as an argument and just
1366       // checks if any of the items support a particular flavor, even though
1367       // the GetData method does take an index. Here, we just assume that
1368       // every item being dragged has the same set of flavors.
1369       bool supported;
1370       dragSession->IsDataFlavorSupported(formats[f], &supported);
1371       // if the format is supported, add an item to the array with null as
1372       // the data. When retrieved, GetRealData will read the data.
1373       if (supported) {
1374         CacheExternalData(formats[f], c, sysPrincipal,
1375                           /* hidden = */ f && hasFileData);
1376       }
1377     }
1378   }
1379 }
1380 
CacheExternalClipboardFormats(bool aPlainTextOnly)1381 void DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly) {
1382   // Called during the constructor for paste events to cache the formats
1383   // available on the clipboard. As with CacheExternalDragFormats, the
1384   // data will only be retrieved when needed.
1385   NS_ASSERTION(mEventMessage == ePaste,
1386                "caching clipboard data for invalid event");
1387 
1388   nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
1389 
1390   nsTArray<nsCString> typesArray;
1391 
1392   if (XRE_IsContentProcess()) {
1393     ContentChild::GetSingleton()->SendGetExternalClipboardFormats(
1394         mClipboardType, aPlainTextOnly, &typesArray);
1395   } else {
1396     GetExternalClipboardFormats(mClipboardType, aPlainTextOnly, &typesArray);
1397   }
1398 
1399   if (aPlainTextOnly) {
1400     // The only thing that will be in types is kUnicodeMime
1401     MOZ_ASSERT(typesArray.IsEmpty() || typesArray.Length() == 1);
1402     if (typesArray.Length() == 1) {
1403       CacheExternalData(kUnicodeMime, 0, sysPrincipal, false);
1404     }
1405     return;
1406   }
1407 
1408   CacheExternalData(typesArray, sysPrincipal);
1409 }
1410 
CacheTransferableFormats()1411 void DataTransfer::CacheTransferableFormats() {
1412   nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
1413 
1414   AutoTArray<nsCString, 10> typesArray;
1415   GetExternalTransferableFormats(mTransferable, false, &typesArray);
1416 
1417   CacheExternalData(typesArray, sysPrincipal);
1418 }
1419 
CacheExternalData(const nsTArray<nsCString> & aTypes,nsIPrincipal * aPrincipal)1420 void DataTransfer::CacheExternalData(const nsTArray<nsCString>& aTypes,
1421                                      nsIPrincipal* aPrincipal) {
1422   bool hasFileData = false;
1423   for (const nsCString& type : aTypes) {
1424     if (type.EqualsLiteral(kCustomTypesMime)) {
1425       FillInExternalCustomTypes(0, aPrincipal);
1426     } else if (type.EqualsLiteral(kFileMime) && XRE_IsContentProcess()) {
1427       // We will be ignoring any application/x-moz-file files found in the paste
1428       // datatransfer within e10s, as they will fail top be sent over IPC.
1429       // Because of that, we will unset hasFileData, whether or not it would
1430       // have been set. (bug 1308007)
1431       hasFileData = false;
1432       continue;
1433     } else {
1434       // We expect that if kFileMime is supported, then it will be the either at
1435       // index 0 or at index 1 in the aTypes returned by
1436       // GetExternalClipboardFormats
1437       if (type.EqualsLiteral(kFileMime) && !XRE_IsContentProcess()) {
1438         hasFileData = true;
1439       }
1440       // If we aren't the file data, and we have file data, we want to be hidden
1441       CacheExternalData(
1442           type.get(), 0, aPrincipal,
1443           /* hidden = */ !type.EqualsLiteral(kFileMime) && hasFileData);
1444     }
1445   }
1446 }
1447 
FillAllExternalData()1448 void DataTransfer::FillAllExternalData() {
1449   if (mIsExternal) {
1450     for (uint32_t i = 0; i < MozItemCount(); ++i) {
1451       const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(i);
1452       for (uint32_t j = 0; j < items.Length(); ++j) {
1453         MOZ_ASSERT(items[j]->Index() == i);
1454 
1455         items[j]->FillInExternalData();
1456       }
1457     }
1458   }
1459 }
1460 
FillInExternalCustomTypes(uint32_t aIndex,nsIPrincipal * aPrincipal)1461 void DataTransfer::FillInExternalCustomTypes(uint32_t aIndex,
1462                                              nsIPrincipal* aPrincipal) {
1463   RefPtr<DataTransferItem> item = new DataTransferItem(
1464       this, NS_LITERAL_STRING_FROM_CSTRING(kCustomTypesMime),
1465       DataTransferItem::KIND_STRING);
1466   item->SetIndex(aIndex);
1467 
1468   nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck();
1469   if (!variant) {
1470     return;
1471   }
1472 
1473   FillInExternalCustomTypes(variant, aIndex, aPrincipal);
1474 }
1475 
FillInExternalCustomTypes(nsIVariant * aData,uint32_t aIndex,nsIPrincipal * aPrincipal)1476 void DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex,
1477                                              nsIPrincipal* aPrincipal) {
1478   char* chrs;
1479   uint32_t len = 0;
1480   nsresult rv = aData->GetAsStringWithSize(&len, &chrs);
1481   if (NS_FAILED(rv)) {
1482     return;
1483   }
1484 
1485   CheckedInt<int32_t> checkedLen(len);
1486   if (!checkedLen.isValid()) {
1487     return;
1488   }
1489 
1490   nsCOMPtr<nsIInputStream> stringStream;
1491   NS_NewByteInputStream(getter_AddRefs(stringStream),
1492                         Span(chrs, checkedLen.value()), NS_ASSIGNMENT_ADOPT);
1493 
1494   nsCOMPtr<nsIObjectInputStream> stream = NS_NewObjectInputStream(stringStream);
1495 
1496   uint32_t type;
1497   do {
1498     rv = stream->Read32(&type);
1499     NS_ENSURE_SUCCESS_VOID(rv);
1500     if (type == eCustomClipboardTypeId_String) {
1501       uint32_t formatLength;
1502       rv = stream->Read32(&formatLength);
1503       NS_ENSURE_SUCCESS_VOID(rv);
1504       char* formatBytes;
1505       rv = stream->ReadBytes(formatLength, &formatBytes);
1506       NS_ENSURE_SUCCESS_VOID(rv);
1507       nsAutoString format;
1508       format.Adopt(reinterpret_cast<char16_t*>(formatBytes),
1509                    formatLength / sizeof(char16_t));
1510 
1511       uint32_t dataLength;
1512       rv = stream->Read32(&dataLength);
1513       NS_ENSURE_SUCCESS_VOID(rv);
1514       char* dataBytes;
1515       rv = stream->ReadBytes(dataLength, &dataBytes);
1516       NS_ENSURE_SUCCESS_VOID(rv);
1517       nsAutoString data;
1518       data.Adopt(reinterpret_cast<char16_t*>(dataBytes),
1519                  dataLength / sizeof(char16_t));
1520 
1521       RefPtr<nsVariantCC> variant = new nsVariantCC();
1522       rv = variant->SetAsAString(data);
1523       NS_ENSURE_SUCCESS_VOID(rv);
1524 
1525       SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
1526     }
1527   } while (type != eCustomClipboardTypeId_None);
1528 }
1529 
SetMode(DataTransfer::Mode aMode)1530 void DataTransfer::SetMode(DataTransfer::Mode aMode) {
1531   if (!StaticPrefs::dom_events_dataTransfer_protected_enabled() &&
1532       aMode == Mode::Protected) {
1533     mMode = Mode::ReadOnly;
1534   } else {
1535     mMode = aMode;
1536   }
1537 }
1538 
1539 }  // namespace mozilla::dom
1540