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