DataTransferItem.cpp 16 KB

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