Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "DataTransferItem.h"
7 : #include "DataTransferItemList.h"
8 :
9 : #include "mozilla/ContentEvents.h"
10 : #include "mozilla/EventForwards.h"
11 : #include "mozilla/dom/DataTransferItemBinding.h"
12 : #include "mozilla/dom/Directory.h"
13 : #include "mozilla/dom/Event.h"
14 : #include "mozilla/dom/FileSystem.h"
15 : #include "mozilla/dom/FileSystemDirectoryEntry.h"
16 : #include "mozilla/dom/FileSystemFileEntry.h"
17 : #include "nsIClipboard.h"
18 : #include "nsISupportsPrimitives.h"
19 : #include "nsIScriptObjectPrincipal.h"
20 : #include "nsNetUtil.h"
21 : #include "nsQueryObject.h"
22 : #include "nsContentUtils.h"
23 : #include "nsVariant.h"
24 :
25 : namespace {
26 :
27 : struct FileMimeNameData
28 : {
29 : const char* mMimeName;
30 : const char* mFileName;
31 : };
32 :
33 : FileMimeNameData kFileMimeNameMap[] = {
34 : { kFileMime, "GenericFileName" },
35 : { kPNGImageMime, "GenericImageNamePNG" },
36 : };
37 :
38 : } // anonymous namespace
39 :
40 : namespace mozilla {
41 : namespace dom {
42 :
43 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData,
44 : mPrincipal, mDataTransfer, mCachedFile)
45 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem)
46 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem)
47 :
48 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem)
49 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
50 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
51 0 : NS_INTERFACE_MAP_END
52 :
53 : JSObject*
54 0 : DataTransferItem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
55 : {
56 0 : return DataTransferItemBinding::Wrap(aCx, this, aGivenProto);
57 : }
58 :
59 : already_AddRefed<DataTransferItem>
60 0 : DataTransferItem::Clone(DataTransfer* aDataTransfer) const
61 : {
62 0 : MOZ_ASSERT(aDataTransfer);
63 :
64 0 : RefPtr<DataTransferItem> it = new DataTransferItem(aDataTransfer, mType);
65 :
66 : // Copy over all of the fields
67 0 : it->mKind = mKind;
68 0 : it->mIndex = mIndex;
69 0 : it->mData = mData;
70 0 : it->mPrincipal = mPrincipal;
71 0 : it->mChromeOnly = mChromeOnly;
72 :
73 0 : return it.forget();
74 : }
75 :
76 : void
77 0 : DataTransferItem::SetData(nsIVariant* aData)
78 : {
79 : // Invalidate our file cache, we will regenerate it with the new data
80 0 : mCachedFile = nullptr;
81 :
82 0 : if (!aData) {
83 : // We are holding a temporary null which will later be filled.
84 : // These are provided by the system, and have guaranteed properties about
85 : // their kind based on their type.
86 0 : MOZ_ASSERT(!mType.IsEmpty());
87 :
88 0 : mKind = KIND_STRING;
89 0 : for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
90 0 : if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
91 0 : mKind = KIND_FILE;
92 0 : break;
93 : }
94 : }
95 :
96 0 : mData = nullptr;
97 0 : return;
98 : }
99 :
100 0 : mData = aData;
101 0 : mKind = KindFromData(mData);
102 : }
103 :
104 : /* static */ DataTransferItem::eKind
105 0 : DataTransferItem::KindFromData(nsIVariant* aData)
106 : {
107 0 : nsCOMPtr<nsISupports> supports;
108 0 : nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
109 0 : if (NS_SUCCEEDED(rv) && supports) {
110 : // Check if we have one of the supported file data formats
111 0 : if (nsCOMPtr<nsIDOMBlob>(do_QueryInterface(supports)) ||
112 0 : nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
113 0 : nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
114 0 : return KIND_FILE;
115 : }
116 : }
117 :
118 0 : nsAutoString string;
119 : // If we can't get the data type as a string, that means that the object
120 : // should be considered to be of the "other" type. This is impossible
121 : // through the APIs defined by the spec, but we provide extra Moz* APIs,
122 : // which allow setting of non-string data. We determine whether we can
123 : // consider it a string, by calling GetAsAString, and checking for success.
124 0 : rv = aData->GetAsAString(string);
125 0 : if (NS_SUCCEEDED(rv)) {
126 0 : return KIND_STRING;
127 : }
128 :
129 0 : return KIND_OTHER;
130 : }
131 :
132 : void
133 0 : DataTransferItem::FillInExternalData()
134 : {
135 0 : if (mData) {
136 0 : return;
137 : }
138 :
139 0 : NS_ConvertUTF16toUTF8 utf8format(mType);
140 0 : const char* format = utf8format.get();
141 0 : if (strcmp(format, "text/plain") == 0) {
142 0 : format = kUnicodeMime;
143 0 : } else if (strcmp(format, "text/uri-list") == 0) {
144 0 : format = kURLDataMime;
145 : }
146 :
147 : nsCOMPtr<nsITransferable> trans =
148 0 : do_CreateInstance("@mozilla.org/widget/transferable;1");
149 0 : if (NS_WARN_IF(!trans)) {
150 0 : return;
151 : }
152 :
153 0 : trans->Init(nullptr);
154 0 : trans->AddDataFlavor(format);
155 :
156 0 : if (mDataTransfer->GetEventMessage() == ePaste) {
157 0 : MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
158 :
159 : nsCOMPtr<nsIClipboard> clipboard =
160 0 : do_GetService("@mozilla.org/widget/clipboard;1");
161 0 : if (!clipboard || mDataTransfer->ClipboardType() < 0) {
162 0 : return;
163 : }
164 :
165 0 : nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType());
166 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
167 0 : return;
168 : }
169 : } else {
170 0 : nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
171 0 : if (!dragSession) {
172 0 : return;
173 : }
174 :
175 0 : nsresult rv = dragSession->GetData(trans, mIndex);
176 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
177 0 : return;
178 : }
179 : }
180 :
181 0 : uint32_t length = 0;
182 0 : nsCOMPtr<nsISupports> data;
183 0 : nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length);
184 0 : if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
185 0 : return;
186 : }
187 :
188 : // Fill the variant
189 0 : RefPtr<nsVariantCC> variant = new nsVariantCC();
190 :
191 0 : eKind oldKind = Kind();
192 0 : if (oldKind == KIND_FILE) {
193 : // Because this is an external piece of data, mType is one of kFileMime,
194 : // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
195 : // are passed in as a nsIInputStream which must be converted to a
196 : // dom::File before storing.
197 0 : if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
198 0 : RefPtr<File> file = CreateFileFromInputStream(istream);
199 0 : if (NS_WARN_IF(!file)) {
200 0 : return;
201 : }
202 0 : data = do_QueryObject(file);
203 : }
204 :
205 0 : variant->SetAsISupports(data);
206 : } else {
207 : // We have an external piece of string data. Extract it and store it in the variant
208 0 : MOZ_ASSERT(oldKind == KIND_STRING);
209 :
210 0 : nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
211 0 : if (supportsstr) {
212 0 : nsAutoString str;
213 0 : supportsstr->GetData(str);
214 0 : variant->SetAsAString(str);
215 : } else {
216 0 : nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
217 0 : if (supportscstr) {
218 0 : nsAutoCString str;
219 0 : supportscstr->GetData(str);
220 0 : variant->SetAsACString(str);
221 : }
222 : }
223 : }
224 :
225 0 : SetData(variant);
226 :
227 0 : if (oldKind != Kind()) {
228 0 : NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
229 0 : mDataTransfer->TypesListMayHaveChanged();
230 : }
231 : }
232 :
233 : void
234 0 : DataTransferItem::GetType(nsAString& aType)
235 : {
236 : // If we don't have a File, we can just put whatever our recorded internal
237 : // type is.
238 0 : if (Kind() != KIND_FILE) {
239 0 : aType = mType;
240 0 : return;
241 : }
242 :
243 : // If we do have a File, then we need to look at our File object to discover
244 : // what its mime type is. We can use the System Principal here, as this
245 : // information should be avaliable even if the data is currently inaccessible
246 : // (for example during a dragover).
247 : //
248 : // XXX: This seems inefficient, as it seems like we should be able to get this
249 : // data without getting the entire File object, which may require talking to
250 : // the OS.
251 0 : ErrorResult rv;
252 0 : RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv);
253 0 : MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal");
254 :
255 : // If we don't actually have a file, fall back to returning the internal type.
256 0 : if (NS_WARN_IF(!file)) {
257 0 : aType = mType;
258 0 : return;
259 : }
260 :
261 0 : file->GetType(aType);
262 : }
263 :
264 : already_AddRefed<File>
265 0 : DataTransferItem::GetAsFile(nsIPrincipal& aSubjectPrincipal,
266 : ErrorResult& aRv)
267 : {
268 : // This is done even if we have an mCachedFile, as it performs the necessary
269 : // permissions checks to ensure that we are allowed to access this type.
270 0 : nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
271 0 : if (NS_WARN_IF(!data || aRv.Failed())) {
272 0 : return nullptr;
273 : }
274 :
275 : // We have to check our kind after getting the data, because if we have
276 : // external data and the OS lied to us (which unfortunately does happen
277 : // sometimes), then we might not have the same type of data as we did coming
278 : // into this function.
279 0 : if (NS_WARN_IF(mKind != KIND_FILE)) {
280 0 : return nullptr;
281 : }
282 :
283 : // Generate the dom::File from the stored data, caching it so that the
284 : // same object is returned in the future.
285 0 : if (!mCachedFile) {
286 0 : nsCOMPtr<nsISupports> supports;
287 0 : aRv = data->GetAsISupports(getter_AddRefs(supports));
288 0 : MOZ_ASSERT(!aRv.Failed() && supports,
289 : "File objects should be stored as nsISupports variants");
290 0 : if (aRv.Failed() || !supports) {
291 0 : return nullptr;
292 : }
293 :
294 0 : if (nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports)) {
295 0 : Blob* blob = static_cast<Blob*>(domBlob.get());
296 0 : mCachedFile = blob->ToFile();
297 0 : } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
298 0 : MOZ_ASSERT(blobImpl->IsFile());
299 0 : mCachedFile = File::Create(mDataTransfer, blobImpl);
300 0 : } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
301 0 : mCachedFile = File::CreateFromFile(mDataTransfer, ifile);
302 : } else {
303 0 : MOZ_ASSERT(false, "One of the above code paths should be taken");
304 : return nullptr;
305 : }
306 : }
307 :
308 0 : RefPtr<File> file = mCachedFile;
309 0 : return file.forget();
310 : }
311 :
312 : already_AddRefed<FileSystemEntry>
313 0 : DataTransferItem::GetAsEntry(nsIPrincipal& aSubjectPrincipal,
314 : ErrorResult& aRv)
315 : {
316 0 : RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv);
317 0 : if (NS_WARN_IF(aRv.Failed()) || !file) {
318 0 : return nullptr;
319 : }
320 :
321 0 : nsCOMPtr<nsIGlobalObject> global;
322 : // This is annoying, but DataTransfer may have various things as parent.
323 : nsCOMPtr<EventTarget> target =
324 0 : do_QueryInterface(mDataTransfer->GetParentObject());
325 0 : if (target) {
326 0 : global = target->GetOwnerGlobal();
327 : } else {
328 : nsCOMPtr<nsIDOMEvent> event =
329 0 : do_QueryInterface(mDataTransfer->GetParentObject());
330 0 : if (event) {
331 0 : global = event->InternalDOMEvent()->GetParentObject();
332 : }
333 : }
334 :
335 0 : if (!global) {
336 0 : return nullptr;
337 : }
338 :
339 0 : RefPtr<FileSystem> fs = FileSystem::Create(global);
340 0 : RefPtr<FileSystemEntry> entry;
341 0 : BlobImpl* impl = file->Impl();
342 0 : MOZ_ASSERT(impl);
343 :
344 0 : if (impl->IsDirectory()) {
345 0 : nsAutoString fullpath;
346 0 : impl->GetMozFullPathInternal(fullpath, aRv);
347 0 : if (aRv.Failed()) {
348 0 : aRv.SuppressException();
349 0 : return nullptr;
350 : }
351 :
352 0 : nsCOMPtr<nsIFile> directoryFile;
353 : // fullPath is already in unicode, we don't have to use
354 : // NS_NewNativeLocalFile.
355 0 : nsresult rv = NS_NewLocalFile(fullpath, true,
356 0 : getter_AddRefs(directoryFile));
357 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
358 0 : return nullptr;
359 : }
360 :
361 0 : RefPtr<Directory> directory = Directory::Create(global, directoryFile);
362 0 : entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs);
363 : } else {
364 0 : entry = new FileSystemFileEntry(global, file, nullptr, fs);
365 : }
366 :
367 0 : Sequence<RefPtr<FileSystemEntry>> entries;
368 0 : if (!entries.AppendElement(entry, fallible)) {
369 0 : return nullptr;
370 : }
371 :
372 0 : fs->CreateRoot(entries);
373 0 : return entry.forget();
374 : }
375 :
376 : already_AddRefed<File>
377 0 : DataTransferItem::CreateFileFromInputStream(nsIInputStream* aStream)
378 : {
379 0 : const char* key = nullptr;
380 0 : for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
381 0 : if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
382 0 : key = kFileMimeNameMap[i].mFileName;
383 0 : break;
384 : }
385 : }
386 0 : if (!key) {
387 0 : MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
388 : key = "GenericFileName";
389 : }
390 :
391 0 : nsXPIDLString fileName;
392 : nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
393 0 : key, fileName);
394 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
395 0 : return nullptr;
396 : }
397 :
398 : uint64_t available;
399 0 : rv = aStream->Available(&available);
400 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
401 0 : return nullptr;
402 : }
403 :
404 0 : void* data = nullptr;
405 0 : rv = NS_ReadInputStreamToBuffer(aStream, &data, available);
406 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
407 0 : return nullptr;
408 : }
409 :
410 : return File::CreateMemoryFile(mDataTransfer, data, available, fileName,
411 0 : mType, PR_Now());
412 : }
413 :
414 : void
415 0 : DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
416 : nsIPrincipal& aSubjectPrincipal,
417 : ErrorResult& aRv)
418 : {
419 0 : if (!aCallback) {
420 0 : return;
421 : }
422 :
423 : // Theoretically this should be done inside of the runnable, as it might be an
424 : // expensive operation on some systems, however we wouldn't get access to the
425 : // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
426 0 : nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
427 0 : if (NS_WARN_IF(!data || aRv.Failed())) {
428 0 : return;
429 : }
430 :
431 : // We have to check our kind after getting the data, because if we have
432 : // external data and the OS lied to us (which unfortunately does happen
433 : // sometimes), then we might not have the same type of data as we did coming
434 : // into this function.
435 0 : if (NS_WARN_IF(mKind != KIND_STRING)) {
436 0 : return;
437 : }
438 :
439 0 : nsAutoString stringData;
440 0 : nsresult rv = data->GetAsAString(stringData);
441 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
442 0 : return;
443 : }
444 :
445 : // Dispatch the callback to the main thread
446 0 : class GASRunnable final : public Runnable
447 : {
448 : public:
449 0 : GASRunnable(FunctionStringCallback* aCallback, const nsAString& aStringData)
450 0 : : mozilla::Runnable("GASRunnable")
451 : , mCallback(aCallback)
452 0 : , mStringData(aStringData)
453 0 : {}
454 :
455 0 : NS_IMETHOD Run() override
456 : {
457 0 : ErrorResult rv;
458 0 : mCallback->Call(mStringData, rv);
459 0 : NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
460 0 : return rv.StealNSResult();
461 : }
462 : private:
463 : RefPtr<FunctionStringCallback> mCallback;
464 : nsString mStringData;
465 : };
466 :
467 0 : RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
468 :
469 : // DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
470 : // nsPIDOMWindowOuter, null
471 0 : nsISupports* parent = mDataTransfer->GetParentObject();
472 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(parent);
473 0 : if (parent && !global) {
474 0 : if (nsCOMPtr<dom::EventTarget> target = do_QueryInterface(parent)) {
475 0 : global = target->GetOwnerGlobal();
476 0 : } else if (nsCOMPtr<nsIDOMEvent> event = do_QueryInterface(parent)) {
477 0 : global = event->InternalDOMEvent()->GetParentObject();
478 : }
479 : }
480 0 : if (global) {
481 0 : rv = global->Dispatch("GASRunnable", TaskCategory::Other, runnable.forget());
482 : } else {
483 0 : rv = NS_DispatchToMainThread(runnable);
484 : }
485 0 : if (NS_FAILED(rv)) {
486 : NS_WARNING("Dispatch to main thread Failed in "
487 0 : "DataTransferItem::GetAsString!");
488 : }
489 : }
490 :
491 : already_AddRefed<nsIVariant>
492 0 : DataTransferItem::DataNoSecurityCheck()
493 : {
494 0 : if (!mData) {
495 0 : FillInExternalData();
496 : }
497 0 : nsCOMPtr<nsIVariant> data = mData;
498 0 : return data.forget();
499 : }
500 :
501 : already_AddRefed<nsIVariant>
502 0 : DataTransferItem::Data(nsIPrincipal* aPrincipal, ErrorResult& aRv)
503 : {
504 0 : MOZ_ASSERT(aPrincipal);
505 :
506 0 : nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck();
507 :
508 : // If the inbound principal is system, we can skip the below checks, as
509 : // they will trivially succeed.
510 0 : if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
511 0 : return variant.forget();
512 : }
513 :
514 0 : MOZ_ASSERT(!ChromeOnly(), "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
515 0 : if (ChromeOnly()) {
516 0 : aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
517 0 : return nullptr;
518 : }
519 :
520 0 : bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() ||
521 0 : (mDataTransfer->GetEventMessage() != eDrop &&
522 0 : mDataTransfer->GetEventMessage() != ePaste);
523 :
524 : // Check if the caller is allowed to access the drag data. Callers with
525 : // chrome privileges can always read the data. During the
526 : // drop event, allow retrieving the data except in the case where the
527 : // source of the drag is in a child frame of the caller. In that case,
528 : // we only allow access to data of the same principal. During other events,
529 : // only allow access to the data with the same principal.
530 : //
531 : // We don't want to fail with an exception in this siutation, rather we want
532 : // to just pretend as though the stored data is "nullptr". This is consistent
533 : // with Chrome's behavior and is less surprising for web applications which
534 : // don't expect execptions to be raised when performing certain operations.
535 0 : if (Principal() && checkItemPrincipal &&
536 0 : !aPrincipal->Subsumes(Principal())) {
537 0 : return nullptr;
538 : }
539 :
540 0 : if (!variant) {
541 0 : return nullptr;
542 : }
543 :
544 0 : nsCOMPtr<nsISupports> data;
545 0 : nsresult rv = variant->GetAsISupports(getter_AddRefs(data));
546 0 : if (NS_SUCCEEDED(rv) && data) {
547 0 : nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
548 0 : if (pt) {
549 0 : nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
550 0 : if (NS_WARN_IF(NS_FAILED(rv) || !c)) {
551 0 : return nullptr;
552 : }
553 :
554 0 : nsIGlobalObject* go = c->GetGlobalObject();
555 0 : if (NS_WARN_IF(!go)) {
556 0 : return nullptr;
557 : }
558 :
559 0 : nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
560 0 : MOZ_ASSERT(sp, "This cannot fail on the main thread.");
561 :
562 0 : nsIPrincipal* dataPrincipal = sp->GetPrincipal();
563 0 : if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) {
564 0 : return nullptr;
565 : }
566 : }
567 : }
568 :
569 0 : return variant.forget();
570 : }
571 :
572 : } // namespace dom
573 : } // namespace mozilla
|