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