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/. */
7 #include "DataTransferItem.h"
8 #include "DataTransferItemList.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/ContentEvents.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/StaticPrefs_dom.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/DataTransferItemBinding.h"
17 #include "mozilla/dom/Directory.h"
18 #include "mozilla/dom/Event.h"
19 #include "mozilla/dom/FileSystem.h"
20 #include "mozilla/dom/FileSystemDirectoryEntry.h"
21 #include "mozilla/dom/FileSystemFileEntry.h"
22 #include "imgIContainer.h"
23 #include "imgITools.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsIClipboard.h"
27 #include "nsIInputStream.h"
28 #include "nsISupportsPrimitives.h"
29 #include "nsIScriptObjectPrincipal.h"
30 #include "nsNetUtil.h"
31 #include "nsQueryObject.h"
32 #include "nsContentUtils.h"
33 #include "nsThreadUtils.h"
34 #include "nsVariant.h"
38 struct FileMimeNameData
{
39 const char* mMimeName
;
40 const char* mFileName
;
43 FileMimeNameData kFileMimeNameMap
[] = {
44 {kFileMime
, "GenericFileName"},
45 {kPNGImageMime
, "GenericImageNamePNG"},
48 } // anonymous namespace
50 namespace mozilla::dom
{
52 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem
, mData
, mPrincipal
,
53 mDataTransfer
, mCachedFile
)
54 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem
)
55 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem
)
57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem
)
58 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
59 NS_INTERFACE_MAP_ENTRY(nsISupports
)
62 JSObject
* DataTransferItem::WrapObject(JSContext
* aCx
,
63 JS::Handle
<JSObject
*> aGivenProto
) {
64 return DataTransferItem_Binding::Wrap(aCx
, this, aGivenProto
);
67 already_AddRefed
<DataTransferItem
> DataTransferItem::Clone(
68 DataTransfer
* aDataTransfer
) const {
69 MOZ_ASSERT(aDataTransfer
);
71 RefPtr
<DataTransferItem
> it
= new DataTransferItem(aDataTransfer
, mType
);
73 // Copy over all of the fields
77 it
->mPrincipal
= mPrincipal
;
78 it
->mChromeOnly
= mChromeOnly
;
83 void DataTransferItem::SetData(nsIVariant
* aData
) {
84 // Invalidate our file cache, we will regenerate it with the new data
85 mCachedFile
= nullptr;
88 // We are holding a temporary null which will later be filled.
89 // These are provided by the system, and have guaranteed properties about
90 // their kind based on their type.
91 MOZ_ASSERT(!mType
.IsEmpty());
92 // This type should not be provided by the OS.
93 MOZ_ASSERT(!mType
.EqualsASCII(kNativeImageMime
));
96 for (uint32_t i
= 0; i
< ArrayLength(kFileMimeNameMap
); ++i
) {
97 if (mType
.EqualsASCII(kFileMimeNameMap
[i
].mMimeName
)) {
108 mKind
= KindFromData(mData
);
111 /* static */ DataTransferItem::eKind
DataTransferItem::KindFromData(
113 nsCOMPtr
<nsISupports
> supports
;
114 nsresult rv
= aData
->GetAsISupports(getter_AddRefs(supports
));
115 if (NS_SUCCEEDED(rv
) && supports
) {
116 // Check if we have one of the supported file data formats
117 if (RefPtr
<Blob
>(do_QueryObject(supports
)) ||
118 nsCOMPtr
<BlobImpl
>(do_QueryInterface(supports
)) ||
119 nsCOMPtr
<nsIFile
>(do_QueryInterface(supports
))) {
123 if (StaticPrefs::dom_events_dataTransfer_imageAsFile_enabled()) {
124 // Firefox internally uses imgIContainer to represent images being
125 // copied/dragged. These need to be encoded to PNG files.
126 if (nsCOMPtr
<imgIContainer
>(do_QueryInterface(supports
))) {
133 // If we can't get the data type as a string, that means that the object
134 // should be considered to be of the "other" type. This is impossible
135 // through the APIs defined by the spec, but we provide extra Moz* APIs,
136 // which allow setting of non-string data. We determine whether we can
137 // consider it a string, by calling GetAsAString, and checking for success.
138 rv
= aData
->GetAsAString(string
);
139 if (NS_SUCCEEDED(rv
)) {
146 void DataTransferItem::FillInExternalData() {
151 NS_ConvertUTF16toUTF8
utf8format(mType
);
152 const char* format
= utf8format
.get();
153 if (strcmp(format
, "text/uri-list") == 0) {
154 format
= kURLDataMime
;
157 nsCOMPtr
<nsITransferable
> trans
= mDataTransfer
->GetTransferable();
159 trans
= do_CreateInstance("@mozilla.org/widget/transferable;1");
160 if (NS_WARN_IF(!trans
)) {
164 trans
->Init(nullptr);
165 trans
->AddDataFlavor(format
);
167 if (mDataTransfer
->GetEventMessage() == ePaste
) {
168 MOZ_ASSERT(mIndex
== 0, "index in clipboard must be 0");
170 nsCOMPtr
<nsIClipboard
> clipboard
=
171 do_GetService("@mozilla.org/widget/clipboard;1");
172 if (!clipboard
|| mDataTransfer
->ClipboardType() < 0) {
176 nsresult rv
= clipboard
->GetData(trans
, mDataTransfer
->ClipboardType());
177 if (NS_WARN_IF(NS_FAILED(rv
))) {
181 nsCOMPtr
<nsIDragSession
> dragSession
= nsContentUtils::GetDragSession();
186 nsresult rv
= dragSession
->GetData(trans
, mIndex
);
187 if (NS_WARN_IF(NS_FAILED(rv
))) {
193 nsCOMPtr
<nsISupports
> data
;
194 nsresult rv
= trans
->GetTransferData(format
, getter_AddRefs(data
));
195 if (NS_WARN_IF(NS_FAILED(rv
) || !data
)) {
200 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
202 eKind oldKind
= Kind();
203 if (oldKind
== KIND_FILE
) {
204 // Because this is an external piece of data, mType is one of kFileMime,
205 // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
206 // are passed in as a nsIInputStream which must be converted to a
207 // dom::File before storing.
208 if (nsCOMPtr
<nsIInputStream
> istream
= do_QueryInterface(data
)) {
209 RefPtr
<File
> file
= CreateFileFromInputStream(istream
);
210 if (NS_WARN_IF(!file
)) {
213 data
= do_QueryObject(file
);
215 variant
->SetAsISupports(data
);
217 // We have an external piece of string data. Extract it and store it in the
219 MOZ_ASSERT(oldKind
== KIND_STRING
);
221 nsCOMPtr
<nsISupportsString
> supportsstr
= do_QueryInterface(data
);
224 supportsstr
->GetData(str
);
225 variant
->SetAsAString(str
);
227 nsCOMPtr
<nsISupportsCString
> supportscstr
= do_QueryInterface(data
);
230 supportscstr
->GetData(str
);
231 variant
->SetAsACString(str
);
238 if (oldKind
!= Kind()) {
240 "Clipboard data provided by the OS does not match predicted kind");
241 mDataTransfer
->TypesListMayHaveChanged();
245 void DataTransferItem::GetType(nsAString
& aType
) {
246 // If we don't have a File, we can just put whatever our recorded internal
248 if (Kind() != KIND_FILE
) {
253 // If we do have a File, then we need to look at our File object to discover
254 // what its mime type is. We can use the System Principal here, as this
255 // information should be avaliable even if the data is currently inaccessible
256 // (for example during a dragover).
258 // XXX: This seems inefficient, as it seems like we should be able to get this
259 // data without getting the entire File object, which may require talking to
262 RefPtr
<File
> file
= GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv
);
263 MOZ_ASSERT(!rv
.Failed(), "Failed to get file data with system principal");
265 // If we don't actually have a file, fall back to returning the internal type.
266 if (NS_WARN_IF(!file
)) {
271 file
->GetType(aType
);
274 already_AddRefed
<File
> DataTransferItem::GetAsFile(
275 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
276 // This is done even if we have an mCachedFile, as it performs the necessary
277 // permissions checks to ensure that we are allowed to access this type.
278 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
279 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
283 // We have to check our kind after getting the data, because if we have
284 // external data and the OS lied to us (which unfortunately does happen
285 // sometimes), then we might not have the same type of data as we did coming
286 // into this function.
287 if (NS_WARN_IF(mKind
!= KIND_FILE
)) {
291 // Generate the dom::File from the stored data, caching it so that the
292 // same object is returned in the future.
294 nsCOMPtr
<nsISupports
> supports
;
295 aRv
= data
->GetAsISupports(getter_AddRefs(supports
));
296 MOZ_ASSERT(!aRv
.Failed() && supports
,
297 "File objects should be stored as nsISupports variants");
298 if (aRv
.Failed() || !supports
) {
302 if (RefPtr
<Blob
> blob
= do_QueryObject(supports
)) {
303 mCachedFile
= blob
->ToFile();
305 nsCOMPtr
<nsIGlobalObject
> global
= GetGlobalFromDataTransfer();
306 if (NS_WARN_IF(!global
)) {
310 if (nsCOMPtr
<BlobImpl
> blobImpl
= do_QueryInterface(supports
)) {
311 MOZ_ASSERT(blobImpl
->IsFile());
312 mCachedFile
= File::Create(global
, blobImpl
);
313 if (NS_WARN_IF(!mCachedFile
)) {
316 } else if (nsCOMPtr
<nsIFile
> ifile
= do_QueryInterface(supports
)) {
317 mCachedFile
= File::CreateFromFile(global
, ifile
);
318 if (NS_WARN_IF(!mCachedFile
)) {
321 } else if (nsCOMPtr
<imgIContainer
> img
= do_QueryInterface(supports
)) {
322 nsCOMPtr
<imgITools
> imgTools
=
323 do_CreateInstance("@mozilla.org/image/tools;1");
325 nsCOMPtr
<nsIInputStream
> inputStream
;
326 nsresult rv
= imgTools
->EncodeImage(img
, "image/png"_ns
, u
""_ns
,
327 getter_AddRefs(inputStream
));
328 if (NS_WARN_IF(NS_FAILED(rv
))) {
332 mCachedFile
= CreateFileFromInputStream(
333 inputStream
, "GenericImageNamePNG", u
"image/png"_ns
);
334 if (NS_WARN_IF(!mCachedFile
)) {
338 MOZ_ASSERT(false, "One of the above code paths should be taken");
344 RefPtr
<File
> file
= mCachedFile
;
345 return file
.forget();
348 already_AddRefed
<FileSystemEntry
> DataTransferItem::GetAsEntry(
349 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
350 RefPtr
<File
> file
= GetAsFile(aSubjectPrincipal
, aRv
);
351 if (NS_WARN_IF(aRv
.Failed()) || !file
) {
355 nsCOMPtr
<nsIGlobalObject
> global
= GetGlobalFromDataTransfer();
356 if (NS_WARN_IF(!global
)) {
360 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
361 RefPtr
<FileSystemEntry
> entry
;
362 BlobImpl
* impl
= file
->Impl();
365 if (impl
->IsDirectory()) {
366 nsAutoString fullpath
;
367 impl
->GetMozFullPathInternal(fullpath
, aRv
);
369 aRv
.SuppressException();
373 nsCOMPtr
<nsIFile
> directoryFile
;
374 // fullPath is already in unicode, we don't have to use
375 // NS_NewNativeLocalFile.
377 NS_NewLocalFile(fullpath
, true, getter_AddRefs(directoryFile
));
378 if (NS_WARN_IF(NS_FAILED(rv
))) {
382 RefPtr
<Directory
> directory
= Directory::Create(global
, directoryFile
);
383 entry
= new FileSystemDirectoryEntry(global
, directory
, nullptr, fs
);
385 entry
= new FileSystemFileEntry(global
, file
, nullptr, fs
);
388 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
389 if (!entries
.AppendElement(entry
, fallible
)) {
393 fs
->CreateRoot(entries
);
394 return entry
.forget();
397 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
398 nsIInputStream
* aStream
) {
399 const char* key
= nullptr;
400 for (uint32_t i
= 0; i
< ArrayLength(kFileMimeNameMap
); ++i
) {
401 if (mType
.EqualsASCII(kFileMimeNameMap
[i
].mMimeName
)) {
402 key
= kFileMimeNameMap
[i
].mFileName
;
407 MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
408 key
= "GenericFileName";
411 return CreateFileFromInputStream(aStream
, key
, mType
);
414 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
415 nsIInputStream
* aStream
, const char* aFileNameKey
,
416 const nsAString
& aContentType
) {
417 nsAutoString fileName
;
418 nsresult rv
= nsContentUtils::GetLocalizedString(
419 nsContentUtils::eDOM_PROPERTIES
, aFileNameKey
, fileName
);
420 if (NS_WARN_IF(NS_FAILED(rv
))) {
425 void* data
= nullptr;
426 rv
= NS_ReadInputStreamToBuffer(aStream
, &data
, -1, &available
);
427 if (NS_WARN_IF(NS_FAILED(rv
))) {
431 nsCOMPtr
<nsIGlobalObject
> global
= GetGlobalFromDataTransfer();
432 if (NS_WARN_IF(!global
)) {
436 return File::CreateMemoryFileWithLastModifiedNow(global
, data
, available
,
437 fileName
, aContentType
);
440 void DataTransferItem::GetAsString(FunctionStringCallback
* aCallback
,
441 nsIPrincipal
& aSubjectPrincipal
,
447 // Theoretically this should be done inside of the runnable, as it might be an
448 // expensive operation on some systems, however we wouldn't get access to the
449 // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
450 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
451 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
455 // We have to check our kind after getting the data, because if we have
456 // external data and the OS lied to us (which unfortunately does happen
457 // sometimes), then we might not have the same type of data as we did coming
458 // into this function.
459 if (NS_WARN_IF(mKind
!= KIND_STRING
)) {
463 nsAutoString stringData
;
464 nsresult rv
= data
->GetAsAString(stringData
);
465 if (NS_WARN_IF(NS_FAILED(rv
))) {
469 // Dispatch the callback to the main thread
470 class GASRunnable final
: public Runnable
{
472 GASRunnable(FunctionStringCallback
* aCallback
, const nsAString
& aStringData
)
473 : mozilla::Runnable("GASRunnable"),
474 mCallback(aCallback
),
475 mStringData(aStringData
) {}
477 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
478 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
479 MOZ_CAN_RUN_SCRIPT_BOUNDARY
480 NS_IMETHOD
Run() override
{
482 mCallback
->Call(mStringData
, rv
);
483 NS_WARNING_ASSERTION(!rv
.Failed(), "callback failed");
484 return rv
.StealNSResult();
488 const RefPtr
<FunctionStringCallback
> mCallback
;
489 nsString mStringData
;
492 RefPtr
<GASRunnable
> runnable
= new GASRunnable(aCallback
, stringData
);
494 // DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
495 // nsPIDOMWindowOuter, null
496 nsISupports
* parent
= mDataTransfer
->GetParentObject();
497 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(parent
);
498 if (parent
&& !global
) {
499 if (nsCOMPtr
<dom::EventTarget
> target
= do_QueryInterface(parent
)) {
500 global
= target
->GetOwnerGlobal();
501 } else if (RefPtr
<Event
> event
= do_QueryObject(parent
)) {
502 global
= event
->GetParentObject();
506 rv
= global
->Dispatch(TaskCategory::Other
, runnable
.forget());
508 rv
= NS_DispatchToMainThread(runnable
);
512 "Dispatch to main thread Failed in "
513 "DataTransferItem::GetAsString!");
517 already_AddRefed
<nsIVariant
> DataTransferItem::DataNoSecurityCheck() {
519 FillInExternalData();
521 nsCOMPtr
<nsIVariant
> data
= mData
;
522 return data
.forget();
525 already_AddRefed
<nsIVariant
> DataTransferItem::Data(nsIPrincipal
* aPrincipal
,
527 MOZ_ASSERT(aPrincipal
);
529 // If the inbound principal is system, we can skip the below checks, as
530 // they will trivially succeed.
531 if (aPrincipal
->IsSystemPrincipal()) {
532 return DataNoSecurityCheck();
535 // We should not allow raw data to be accessed from a Protected DataTransfer.
536 // We don't prevent this access if the accessing document is Chrome.
537 if (mDataTransfer
->IsProtected()) {
541 nsCOMPtr
<nsIVariant
> variant
= DataNoSecurityCheck();
543 MOZ_ASSERT(!ChromeOnly(),
544 "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
546 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
550 bool checkItemPrincipal
= mDataTransfer
->IsCrossDomainSubFrameDrop() ||
551 (mDataTransfer
->GetEventMessage() != eDrop
&&
552 mDataTransfer
->GetEventMessage() != ePaste
&&
553 mDataTransfer
->GetEventMessage() != eEditorInput
);
555 // Check if the caller is allowed to access the drag data. Callers with
556 // chrome privileges can always read the data. During the
557 // drop event, allow retrieving the data except in the case where the
558 // source of the drag is in a child frame of the caller. In that case,
559 // we only allow access to data of the same principal. During other events,
560 // only allow access to the data with the same principal.
562 // We don't want to fail with an exception in this siutation, rather we want
563 // to just pretend as though the stored data is "nullptr". This is consistent
564 // with Chrome's behavior and is less surprising for web applications which
565 // don't expect execptions to be raised when performing certain operations.
566 if (Principal() && checkItemPrincipal
&& !aPrincipal
->Subsumes(Principal())) {
574 nsCOMPtr
<nsISupports
> data
;
575 nsresult rv
= variant
->GetAsISupports(getter_AddRefs(data
));
576 if (NS_SUCCEEDED(rv
) && data
) {
577 nsCOMPtr
<EventTarget
> pt
= do_QueryInterface(data
);
579 nsIGlobalObject
* go
= pt
->GetOwnerGlobal();
580 if (NS_WARN_IF(!go
)) {
584 nsCOMPtr
<nsIScriptObjectPrincipal
> sp
= do_QueryInterface(go
);
585 MOZ_ASSERT(sp
, "This cannot fail on the main thread.");
587 nsIPrincipal
* dataPrincipal
= sp
->GetPrincipal();
588 if (NS_WARN_IF(!dataPrincipal
|| !aPrincipal
->Equals(dataPrincipal
))) {
594 return variant
.forget();
597 already_AddRefed
<nsIGlobalObject
>
598 DataTransferItem::GetGlobalFromDataTransfer() {
599 nsCOMPtr
<nsIGlobalObject
> global
;
600 // This is annoying, but DataTransfer may have various things as parent.
601 nsCOMPtr
<EventTarget
> target
=
602 do_QueryInterface(mDataTransfer
->GetParentObject());
604 global
= target
->GetOwnerGlobal();
606 RefPtr
<Event
> event
= do_QueryObject(mDataTransfer
->GetParentObject());
608 global
= event
->GetParentObject();
612 return global
.forget();
615 } // namespace mozilla::dom