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
;
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 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
177 WindowContext
* windowContext
= nullptr;
179 const auto* innerWindow
= global
->GetAsInnerWindow();
180 windowContext
= innerWindow
? innerWindow
->GetWindowContext() : nullptr;
182 MOZ_ASSERT(windowContext
);
183 nsresult rv
= clipboard
->GetData(trans
, mDataTransfer
->ClipboardType(),
185 if (NS_WARN_IF(NS_FAILED(rv
))) {
189 nsCOMPtr
<nsIDragSession
> dragSession
= nsContentUtils::GetDragSession();
194 nsresult rv
= dragSession
->GetData(trans
, mIndex
);
195 if (NS_WARN_IF(NS_FAILED(rv
))) {
201 nsCOMPtr
<nsISupports
> data
;
202 nsresult rv
= trans
->GetTransferData(format
, getter_AddRefs(data
));
203 if (NS_WARN_IF(NS_FAILED(rv
) || !data
)) {
208 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
210 eKind oldKind
= Kind();
211 if (oldKind
== KIND_FILE
) {
212 // Because this is an external piece of data, mType is one of kFileMime,
213 // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
214 // are passed in as a nsIInputStream which must be converted to a
215 // dom::File before storing.
216 if (nsCOMPtr
<nsIInputStream
> istream
= do_QueryInterface(data
)) {
217 RefPtr
<File
> file
= CreateFileFromInputStream(istream
);
218 if (NS_WARN_IF(!file
)) {
221 data
= do_QueryObject(file
);
223 variant
->SetAsISupports(data
);
225 // We have an external piece of string data. Extract it and store it in the
227 MOZ_ASSERT(oldKind
== KIND_STRING
);
229 nsCOMPtr
<nsISupportsString
> supportsstr
= do_QueryInterface(data
);
232 supportsstr
->GetData(str
);
233 variant
->SetAsAString(str
);
235 nsCOMPtr
<nsISupportsCString
> supportscstr
= do_QueryInterface(data
);
238 supportscstr
->GetData(str
);
239 variant
->SetAsACString(str
);
246 if (oldKind
!= Kind()) {
248 "Clipboard data provided by the OS does not match predicted kind");
249 mDataTransfer
->TypesListMayHaveChanged();
253 void DataTransferItem::GetType(nsAString
& aType
) {
254 // If we don't have a File, we can just put whatever our recorded internal
256 if (Kind() != KIND_FILE
) {
261 // If we do have a File, then we need to look at our File object to discover
262 // what its mime type is. We can use the System Principal here, as this
263 // information should be avaliable even if the data is currently inaccessible
264 // (for example during a dragover).
266 // XXX: This seems inefficient, as it seems like we should be able to get this
267 // data without getting the entire File object, which may require talking to
270 RefPtr
<File
> file
= GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv
);
271 MOZ_ASSERT(!rv
.Failed(), "Failed to get file data with system principal");
273 // If we don't actually have a file, fall back to returning the internal type.
274 if (NS_WARN_IF(!file
)) {
279 file
->GetType(aType
);
282 already_AddRefed
<File
> DataTransferItem::GetAsFile(
283 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
284 // This is done even if we have an mCachedFile, as it performs the necessary
285 // permissions checks to ensure that we are allowed to access this type.
286 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
287 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
291 // We have to check our kind after getting the data, because if we have
292 // external data and the OS lied to us (which unfortunately does happen
293 // sometimes), then we might not have the same type of data as we did coming
294 // into this function.
295 if (NS_WARN_IF(mKind
!= KIND_FILE
)) {
299 // Generate the dom::File from the stored data, caching it so that the
300 // same object is returned in the future.
302 nsCOMPtr
<nsISupports
> supports
;
303 aRv
= data
->GetAsISupports(getter_AddRefs(supports
));
304 MOZ_ASSERT(!aRv
.Failed() && supports
,
305 "File objects should be stored as nsISupports variants");
306 if (aRv
.Failed() || !supports
) {
310 if (RefPtr
<Blob
> blob
= do_QueryObject(supports
)) {
311 mCachedFile
= blob
->ToFile();
313 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
314 if (NS_WARN_IF(!global
)) {
318 if (nsCOMPtr
<BlobImpl
> blobImpl
= do_QueryInterface(supports
)) {
319 MOZ_ASSERT(blobImpl
->IsFile());
320 mCachedFile
= File::Create(global
, blobImpl
);
321 if (NS_WARN_IF(!mCachedFile
)) {
324 } else if (nsCOMPtr
<nsIFile
> ifile
= do_QueryInterface(supports
)) {
325 mCachedFile
= File::CreateFromFile(global
, ifile
);
326 if (NS_WARN_IF(!mCachedFile
)) {
329 } else if (nsCOMPtr
<imgIContainer
> img
= do_QueryInterface(supports
)) {
330 nsCOMPtr
<imgITools
> imgTools
=
331 do_CreateInstance("@mozilla.org/image/tools;1");
333 nsCOMPtr
<nsIInputStream
> inputStream
;
334 nsresult rv
= imgTools
->EncodeImage(img
, "image/png"_ns
, u
""_ns
,
335 getter_AddRefs(inputStream
));
336 if (NS_WARN_IF(NS_FAILED(rv
))) {
340 mCachedFile
= CreateFileFromInputStream(
341 inputStream
, "GenericImageNamePNG", u
"image/png"_ns
);
342 if (NS_WARN_IF(!mCachedFile
)) {
346 MOZ_ASSERT(false, "One of the above code paths should be taken");
352 RefPtr
<File
> file
= mCachedFile
;
353 return file
.forget();
356 already_AddRefed
<FileSystemEntry
> DataTransferItem::GetAsEntry(
357 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
358 RefPtr
<File
> file
= GetAsFile(aSubjectPrincipal
, aRv
);
359 if (NS_WARN_IF(aRv
.Failed()) || !file
) {
363 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
364 if (NS_WARN_IF(!global
)) {
368 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
369 RefPtr
<FileSystemEntry
> entry
;
370 BlobImpl
* impl
= file
->Impl();
373 if (impl
->IsDirectory()) {
374 nsAutoString fullpath
;
375 impl
->GetMozFullPathInternal(fullpath
, aRv
);
377 aRv
.SuppressException();
381 nsCOMPtr
<nsIFile
> directoryFile
;
382 // fullPath is already in unicode, we don't have to use
383 // NS_NewNativeLocalFile.
385 NS_NewLocalFile(fullpath
, true, getter_AddRefs(directoryFile
));
386 if (NS_WARN_IF(NS_FAILED(rv
))) {
390 RefPtr
<Directory
> directory
= Directory::Create(global
, directoryFile
);
391 entry
= new FileSystemDirectoryEntry(global
, directory
, nullptr, fs
);
393 entry
= new FileSystemFileEntry(global
, file
, nullptr, fs
);
396 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
397 if (!entries
.AppendElement(entry
, fallible
)) {
401 fs
->CreateRoot(entries
);
402 return entry
.forget();
405 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
406 nsIInputStream
* aStream
) {
407 const char* key
= nullptr;
408 for (uint32_t i
= 0; i
< ArrayLength(kFileMimeNameMap
); ++i
) {
409 if (mType
.EqualsASCII(kFileMimeNameMap
[i
].mMimeName
)) {
410 key
= kFileMimeNameMap
[i
].mFileName
;
415 MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
416 key
= "GenericFileName";
419 return CreateFileFromInputStream(aStream
, key
, mType
);
422 already_AddRefed
<File
> DataTransferItem::CreateFileFromInputStream(
423 nsIInputStream
* aStream
, const char* aFileNameKey
,
424 const nsAString
& aContentType
) {
425 nsAutoString fileName
;
426 nsresult rv
= nsContentUtils::GetLocalizedString(
427 nsContentUtils::eDOM_PROPERTIES
, aFileNameKey
, fileName
);
428 if (NS_WARN_IF(NS_FAILED(rv
))) {
433 void* data
= nullptr;
434 rv
= NS_ReadInputStreamToBuffer(aStream
, &data
, -1, &available
);
435 if (NS_WARN_IF(NS_FAILED(rv
))) {
439 nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal();
440 if (NS_WARN_IF(!global
)) {
444 return File::CreateMemoryFileWithLastModifiedNow(global
, data
, available
,
445 fileName
, aContentType
);
448 void DataTransferItem::GetAsString(FunctionStringCallback
* aCallback
,
449 nsIPrincipal
& aSubjectPrincipal
,
455 // Theoretically this should be done inside of the runnable, as it might be an
456 // expensive operation on some systems, however we wouldn't get access to the
457 // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
458 nsCOMPtr
<nsIVariant
> data
= Data(&aSubjectPrincipal
, aRv
);
459 if (NS_WARN_IF(!data
|| aRv
.Failed())) {
463 // We have to check our kind after getting the data, because if we have
464 // external data and the OS lied to us (which unfortunately does happen
465 // sometimes), then we might not have the same type of data as we did coming
466 // into this function.
467 if (NS_WARN_IF(mKind
!= KIND_STRING
)) {
471 nsAutoString stringData
;
472 nsresult rv
= data
->GetAsAString(stringData
);
473 if (NS_WARN_IF(NS_FAILED(rv
))) {
477 // Dispatch the callback to the main thread
478 class GASRunnable final
: public Runnable
{
480 GASRunnable(FunctionStringCallback
* aCallback
, const nsAString
& aStringData
)
481 : mozilla::Runnable("GASRunnable"),
482 mCallback(aCallback
),
483 mStringData(aStringData
) {}
485 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
486 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
487 MOZ_CAN_RUN_SCRIPT_BOUNDARY
488 NS_IMETHOD
Run() override
{
490 mCallback
->Call(mStringData
, rv
);
491 NS_WARNING_ASSERTION(!rv
.Failed(), "callback failed");
492 return rv
.StealNSResult();
496 const RefPtr
<FunctionStringCallback
> mCallback
;
497 nsString mStringData
;
500 RefPtr
<GASRunnable
> runnable
= new GASRunnable(aCallback
, stringData
);
502 if (nsCOMPtr
<nsIGlobalObject
> global
= mDataTransfer
->GetGlobal()) {
503 rv
= global
->Dispatch(runnable
.forget());
505 rv
= NS_DispatchToMainThread(runnable
);
509 "Dispatch to main thread Failed in "
510 "DataTransferItem::GetAsString!");
514 already_AddRefed
<nsIVariant
> DataTransferItem::DataNoSecurityCheck() {
516 FillInExternalData();
518 nsCOMPtr
<nsIVariant
> data
= mData
;
519 return data
.forget();
522 already_AddRefed
<nsIVariant
> DataTransferItem::Data(nsIPrincipal
* aPrincipal
,
524 MOZ_ASSERT(aPrincipal
);
526 // If the inbound principal is system, we can skip the below checks, as
527 // they will trivially succeed.
528 if (aPrincipal
->IsSystemPrincipal()) {
529 return DataNoSecurityCheck();
532 // We should not allow raw data to be accessed from a Protected DataTransfer.
533 // We don't prevent this access if the accessing document is Chrome.
534 if (mDataTransfer
->IsProtected()) {
538 nsCOMPtr
<nsIVariant
> variant
= DataNoSecurityCheck();
540 MOZ_ASSERT(!ChromeOnly(),
541 "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
543 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
547 bool checkItemPrincipal
= mDataTransfer
->IsCrossDomainSubFrameDrop() ||
548 (mDataTransfer
->GetEventMessage() != eDrop
&&
549 mDataTransfer
->GetEventMessage() != ePaste
&&
550 mDataTransfer
->GetEventMessage() != eEditorInput
);
552 // Check if the caller is allowed to access the drag data. Callers with
553 // chrome privileges can always read the data. During the
554 // drop event, allow retrieving the data except in the case where the
555 // source of the drag is in a child frame of the caller. In that case,
556 // we only allow access to data of the same principal. During other events,
557 // only allow access to the data with the same principal.
559 // We don't want to fail with an exception in this siutation, rather we want
560 // to just pretend as though the stored data is "nullptr". This is consistent
561 // with Chrome's behavior and is less surprising for web applications which
562 // don't expect execptions to be raised when performing certain operations.
563 if (Principal() && checkItemPrincipal
&& !aPrincipal
->Subsumes(Principal())) {
571 nsCOMPtr
<nsISupports
> data
;
572 nsresult rv
= variant
->GetAsISupports(getter_AddRefs(data
));
573 if (NS_SUCCEEDED(rv
) && data
) {
574 nsCOMPtr
<EventTarget
> pt
= do_QueryInterface(data
);
576 nsIGlobalObject
* go
= pt
->GetOwnerGlobal();
577 if (NS_WARN_IF(!go
)) {
581 nsCOMPtr
<nsIScriptObjectPrincipal
> sp
= do_QueryInterface(go
);
582 MOZ_ASSERT(sp
, "This cannot fail on the main thread.");
584 nsIPrincipal
* dataPrincipal
= sp
->GetPrincipal();
585 if (NS_WARN_IF(!dataPrincipal
|| !aPrincipal
->Equals(dataPrincipal
))) {
591 return variant
.forget();
594 } // namespace mozilla::dom