1// Copyright (c) 2012 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
5#include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
6
7#include "base/logging.h"
8#include "base/memory/ref_counted_memory.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.h"
13#include "ui/base/clipboard/scoped_clipboard_writer.h"
14#include "ui/base/dragdrop/file_info.h"
15#include "ui/base/x/selection_utils.h"
16#include "ui/base/x/x11_util.h"
17#include "ui/events/platform/platform_event_source.h"
18
19// Note: the GetBlah() methods are used immediately by the
20// web_contents_view_aura.cc:PrepareDropData(), while the omnibox is a
21// little more discriminating and calls HasBlah() before trying to get the
22// information.
23
24namespace ui {
25
26namespace {
27
28const char kDndSelection[] = "XdndSelection";
29const char kRendererTaint[] = "chromium/x-renderer-taint";
30
31const char kNetscapeURL[] = "_NETSCAPE_URL";
32
33const char* kAtomsToCache[] = {
34  kString,
35  kText,
36  kUtf8String,
37  kDndSelection,
38  Clipboard::kMimeTypeURIList,
39  kMimeTypeMozillaURL,
40  kNetscapeURL,
41  Clipboard::kMimeTypeText,
42  kRendererTaint,
43  NULL
44};
45
46}  // namespace
47
48OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11(
49    ::Window x_window,
50    const SelectionFormatMap& selection)
51    : x_display_(gfx::GetXDisplay()),
52      x_root_window_(DefaultRootWindow(x_display_)),
53      own_window_(false),
54      x_window_(x_window),
55      atom_cache_(x_display_, kAtomsToCache),
56      format_map_(selection),
57      selection_owner_(x_display_, x_window_,
58                       atom_cache_.GetAtom(kDndSelection)) {
59  // We don't know all possible MIME types at compile time.
60  atom_cache_.allow_uncached_atoms();
61}
62
63OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11()
64    : x_display_(gfx::GetXDisplay()),
65      x_root_window_(DefaultRootWindow(x_display_)),
66      own_window_(true),
67      x_window_(XCreateWindow(
68          x_display_,
69          x_root_window_,
70          -100, -100, 10, 10,  // x, y, width, height
71          0,                   // border width
72          CopyFromParent,      // depth
73          InputOnly,
74          CopyFromParent,      // visual
75          0,
76          NULL)),
77      atom_cache_(x_display_, kAtomsToCache),
78      format_map_(),
79      selection_owner_(x_display_, x_window_,
80                       atom_cache_.GetAtom(kDndSelection)) {
81  // We don't know all possible MIME types at compile time.
82  atom_cache_.allow_uncached_atoms();
83
84  XStoreName(x_display_, x_window_, "Chromium Drag & Drop Window");
85
86  PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
87}
88
89OSExchangeDataProviderAuraX11::~OSExchangeDataProviderAuraX11() {
90  if (own_window_) {
91    PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
92    XDestroyWindow(x_display_, x_window_);
93  }
94}
95
96void OSExchangeDataProviderAuraX11::TakeOwnershipOfSelection() const {
97  selection_owner_.TakeOwnershipOfSelection(format_map_);
98}
99
100void OSExchangeDataProviderAuraX11::RetrieveTargets(
101    std::vector<Atom>* targets) const {
102  selection_owner_.RetrieveTargets(targets);
103}
104
105SelectionFormatMap OSExchangeDataProviderAuraX11::GetFormatMap() const {
106  // We return the |selection_owner_|'s format map instead of our own in case
107  // ours has been modified since TakeOwnershipOfSelection() was called.
108  return selection_owner_.selection_format_map();
109}
110
111OSExchangeData::Provider* OSExchangeDataProviderAuraX11::Clone() const {
112  OSExchangeDataProviderAuraX11* ret = new OSExchangeDataProviderAuraX11();
113  ret->format_map_ = format_map_;
114  return ret;
115}
116
117void OSExchangeDataProviderAuraX11::MarkOriginatedFromRenderer() {
118  std::string empty;
119  format_map_.Insert(atom_cache_.GetAtom(kRendererTaint),
120                     scoped_refptr<base::RefCountedMemory>(
121                         base::RefCountedString::TakeString(&empty)));
122}
123
124bool OSExchangeDataProviderAuraX11::DidOriginateFromRenderer() const {
125  return format_map_.find(atom_cache_.GetAtom(kRendererTaint)) !=
126         format_map_.end();
127}
128
129void OSExchangeDataProviderAuraX11::SetString(const base::string16& text_data) {
130  if (HasString())
131    return;
132
133  std::string utf8 = base::UTF16ToUTF8(text_data);
134  scoped_refptr<base::RefCountedMemory> mem(
135      base::RefCountedString::TakeString(&utf8));
136
137  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeText), mem);
138  format_map_.Insert(atom_cache_.GetAtom(kText), mem);
139  format_map_.Insert(atom_cache_.GetAtom(kString), mem);
140  format_map_.Insert(atom_cache_.GetAtom(kUtf8String), mem);
141}
142
143void OSExchangeDataProviderAuraX11::SetURL(const GURL& url,
144                                           const base::string16& title) {
145  // TODO(dcheng): The original GTK code tries very hard to avoid writing out an
146  // empty title. Is this necessary?
147  if (url.is_valid()) {
148    // Mozilla's URL format: (UTF16: URL, newline, title)
149    base::string16 spec = base::UTF8ToUTF16(url.spec());
150
151    std::vector<unsigned char> data;
152    ui::AddString16ToVector(spec, &data);
153    ui::AddString16ToVector(base::ASCIIToUTF16("\n"), &data);
154    ui::AddString16ToVector(title, &data);
155    scoped_refptr<base::RefCountedMemory> mem(
156        base::RefCountedBytes::TakeVector(&data));
157
158    format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem);
159
160    // Set a string fallback as well.
161    SetString(spec);
162
163    // Return early if this drag already contains file contents (this implies
164    // that file contents must be populated before URLs). Nautilus (and possibly
165    // other file managers) prefer _NETSCAPE_URL over the X Direct Save
166    // protocol, but we want to prioritize XDS in this case.
167    if (!file_contents_name_.empty())
168      return;
169
170    // Set _NETSCAPE_URL for file managers like Nautilus that use it as a hint
171    // to create a link to the URL. Setting text/uri-list doesn't work because
172    // Nautilus will fetch and copy the contents of the URL to the drop target
173    // instead of linking...
174    // Format is UTF8: URL + "\n" + title.
175    std::string netscape_url = url.spec();
176    netscape_url += "\n";
177    netscape_url += base::UTF16ToUTF8(title);
178    format_map_.Insert(atom_cache_.GetAtom(kNetscapeURL),
179                       scoped_refptr<base::RefCountedMemory>(
180                           base::RefCountedString::TakeString(&netscape_url)));
181  }
182}
183
184void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) {
185  std::vector<FileInfo> data;
186  data.push_back(FileInfo(path, base::FilePath()));
187  SetFilenames(data);
188}
189
190void OSExchangeDataProviderAuraX11::SetFilenames(
191    const std::vector<FileInfo>& filenames) {
192  std::vector<std::string> paths;
193  for (std::vector<FileInfo>::const_iterator it = filenames.begin();
194       it != filenames.end();
195       ++it) {
196    std::string url_spec = net::FilePathToFileURL(it->path).spec();
197    if (!url_spec.empty())
198      paths.push_back(url_spec);
199  }
200
201  std::string joined_data = JoinString(paths, '\n');
202  scoped_refptr<base::RefCountedMemory> mem(
203      base::RefCountedString::TakeString(&joined_data));
204  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeURIList), mem);
205}
206
207void OSExchangeDataProviderAuraX11::SetPickledData(
208    const OSExchangeData::CustomFormat& format,
209    const Pickle& pickle) {
210  const unsigned char* data =
211      reinterpret_cast<const unsigned char*>(pickle.data());
212
213  std::vector<unsigned char> bytes;
214  bytes.insert(bytes.end(), data, data + pickle.size());
215  scoped_refptr<base::RefCountedMemory> mem(
216      base::RefCountedBytes::TakeVector(&bytes));
217
218  format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem);
219}
220
221bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const {
222  if (HasFile()) {
223    // Various Linux file managers both pass a list of file:// URIs and set the
224    // string representation to the URI. We explicitly don't want to return use
225    // this representation.
226    return false;
227  }
228
229  std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
230  std::vector< ::Atom> requested_types;
231  ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
232
233  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
234  if (data.IsValid()) {
235    std::string text = data.GetText();
236    *result = base::UTF8ToUTF16(text);
237    return true;
238  }
239
240  return false;
241}
242
243bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
244    OSExchangeData::FilenameToURLPolicy policy,
245    GURL* url,
246    base::string16* title) const {
247  std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
248  std::vector< ::Atom> requested_types;
249  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
250
251  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
252  if (data.IsValid()) {
253    // TODO(erg): Technically, both of these forms can accept multiple URLs,
254    // but that doesn't match the assumptions of the rest of the system which
255    // expect single types.
256
257    if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
258      // Mozilla URLs are (UTF16: URL, newline, title).
259      base::string16 unparsed;
260      data.AssignTo(&unparsed);
261
262      std::vector<base::string16> tokens;
263      size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens);
264      if (num_tokens > 0) {
265        if (num_tokens > 1)
266          *title = tokens[1];
267        else
268          *title = base::string16();
269
270        *url = GURL(tokens[0]);
271        return true;
272      }
273    } else if (data.GetType() == atom_cache_.GetAtom(
274                   Clipboard::kMimeTypeURIList)) {
275      std::vector<std::string> tokens = ui::ParseURIList(data);
276      for (std::vector<std::string>::const_iterator it = tokens.begin();
277           it != tokens.end(); ++it) {
278        GURL test_url(*it);
279        if (!test_url.SchemeIsFile() ||
280            policy == OSExchangeData::CONVERT_FILENAMES) {
281          *url = test_url;
282          *title = base::string16();
283          return true;
284        }
285      }
286    }
287  }
288
289  return false;
290}
291
292bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const {
293  std::vector<FileInfo> filenames;
294  if (GetFilenames(&filenames)) {
295    *path = filenames.front().path;
296    return true;
297  }
298
299  return false;
300}
301
302bool OSExchangeDataProviderAuraX11::GetFilenames(
303    std::vector<FileInfo>* filenames) const {
304  std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
305  std::vector< ::Atom> requested_types;
306  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
307
308  filenames->clear();
309  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
310  if (data.IsValid()) {
311    std::vector<std::string> tokens = ui::ParseURIList(data);
312    for (std::vector<std::string>::const_iterator it = tokens.begin();
313         it != tokens.end(); ++it) {
314      GURL url(*it);
315      base::FilePath file_path;
316      if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
317        filenames->push_back(FileInfo(file_path, base::FilePath()));
318      }
319    }
320  }
321
322  return !filenames->empty();
323}
324
325bool OSExchangeDataProviderAuraX11::GetPickledData(
326    const OSExchangeData::CustomFormat& format,
327    Pickle* pickle) const {
328  std::vector< ::Atom> requested_types;
329  requested_types.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
330
331  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
332  if (data.IsValid()) {
333    // Note that the pickle object on the right hand side of the assignment
334    // only refers to the bytes in |data|. The assignment copies the data.
335    *pickle = Pickle(reinterpret_cast<const char*>(data.GetData()),
336                     static_cast<int>(data.GetSize()));
337    return true;
338  }
339
340  return false;
341}
342
343bool OSExchangeDataProviderAuraX11::HasString() const {
344  std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
345  std::vector< ::Atom> requested_types;
346  ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
347  return !requested_types.empty() && !HasFile();
348}
349
350bool OSExchangeDataProviderAuraX11::HasURL(
351    OSExchangeData::FilenameToURLPolicy policy) const {
352  std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
353  std::vector< ::Atom> requested_types;
354  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
355
356  if (requested_types.empty())
357    return false;
358
359  // The Linux desktop doesn't differentiate between files and URLs like
360  // Windows does and stuffs all the data into one mime type.
361  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
362  if (data.IsValid()) {
363    if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
364      // File managers shouldn't be using this type, so this is a URL.
365      return true;
366    } else if (data.GetType() == atom_cache_.GetAtom(
367        ui::Clipboard::kMimeTypeURIList)) {
368      std::vector<std::string> tokens = ui::ParseURIList(data);
369      for (std::vector<std::string>::const_iterator it = tokens.begin();
370           it != tokens.end(); ++it) {
371        if (!GURL(*it).SchemeIsFile() ||
372            policy == OSExchangeData::CONVERT_FILENAMES)
373          return true;
374      }
375
376      return false;
377    }
378  }
379
380  return false;
381}
382
383bool OSExchangeDataProviderAuraX11::HasFile() const {
384  std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
385  std::vector< ::Atom> requested_types;
386  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
387
388  if (requested_types.empty())
389    return false;
390
391  // To actually answer whether we have a file, we need to look through the
392  // contents of the kMimeTypeURIList type, and see if any of them are file://
393  // URIs.
394  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
395  if (data.IsValid()) {
396    std::vector<std::string> tokens = ui::ParseURIList(data);
397    for (std::vector<std::string>::const_iterator it = tokens.begin();
398         it != tokens.end(); ++it) {
399      GURL url(*it);
400      base::FilePath file_path;
401      if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
402        return true;
403    }
404  }
405
406  return false;
407}
408
409bool OSExchangeDataProviderAuraX11::HasCustomFormat(
410    const OSExchangeData::CustomFormat& format) const {
411  std::vector< ::Atom> url_atoms;
412  url_atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
413  std::vector< ::Atom> requested_types;
414  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
415
416  return !requested_types.empty();
417}
418
419void OSExchangeDataProviderAuraX11::SetFileContents(
420    const base::FilePath& filename,
421    const std::string& file_contents) {
422  DCHECK(!filename.empty());
423  DCHECK(format_map_.end() ==
424         format_map_.find(atom_cache_.GetAtom(kMimeTypeMozillaURL)));
425
426  file_contents_name_ = filename;
427
428  // Direct save handling is a complicated juggling affair between this class,
429  // SelectionFormat, and DesktopDragDropClientAuraX11. The general idea behind
430  // the protocol is this:
431  // - The source window sets its XdndDirectSave0 window property to the
432  //   proposed filename.
433  // - When a target window receives the drop, it updates the XdndDirectSave0
434  //   property on the source window to the filename it would like the contents
435  //   to be saved to and then requests the XdndDirectSave0 type from the
436  //   source.
437  // - The source is supposed to copy the file here and return success (S),
438  //   failure (F), or error (E).
439  // - In this case, failure means the destination should try to populate the
440  //   file itself by copying the data from application/octet-stream. To make
441  //   things simpler for Chrome, we always 'fail' and let the destination do
442  //   the work.
443  std::string failure("F");
444  format_map_.Insert(
445      atom_cache_.GetAtom("XdndDirectSave0"),
446                          scoped_refptr<base::RefCountedMemory>(
447                              base::RefCountedString::TakeString(&failure)));
448  std::string file_contents_copy = file_contents;
449  format_map_.Insert(
450      atom_cache_.GetAtom("application/octet-stream"),
451      scoped_refptr<base::RefCountedMemory>(
452          base::RefCountedString::TakeString(&file_contents_copy)));
453}
454
455void OSExchangeDataProviderAuraX11::SetHtml(const base::string16& html,
456                                            const GURL& base_url) {
457  std::vector<unsigned char> bytes;
458  // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
459  // assume UTF-8.
460  bytes.push_back(0xFF);
461  bytes.push_back(0xFE);
462  ui::AddString16ToVector(html, &bytes);
463  scoped_refptr<base::RefCountedMemory> mem(
464      base::RefCountedBytes::TakeVector(&bytes));
465
466  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem);
467}
468
469bool OSExchangeDataProviderAuraX11::GetHtml(base::string16* html,
470                                            GURL* base_url) const {
471  std::vector< ::Atom> url_atoms;
472  url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
473  std::vector< ::Atom> requested_types;
474  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
475
476  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
477  if (data.IsValid()) {
478    *html = data.GetHtml();
479    *base_url = GURL();
480    return true;
481  }
482
483  return false;
484}
485
486bool OSExchangeDataProviderAuraX11::HasHtml() const {
487  std::vector< ::Atom> url_atoms;
488  url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
489  std::vector< ::Atom> requested_types;
490  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
491
492  return !requested_types.empty();
493}
494
495void OSExchangeDataProviderAuraX11::SetDragImage(
496    const gfx::ImageSkia& image,
497    const gfx::Vector2d& cursor_offset) {
498  drag_image_ = image;
499  drag_image_offset_ = cursor_offset;
500}
501
502const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const {
503  return drag_image_;
504}
505
506const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
507  return drag_image_offset_;
508}
509
510bool OSExchangeDataProviderAuraX11::CanDispatchEvent(
511    const PlatformEvent& event) {
512  return event->xany.window == x_window_;
513}
514
515uint32_t OSExchangeDataProviderAuraX11::DispatchEvent(
516    const PlatformEvent& event) {
517  XEvent* xev = event;
518  switch (xev->type) {
519    case SelectionRequest:
520      selection_owner_.OnSelectionRequest(*xev);
521      return ui::POST_DISPATCH_STOP_PROPAGATION;
522    default:
523      NOTIMPLEMENTED();
524  }
525  return ui::POST_DISPATCH_NONE;
526}
527
528bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const {
529  base::string16 text;
530  if (GetString(&text)) {
531    GURL test_url(text);
532    if (test_url.is_valid()) {
533      *url = test_url;
534      return true;
535    }
536  }
537
538  return false;
539}
540
541std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const {
542  return format_map_.GetTypes();
543}
544
545///////////////////////////////////////////////////////////////////////////////
546// OSExchangeData, public:
547
548// static
549OSExchangeData::Provider* OSExchangeData::CreateProvider() {
550  return new OSExchangeDataProviderAuraX11();
551}
552
553}  // namespace ui
554