x11_os_exchange_data_provider.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. // Copyright (c) 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ui/base/x/x11_os_exchange_data_provider.h"
  5. #include <utility>
  6. #include "base/containers/contains.h"
  7. #include "base/memory/ref_counted_memory.h"
  8. #include "base/strings/string_split.h"
  9. #include "base/strings/string_util.h"
  10. #include "base/strings/utf_string_conversions.h"
  11. #include "net/base/filename_util.h"
  12. #include "ui/base/clipboard/clipboard_constants.h"
  13. #include "ui/base/clipboard/clipboard_format_type.h"
  14. #include "ui/base/clipboard/file_info.h"
  15. #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
  16. #include "ui/base/x/selection_utils.h"
  17. #include "ui/base/x/x11_drag_drop_client.h"
  18. #include "ui/base/x/x11_util.h"
  19. #include "ui/gfx/x/x11_atom_cache.h"
  20. #include "ui/gfx/x/xproto_util.h"
  21. // Note: the GetBlah() methods are used immediately by the
  22. // web_contents_view_aura.cc:PrepareDropData(), while the omnibox is a
  23. // little more discriminating and calls HasBlah() before trying to get the
  24. // information.
  25. namespace ui {
  26. namespace {
  27. const char kDndSelection[] = "XdndSelection";
  28. const char kRendererTaint[] = "chromium/x-renderer-taint";
  29. const char kFromPrivileged[] = "chromium/from-privileged";
  30. const char kNetscapeURL[] = "_NETSCAPE_URL";
  31. } // namespace
  32. XOSExchangeDataProvider::XOSExchangeDataProvider(
  33. x11::Window x_window,
  34. x11::Window source_window,
  35. const SelectionFormatMap& selection)
  36. : connection_(x11::Connection::Get()),
  37. x_root_window_(ui::GetX11RootWindow()),
  38. own_window_(false),
  39. x_window_(x_window),
  40. source_window_(source_window),
  41. format_map_(selection),
  42. selection_owner_(connection_, x_window_, x11::GetAtom(kDndSelection)) {}
  43. XOSExchangeDataProvider::XOSExchangeDataProvider()
  44. : connection_(x11::Connection::Get()),
  45. x_root_window_(ui::GetX11RootWindow()),
  46. own_window_(true),
  47. x_window_(x11::CreateDummyWindow("Chromium Drag & Drop Window")),
  48. source_window_(x_window_),
  49. selection_owner_(connection_, x_window_, x11::GetAtom(kDndSelection)) {}
  50. XOSExchangeDataProvider::~XOSExchangeDataProvider() {
  51. if (own_window_)
  52. connection_->DestroyWindow({x_window_});
  53. }
  54. void XOSExchangeDataProvider::TakeOwnershipOfSelection() const {
  55. selection_owner_.TakeOwnershipOfSelection(format_map_);
  56. }
  57. void XOSExchangeDataProvider::RetrieveTargets(
  58. std::vector<x11::Atom>* targets) const {
  59. selection_owner_.RetrieveTargets(targets);
  60. }
  61. SelectionFormatMap XOSExchangeDataProvider::GetFormatMap() const {
  62. // We return the |selection_owner_|'s format map instead of our own in case
  63. // ours has been modified since TakeOwnershipOfSelection() was called.
  64. return selection_owner_.selection_format_map();
  65. }
  66. std::unique_ptr<OSExchangeDataProvider> XOSExchangeDataProvider::Clone() const {
  67. std::unique_ptr<XOSExchangeDataProvider> ret(new XOSExchangeDataProvider());
  68. ret->set_format_map(format_map());
  69. return std::move(ret);
  70. }
  71. void XOSExchangeDataProvider::MarkOriginatedFromRenderer() {
  72. std::string empty;
  73. format_map_.Insert(x11::GetAtom(kRendererTaint),
  74. scoped_refptr<base::RefCountedMemory>(
  75. base::RefCountedString::TakeString(&empty)));
  76. }
  77. bool XOSExchangeDataProvider::DidOriginateFromRenderer() const {
  78. return format_map_.find(x11::GetAtom(kRendererTaint)) != format_map_.end();
  79. }
  80. void XOSExchangeDataProvider::MarkAsFromPrivileged() {
  81. std::string empty;
  82. format_map_.Insert(x11::GetAtom(kFromPrivileged),
  83. scoped_refptr<base::RefCountedMemory>(
  84. base::RefCountedString::TakeString(&empty)));
  85. }
  86. bool XOSExchangeDataProvider::IsFromPrivileged() const {
  87. return format_map_.find(x11::GetAtom(kFromPrivileged)) != format_map_.end();
  88. }
  89. void XOSExchangeDataProvider::SetString(const std::u16string& text_data) {
  90. if (HasString())
  91. return;
  92. std::string utf8 = base::UTF16ToUTF8(text_data);
  93. scoped_refptr<base::RefCountedMemory> mem(
  94. base::RefCountedString::TakeString(&utf8));
  95. format_map_.Insert(x11::GetAtom(kMimeTypeText), mem);
  96. format_map_.Insert(x11::GetAtom(kMimeTypeLinuxText), mem);
  97. format_map_.Insert(x11::GetAtom(kMimeTypeLinuxString), mem);
  98. format_map_.Insert(x11::GetAtom(kMimeTypeLinuxUtf8String), mem);
  99. }
  100. void XOSExchangeDataProvider::SetURL(const GURL& url,
  101. const std::u16string& title) {
  102. // TODO(dcheng): The original GTK code tries very hard to avoid writing out an
  103. // empty title. Is this necessary?
  104. if (url.is_valid()) {
  105. // Mozilla's URL format: (UTF16: URL, newline, title)
  106. std::u16string spec = base::UTF8ToUTF16(url.spec());
  107. std::vector<unsigned char> data;
  108. ui::AddString16ToVector(spec, &data);
  109. ui::AddString16ToVector(u"\n", &data);
  110. ui::AddString16ToVector(title, &data);
  111. scoped_refptr<base::RefCountedMemory> mem(
  112. base::RefCountedBytes::TakeVector(&data));
  113. format_map_.Insert(x11::GetAtom(kMimeTypeMozillaURL), mem);
  114. // Set a string fallback as well.
  115. SetString(spec);
  116. // Return early if this drag already contains file contents (this implies
  117. // that file contents must be populated before URLs). Nautilus (and possibly
  118. // other file managers) prefer _NETSCAPE_URL over the X Direct Save
  119. // protocol, but we want to prioritize XDS in this case.
  120. if (!file_contents_name_.empty())
  121. return;
  122. // Set _NETSCAPE_URL for file managers like Nautilus that use it as a hint
  123. // to create a link to the URL. Setting text/uri-list doesn't work because
  124. // Nautilus will fetch and copy the contents of the URL to the drop target
  125. // instead of linking...
  126. // Format is UTF8: URL + "\n" + title.
  127. std::string netscape_url = url.spec();
  128. netscape_url += "\n";
  129. netscape_url += base::UTF16ToUTF8(title);
  130. format_map_.Insert(x11::GetAtom(kNetscapeURL),
  131. scoped_refptr<base::RefCountedMemory>(
  132. base::RefCountedString::TakeString(&netscape_url)));
  133. }
  134. }
  135. void XOSExchangeDataProvider::SetFilename(const base::FilePath& path) {
  136. std::vector<FileInfo> data;
  137. data.emplace_back(path, base::FilePath());
  138. SetFilenames(data);
  139. }
  140. void XOSExchangeDataProvider::SetFilenames(
  141. const std::vector<FileInfo>& filenames) {
  142. std::vector<std::string> paths;
  143. for (const auto& filename : filenames) {
  144. std::string url_spec = net::FilePathToFileURL(filename.path).spec();
  145. if (!url_spec.empty())
  146. paths.push_back(url_spec);
  147. }
  148. std::string joined_data = base::JoinString(paths, "\n");
  149. scoped_refptr<base::RefCountedMemory> mem(
  150. base::RefCountedString::TakeString(&joined_data));
  151. format_map_.Insert(x11::GetAtom(kMimeTypeURIList), mem);
  152. }
  153. void XOSExchangeDataProvider::SetPickledData(const ClipboardFormatType& format,
  154. const base::Pickle& pickle) {
  155. const unsigned char* data =
  156. reinterpret_cast<const unsigned char*>(pickle.data());
  157. std::vector<unsigned char> bytes;
  158. bytes.insert(bytes.end(), data, data + pickle.size());
  159. scoped_refptr<base::RefCountedMemory> mem(
  160. base::RefCountedBytes::TakeVector(&bytes));
  161. format_map_.Insert(x11::GetAtom(format.GetName().c_str()), mem);
  162. }
  163. bool XOSExchangeDataProvider::GetString(std::u16string* result) const {
  164. if (HasFile()) {
  165. // Various Linux file managers both pass a list of file:// URIs and set the
  166. // string representation to the URI. We explicitly don't want to return use
  167. // this representation.
  168. return false;
  169. }
  170. std::vector<x11::Atom> text_atoms = ui::GetTextAtomsFrom();
  171. std::vector<x11::Atom> requested_types;
  172. GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
  173. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  174. if (data.IsValid()) {
  175. std::string text = data.GetText();
  176. *result = base::UTF8ToUTF16(text);
  177. return true;
  178. }
  179. return false;
  180. }
  181. bool XOSExchangeDataProvider::GetURLAndTitle(FilenameToURLPolicy policy,
  182. GURL* url,
  183. std::u16string* title) const {
  184. std::vector<x11::Atom> url_atoms = ui::GetURLAtomsFrom();
  185. std::vector<x11::Atom> requested_types;
  186. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  187. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  188. if (data.IsValid()) {
  189. // TODO(erg): Technically, both of these forms can accept multiple URLs,
  190. // but that doesn't match the assumptions of the rest of the system which
  191. // expect single types.
  192. if (data.GetType() == x11::GetAtom(kMimeTypeMozillaURL)) {
  193. // Mozilla URLs are (UTF16: URL, newline, title).
  194. std::u16string unparsed;
  195. data.AssignTo(&unparsed);
  196. std::vector<std::u16string> tokens = base::SplitString(
  197. unparsed, u"\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  198. if (tokens.size() > 0) {
  199. if (tokens.size() > 1)
  200. *title = tokens[1];
  201. else
  202. *title = std::u16string();
  203. *url = GURL(tokens[0]);
  204. return true;
  205. }
  206. } else if (data.GetType() == x11::GetAtom(kMimeTypeURIList)) {
  207. std::vector<std::string> tokens = ui::ParseURIList(data);
  208. for (const std::string& token : tokens) {
  209. GURL test_url(token);
  210. if (!test_url.SchemeIsFile() ||
  211. policy == FilenameToURLPolicy::CONVERT_FILENAMES) {
  212. *url = test_url;
  213. *title = std::u16string();
  214. return true;
  215. }
  216. }
  217. }
  218. }
  219. return false;
  220. }
  221. bool XOSExchangeDataProvider::GetFilename(base::FilePath* path) const {
  222. std::vector<FileInfo> filenames;
  223. if (GetFilenames(&filenames)) {
  224. *path = filenames.front().path;
  225. return true;
  226. }
  227. return false;
  228. }
  229. bool XOSExchangeDataProvider::GetFilenames(
  230. std::vector<FileInfo>* filenames) const {
  231. std::vector<x11::Atom> url_atoms = ui::GetURIListAtomsFrom();
  232. std::vector<x11::Atom> requested_types;
  233. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  234. filenames->clear();
  235. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  236. if (data.IsValid()) {
  237. std::vector<std::string> tokens = ui::ParseURIList(data);
  238. for (const std::string& token : tokens) {
  239. GURL url(token);
  240. base::FilePath file_path;
  241. if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
  242. filenames->push_back(FileInfo(file_path, base::FilePath()));
  243. }
  244. }
  245. }
  246. return !filenames->empty();
  247. }
  248. bool XOSExchangeDataProvider::GetPickledData(const ClipboardFormatType& format,
  249. base::Pickle* pickle) const {
  250. std::vector<x11::Atom> requested_types;
  251. requested_types.push_back(x11::GetAtom(format.GetName().c_str()));
  252. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  253. if (data.IsValid()) {
  254. // Note that the pickle object on the right hand side of the assignment
  255. // only refers to the bytes in |data|. The assignment copies the data.
  256. *pickle = base::Pickle(reinterpret_cast<const char*>(data.GetData()),
  257. static_cast<int>(data.GetSize()));
  258. return true;
  259. }
  260. return false;
  261. }
  262. bool XOSExchangeDataProvider::HasString() const {
  263. std::vector<x11::Atom> text_atoms = ui::GetTextAtomsFrom();
  264. std::vector<x11::Atom> requested_types;
  265. GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
  266. return !requested_types.empty() && !HasFile();
  267. }
  268. bool XOSExchangeDataProvider::HasURL(FilenameToURLPolicy policy) const {
  269. std::vector<x11::Atom> url_atoms = ui::GetURLAtomsFrom();
  270. std::vector<x11::Atom> requested_types;
  271. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  272. if (requested_types.empty())
  273. return false;
  274. // The Linux desktop doesn't differentiate between files and URLs like
  275. // Windows does and stuffs all the data into one mime type.
  276. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  277. if (data.IsValid()) {
  278. if (data.GetType() == x11::GetAtom(kMimeTypeMozillaURL)) {
  279. // File managers shouldn't be using this type, so this is a URL.
  280. return true;
  281. } else if (data.GetType() == x11::GetAtom(ui::kMimeTypeURIList)) {
  282. std::vector<std::string> tokens = ui::ParseURIList(data);
  283. for (const std::string& token : tokens) {
  284. if (!GURL(token).SchemeIsFile() ||
  285. policy == FilenameToURLPolicy::CONVERT_FILENAMES)
  286. return true;
  287. }
  288. return false;
  289. }
  290. }
  291. return false;
  292. }
  293. bool XOSExchangeDataProvider::HasFile() const {
  294. std::vector<x11::Atom> url_atoms = ui::GetURIListAtomsFrom();
  295. std::vector<x11::Atom> requested_types;
  296. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  297. if (requested_types.empty())
  298. return false;
  299. // To actually answer whether we have a file, we need to look through the
  300. // contents of the kMimeTypeURIList type, and see if any of them are file://
  301. // URIs.
  302. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  303. if (data.IsValid()) {
  304. std::vector<std::string> tokens = ui::ParseURIList(data);
  305. for (const std::string& token : tokens) {
  306. GURL url(token);
  307. base::FilePath file_path;
  308. if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
  309. return true;
  310. }
  311. }
  312. return false;
  313. }
  314. bool XOSExchangeDataProvider::HasCustomFormat(
  315. const ClipboardFormatType& format) const {
  316. std::vector<x11::Atom> url_atoms;
  317. url_atoms.push_back(x11::GetAtom(format.GetName().c_str()));
  318. std::vector<x11::Atom> requested_types;
  319. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  320. return !requested_types.empty();
  321. }
  322. void XOSExchangeDataProvider::SetFileContents(
  323. const base::FilePath& filename,
  324. const std::string& file_contents) {
  325. DCHECK(!filename.empty());
  326. DCHECK(!base::Contains(format_map(), x11::GetAtom(kMimeTypeMozillaURL)));
  327. set_file_contents_name(filename);
  328. // Direct save handling is a complicated juggling affair between this class,
  329. // SelectionFormat, and XDragDropClient. The general idea behind
  330. // the protocol is this:
  331. // - The source window sets its XdndDirectSave0 window property to the
  332. // proposed filename.
  333. // - When a target window receives the drop, it updates the XdndDirectSave0
  334. // property on the source window to the filename it would like the contents
  335. // to be saved to and then requests the XdndDirectSave0 type from the
  336. // source.
  337. // - The source is supposed to copy the file here and return success (S),
  338. // failure (F), or error (E).
  339. // - In this case, failure means the destination should try to populate the
  340. // file itself by copying the data from application/octet-stream. To make
  341. // things simpler for Chrome, we always 'fail' and let the destination do
  342. // the work.
  343. std::string failure("F");
  344. InsertData(x11::GetAtom(kXdndDirectSave0),
  345. scoped_refptr<base::RefCountedMemory>(
  346. base::RefCountedString::TakeString(&failure)));
  347. std::string file_contents_copy = file_contents;
  348. InsertData(x11::GetAtom(kMimeTypeOctetStream),
  349. scoped_refptr<base::RefCountedMemory>(
  350. base::RefCountedString::TakeString(&file_contents_copy)));
  351. }
  352. bool XOSExchangeDataProvider::GetFileContents(
  353. base::FilePath* filename,
  354. std::string* file_contents) const {
  355. std::vector<char> str;
  356. if (!GetArrayProperty(source_window_, x11::GetAtom(kXdndDirectSave0), &str))
  357. return false;
  358. *filename =
  359. base::FilePath(base::FilePath::StringPieceType(str.data(), str.size()));
  360. std::vector<x11::Atom> file_contents_atoms;
  361. file_contents_atoms.push_back(x11::GetAtom(kMimeTypeOctetStream));
  362. std::vector<x11::Atom> requested_types;
  363. GetAtomIntersection(file_contents_atoms, GetTargets(), &requested_types);
  364. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  365. if (data.IsValid()) {
  366. data.AssignTo(file_contents);
  367. return true;
  368. }
  369. return false;
  370. }
  371. bool XOSExchangeDataProvider::HasFileContents() const {
  372. NOTIMPLEMENTED();
  373. return false;
  374. }
  375. void XOSExchangeDataProvider::SetHtml(const std::u16string& html,
  376. const GURL& base_url) {
  377. std::vector<unsigned char> bytes;
  378. // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
  379. // assume UTF-8.
  380. bytes.push_back(0xFF);
  381. bytes.push_back(0xFE);
  382. ui::AddString16ToVector(html, &bytes);
  383. scoped_refptr<base::RefCountedMemory> mem(
  384. base::RefCountedBytes::TakeVector(&bytes));
  385. format_map_.Insert(x11::GetAtom(kMimeTypeHTML), mem);
  386. }
  387. bool XOSExchangeDataProvider::GetHtml(std::u16string* html,
  388. GURL* base_url) const {
  389. std::vector<x11::Atom> url_atoms;
  390. url_atoms.push_back(x11::GetAtom(kMimeTypeHTML));
  391. std::vector<x11::Atom> requested_types;
  392. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  393. ui::SelectionData data(format_map_.GetFirstOf(requested_types));
  394. if (data.IsValid()) {
  395. *html = data.GetHtml();
  396. *base_url = GURL();
  397. return true;
  398. }
  399. return false;
  400. }
  401. bool XOSExchangeDataProvider::HasHtml() const {
  402. std::vector<x11::Atom> url_atoms;
  403. url_atoms.push_back(x11::GetAtom(kMimeTypeHTML));
  404. std::vector<x11::Atom> requested_types;
  405. GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
  406. return !requested_types.empty();
  407. }
  408. void XOSExchangeDataProvider::SetDragImage(const gfx::ImageSkia& image,
  409. const gfx::Vector2d& cursor_offset) {
  410. drag_image_ = image;
  411. drag_image_offset_ = cursor_offset;
  412. }
  413. gfx::ImageSkia XOSExchangeDataProvider::GetDragImage() const {
  414. return drag_image_;
  415. }
  416. gfx::Vector2d XOSExchangeDataProvider::GetDragImageOffset() const {
  417. return drag_image_offset_;
  418. }
  419. bool XOSExchangeDataProvider::GetPlainTextURL(GURL* url) const {
  420. std::u16string text;
  421. if (GetString(&text)) {
  422. GURL test_url(text);
  423. if (test_url.is_valid()) {
  424. *url = test_url;
  425. return true;
  426. }
  427. }
  428. return false;
  429. }
  430. std::vector<x11::Atom> XOSExchangeDataProvider::GetTargets() const {
  431. return format_map_.GetTypes();
  432. }
  433. void XOSExchangeDataProvider::InsertData(
  434. x11::Atom format,
  435. const scoped_refptr<base::RefCountedMemory>& data) {
  436. format_map_.Insert(format, data);
  437. }
  438. void XOSExchangeDataProvider::SetSource(
  439. std::unique_ptr<DataTransferEndpoint> data_source) {}
  440. DataTransferEndpoint* XOSExchangeDataProvider::GetSource() const {
  441. return nullptr;
  442. }
  443. } // namespace ui