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