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/FileSystem.h"
19 #include "mozilla/dom/FileSystemDirectoryEntry.h"
20 #include "mozilla/dom/FileSystemFileEntry.h"
21 #include "imgIContainer.h"
22 #include "imgITools.h"
23 #include "nsComponentManagerUtils.h"
24 #include "nsGlobalWindowInner.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
;
79 it
->mDoNotAttemptToLoadData
= mDoNotAttemptToLoadData
;
84 void DataTransferItem::SetData(nsIVariant
* aData
) {
85 // Invalidate our file cache, we will regenerate it with the new data
86 mCachedFile
= nullptr;
89 // We are holding a temporary null which will later be filled.
90 // These are provided by the system, and have guaranteed properties about
91 // their kind based on their type.
92 MOZ_ASSERT(!mType
.IsEmpty());
93 // This type should not be provided by the OS.
94 MOZ_ASSERT(!mType
.EqualsASCII(kNativeImageMime
));
97 for (uint32_t i
= 0; i
< ArrayLength(kFileMimeNameMap
); ++i
) {
98 if (mType
.EqualsASCII(kFileMimeNameMap
[i
].mMimeName
)) {
109 mKind
= KindFromData(mData
);
112 /* static */ DataTransferItem::eKind
DataTransferItem::KindFromData(
114 nsCOMPtr
<nsISupports
> supports
;
115 nsresult rv
= aData
->GetAsISupports(getter_AddRefs(supports
));
116 if (NS_SUCCEEDED(rv
) && supports
) {
117 // Check if we have one of the supported file data formats
118 if (RefPtr
<Blob
>(do_QueryObject(supports
)) ||
119 nsCOMPtr
<BlobImpl
>(do_QueryInterface(supports
)) ||
120 nsCOMPtr
<nsIFile
>(do_QueryInterface(supports
))) {
124 if (StaticPrefs::dom_events_dataTransfer_imageAsFile_enabled()) {
125 // Firefox internally uses imgIContainer to represent images being
126 // copied/dragged. These need to be encoded to PNG files.
127 if (nsCOMPtr
<imgIContainer
>(do_QueryInterface(supports
))) {
134 // If we can't get the data type as a string, that means that the object
135 // should be considered to be of the "other" type. This is impossible
136 // through the APIs defined by the spec, but we provide extra Moz* APIs,
137 // which allow setting of non-string data. We determine whether we can
138 // consider it a string, by calling GetAsAString, and checking for success.
139 rv
= aData
->GetAsAString(string
);
140 if (NS_SUCCEEDED(rv
)) {
147 void DataTransferItem::FillInExternalData() {
148 if (mData
|| mDoNotAttemptToLoadData
) {
152 NS_ConvertUTF16toUTF8
utf8format(mType
);
153 const char* format
= utf8format
.get();
154 if (strcmp(format
, "text/uri-list") == 0) {
155 format
= kURLDataMime
;
158 nsCOMPtr
<nsITransferable
> trans
= mDataTransfer
->GetTransferable();
160 trans
= do_CreateInstance("@mozilla.org/widget/transferable;1");
161 if (NS_WARN_IF(!trans
)) {
165 trans
->Init(nullptr);
166 trans
->AddDataFlavor(format
);
168 if (mDataTransfer
->GetEventMessage() == ePaste
) {
169 MOZ_ASSERT(mIndex
== 0, "index in clipboard must be 0");
171 nsCOMPtr
<nsIClipboard
> clipboard
=
172 do_GetService("@mozilla.org/widget/clipboard;1");
173 if (!clipboard
|| mDataTransfer
->ClipboardType() < 0) {
177 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
178 WindowContext
* windowContext
= nullptr;
180 const auto* innerWindow
= global
->GetAsInnerWindow();
181 windowContext
= innerWindow
? innerWindow
->GetWindowContext() : nullptr;
183 MOZ_ASSERT(windowContext
);
184 nsresult rv
= clipboard
->GetData(trans
, mDataTransfer
->ClipboardType(),
186 if (NS_WARN_IF(NS_FAILED(rv
))) {
187 if (rv
== NS_ERROR_CONTENT_BLOCKED
) {
188 // If the load of this content was blocked by Content Analysis,
189 // do not attempt to load it again.
190 mDoNotAttemptToLoadData
= true;
195 nsCOMPtr
<nsIDragSession
> dragSession
=
196 mDataTransfer
->GetOwnerDragSession();
201 nsresult rv
= dragSession
->GetData(trans
, mIndex
);
202 if (NS_WARN_IF(NS_FAILED(rv
))) {
208 nsCOMPtr
<nsISupports
> data
;
209 nsresult rv
= trans
->GetTransferData(format
, getter_AddRefs(data
));
210 if (NS_WARN_IF(NS_FAILED(rv
) || !data
)) {
215 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
217 eKind oldKind
= Kind();
218 if (oldKind
== KIND_FILE
) {
219 // Because this is an external piece of data, mType is one of kFileMime,
220 // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
221 // are passed in as a nsIInputStream which must be converted to a
222 // dom::File before storing.
223 if (nsCOMPtr
<nsIInputStream
> istream
= do_QueryInterface(data
)) {
224 RefPtr
<File
> file
= CreateFileFromInputStream(istream
);
225 if (NS_WARN_IF(!file
)) {
228 data
= do_QueryObject(file
);
230 variant
->SetAsISupports(data
);
232 // We have an external piece of string data. Extract it and store it in the
234 MOZ_ASSERT(oldKind
== KIND_STRING
);
236 nsCOMPtr
<nsISupportsString
> supportsstr
= do_QueryInterface(data
);
239 supportsstr
->GetData(str
);
240 variant
->SetAsAString(str
);
242 nsCOMPtr
<nsISupportsCString
> supportscstr
= do_QueryInterface(data
);
245 supportscstr
->GetData(str
);
246 variant
->SetAsACString(str
);
253 if (oldKind
!= Kind()) {
255 "Clipboard data provided by the OS does not match predicted kind");
256 mDataTransfer
->TypesListMayHaveChanged();
260 void DataTransferItem::GetType(nsAString
& aType
) {
261 // If we don't have a File, we can just put whatever our recorded internal
263 if (Kind() != KIND_FILE
) {
268 // If we do have a File, then we need to look at our File object to discover
269 // what its mime type is. We can use the System Principal here, as this
270 // information should be avaliable even if the data is currently inaccessible
271 // (for example during a dragover).
273 // XXX: This seems inefficient, as it seems like we should be able to get this
274 // data without getting the entire File object, which may require talking to
277 RefPtr
<File
> file
= GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv
);
278 MOZ_ASSERT(!rv
.Failed(), "Failed to get file data with system principal");
280 // If we don't actually have a file, fall back to returning the internal type.
281 if (NS_WARN_IF(!file
)) {
286 file
->GetType(aType
);
289 already_AddRefed
<File
> DataTransferItem::GetAsFile(
290 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
291 // This is done even if we have an mCachedFile, as it performs the necessary
292 // permissions checks to ensure that we are allowed to access this type.
293 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
294 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
298 // We have to check our kind after getting the data, because if we have
299 // external data and the OS lied to us (which unfortunately does happen
300 // sometimes), then we might not have the same type of data as we did coming
301 // into this function.
302 if (NS_WARN_IF(mKind
!= KIND_FILE
)) {
306 // Generate the dom::File from the stored data, caching it so that the
307 // same object is returned in the future.
309 nsCOMPtr
<nsISupports
> supports
;
310 aRv
= data
->GetAsISupports(getter_AddRefs(supports
));
311 MOZ_ASSERT(!aRv
.Failed() && supports
,
312 "File objects should be stored as nsISupports variants");
313 if (aRv
.Failed() || !supports
) {
317 if (RefPtr
<Blob
> blob
= do_QueryObject(supports
)) {
318 mCachedFile
= blob
->ToFile();
320 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
321 if (NS_WARN_IF(!global
)) {
325 if (nsCOMPtr
<BlobImpl
> blobImpl
= do_QueryInterface(supports
)) {
326 MOZ_ASSERT(blobImpl
->IsFile());
327 mCachedFile
= File::Create(global
, blobImpl
);
328 if (NS_WARN_IF(!mCachedFile
)) {
331 } else if (nsCOMPtr
<nsIFile
> ifile
= do_QueryInterface(supports
)) {
332 mCachedFile
= File::CreateFromFile(global
, ifile
);
333 if (NS_WARN_IF(!mCachedFile
)) {
336 } else if (nsCOMPtr
<imgIContainer
> img
= do_QueryInterface(supports
)) {
337 nsCOMPtr
<imgITools
> imgTools
=
338 do_CreateInstance("@mozilla.org/image/tools;1");
340 nsCOMPtr
<nsIInputStream
> inputStream
;
341 nsresult rv
= imgTools
->EncodeImage(img
, "image/png"_ns
, u
""_ns
,
342 getter_AddRefs(inputStream
));
343 if (NS_WARN_IF(NS_FAILED(rv
))) {
347 mCachedFile
= CreateFileFromInputStream(
348 inputStream
, "GenericImageNamePNG", u
"image/png"_ns
);
349 if (NS_WARN_IF(!mCachedFile
)) {
353 MOZ_ASSERT(false, "One of the above code paths should be taken");
359 RefPtr
<File
> file
= mCachedFile
;
360 return file
.forget();
363 already_AddRefed
<FileSystemEntry
> DataTransferItem::GetAsEntry(
364 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
365 RefPtr
<File
> file
= GetAsFile(aSubjectPrincipal
, aRv
);
366 if (NS_WARN_IF(aRv
.Failed()) || !file
) {
370 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
371 if (NS_WARN_IF(!global
)) {
375 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
376 RefPtr
<FileSystemEntry
> entry
;
377 BlobImpl
* impl
= file
->Impl();
380 if (impl
->IsDirectory()) {
381 nsAutoString fullpath
;
382 impl
->GetMozFullPathInternal(fullpath
, aRv
);
384 aRv
.SuppressException();
388 nsCOMPtr
<nsIFile
> directoryFile
;
389 // fullPath is already in unicode, we don't have to use
390 // NS_NewNativeLocalFile.
392 NS_NewLocalFile(fullpath
, true, getter_AddRefs(directoryFile
));
393 if (NS_WARN_IF(NS_FAILED(rv
))) {
397 RefPtr
<Directory
> directory
= Directory::Create(global
, directoryFile
);
398 entry
= new FileSystemDirectoryEntry(global
, directory
, nullptr, fs
);
400 entry
= new FileSystemFileEntry(global
, file
, nullptr, fs
);
403 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
404 if (!entries
.AppendElement(entry
, fallible
)) {
408 fs
->CreateRoot(entries
);
409 return entry
.forget();
412 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
413 nsIInputStream
* aStream
) {
414 const char* key
= nullptr;
415 for (uint32_t i
= 0; i
< ArrayLength(kFileMimeNameMap
); ++i
) {
416 if (mType
.EqualsASCII(kFileMimeNameMap
[i
].mMimeName
)) {
417 key
= kFileMimeNameMap
[i
].mFileName
;
422 MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
423 key
= "GenericFileName";
426 return CreateFileFromInputStream(aStream
, key
, mType
);
429 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
430 nsIInputStream
* aStream
, const char* aFileNameKey
,
431 const nsAString
& aContentType
) {
432 nsAutoString fileName
;
433 nsresult rv
= nsContentUtils::GetLocalizedString(
434 nsContentUtils::eDOM_PROPERTIES
, aFileNameKey
, fileName
);
435 if (NS_WARN_IF(NS_FAILED(rv
))) {
440 void* data
= nullptr;
441 rv
= NS_ReadInputStreamToBuffer(aStream
, &data
, -1, &available
);
442 if (NS_WARN_IF(NS_FAILED(rv
))) {
446 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
447 if (NS_WARN_IF(!global
)) {
451 return File::CreateMemoryFileWithLastModifiedNow(global
, data
, available
,
452 fileName
, aContentType
);
455 void DataTransferItem::GetAsString(FunctionStringCallback
* aCallback
,
456 nsIPrincipal
& aSubjectPrincipal
,
462 // Theoretically this should be done inside of the runnable, as it might be an
463 // expensive operation on some systems, however we wouldn't get access to the
464 // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
465 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
466 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
470 // We have to check our kind after getting the data, because if we have
471 // external data and the OS lied to us (which unfortunately does happen
472 // sometimes), then we might not have the same type of data as we did coming
473 // into this function.
474 if (NS_WARN_IF(mKind
!= KIND_STRING
)) {
478 nsAutoString stringData
;
479 nsresult rv
= data
->GetAsAString(stringData
);
480 if (NS_WARN_IF(NS_FAILED(rv
))) {
484 // Dispatch the callback to the main thread
485 class GASRunnable final
: public Runnable
{
487 GASRunnable(FunctionStringCallback
* aCallback
, const nsAString
& aStringData
)
488 : mozilla::Runnable("GASRunnable"),
489 mCallback(aCallback
),
490 mStringData(aStringData
) {}
492 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
493 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
494 MOZ_CAN_RUN_SCRIPT_BOUNDARY
495 NS_IMETHOD
Run() override
{
497 mCallback
->Call(mStringData
, rv
);
498 NS_WARNING_ASSERTION(!rv
.Failed(), "callback failed");
499 return rv
.StealNSResult();
503 const RefPtr
<FunctionStringCallback
> mCallback
;
504 nsString mStringData
;
507 RefPtr
<GASRunnable
> runnable
= new GASRunnable(aCallback
, stringData
);
509 if (nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal()) {
510 rv
= global
->Dispatch(runnable
.forget());
512 rv
= NS_DispatchToMainThread(runnable
);
516 "Dispatch to main thread Failed in "
517 "DataTransferItem::GetAsString!");
521 already_AddRefed
<nsIVariant
> DataTransferItem::DataNoSecurityCheck() {
523 FillInExternalData();
525 nsCOMPtr
<nsIVariant
> data
= mData
;
526 return data
.forget();
529 already_AddRefed
<nsIVariant
> DataTransferItem::Data(nsIPrincipal
* aPrincipal
,
531 MOZ_ASSERT(aPrincipal
);
533 // If the inbound principal is system, we can skip the below checks, as
534 // they will trivially succeed.
535 if (aPrincipal
->IsSystemPrincipal()) {
536 return DataNoSecurityCheck();
539 // We should not allow raw data to be accessed from a Protected DataTransfer.
540 // We don't prevent this access if the accessing document is Chrome.
541 if (mDataTransfer
->IsProtected()) {
545 nsCOMPtr
<nsIVariant
> variant
= DataNoSecurityCheck();
547 MOZ_ASSERT(!ChromeOnly(),
548 "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
550 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
554 bool checkItemPrincipal
= mDataTransfer
->IsCrossDomainSubFrameDrop() ||
555 (mDataTransfer
->GetEventMessage() != eDrop
&&
556 mDataTransfer
->GetEventMessage() != ePaste
&&
557 mDataTransfer
->GetEventMessage() != eEditorInput
);
559 // Check if the caller is allowed to access the drag data. Callers with
560 // chrome privileges can always read the data. During the
561 // drop event, allow retrieving the data except in the case where the
562 // source of the drag is in a child frame of the caller. In that case,
563 // we only allow access to data of the same principal. During other events,
564 // only allow access to the data with the same principal.
566 // We don't want to fail with an exception in this siutation, rather we want
567 // to just pretend as though the stored data is "nullptr". This is consistent
568 // with Chrome's behavior and is less surprising for web applications which
569 // don't expect execptions to be raised when performing certain operations.
570 if (Principal() && checkItemPrincipal
&& !aPrincipal
->Subsumes(Principal())) {
578 nsCOMPtr
<nsISupports
> data
;
579 nsresult rv
= variant
->GetAsISupports(getter_AddRefs(data
));
580 if (NS_SUCCEEDED(rv
) && data
) {
581 nsCOMPtr
<EventTarget
> pt
= do_QueryInterface(data
);
583 nsIGlobalObject
* go
= pt
->GetOwnerGlobal();
584 if (NS_WARN_IF(!go
)) {
588 nsCOMPtr
<nsIScriptObjectPrincipal
> sp
= do_QueryInterface(go
);
589 MOZ_ASSERT(sp
, "This cannot fail on the main thread.");
591 nsIPrincipal
* dataPrincipal
= sp
->GetPrincipal();
592 if (NS_WARN_IF(!dataPrincipal
|| !aPrincipal
->Equals(dataPrincipal
))) {
598 return variant
.forget();
601 } // namespace mozilla::dom