1// Copyright (c) 2013 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/x/selection_utils.h"
6
7#include <set>
8
9#include "base/i18n/icu_string_conversions.h"
10#include "base/logging.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "ui/base/clipboard/clipboard.h"
14#include "ui/base/x/x11_util.h"
15#include "ui/gfx/x/x11_atom_cache.h"
16
17namespace ui {
18
19const char kMimeTypeMozillaURL[] = "text/x-moz-url";
20const char kString[] = "STRING";
21const char kText[] = "TEXT";
22const char kTextPlain[] = "text/plain";
23const char kTextPlainUtf8[] = "text/plain;charset=utf-8";
24const char kUtf8String[] = "UTF8_STRING";
25
26const char* kSelectionDataAtoms[] = {
27  Clipboard::kMimeTypeHTML,
28  kString,
29  kText,
30  kTextPlain,
31  kTextPlainUtf8,
32  kUtf8String,
33  NULL
34};
35
36std::vector< ::Atom> GetTextAtomsFrom(const X11AtomCache* atom_cache) {
37  std::vector< ::Atom> atoms;
38  atoms.push_back(atom_cache->GetAtom(kUtf8String));
39  atoms.push_back(atom_cache->GetAtom(kString));
40  atoms.push_back(atom_cache->GetAtom(kText));
41  atoms.push_back(atom_cache->GetAtom(kTextPlain));
42  atoms.push_back(atom_cache->GetAtom(kTextPlainUtf8));
43  return atoms;
44}
45
46std::vector< ::Atom> GetURLAtomsFrom(const X11AtomCache* atom_cache) {
47  std::vector< ::Atom> atoms;
48  atoms.push_back(atom_cache->GetAtom(Clipboard::kMimeTypeURIList));
49  atoms.push_back(atom_cache->GetAtom(kMimeTypeMozillaURL));
50  return atoms;
51}
52
53std::vector< ::Atom> GetURIListAtomsFrom(const X11AtomCache* atom_cache) {
54  std::vector< ::Atom> atoms;
55  atoms.push_back(atom_cache->GetAtom(Clipboard::kMimeTypeURIList));
56  return atoms;
57}
58
59void GetAtomIntersection(const std::vector< ::Atom>& desired,
60                         const std::vector< ::Atom>& offered,
61                         std::vector< ::Atom>* output) {
62  for (std::vector< ::Atom>::const_iterator it = desired.begin();
63       it != desired.end(); ++it) {
64    std::vector< ::Atom>::const_iterator jt =
65      std::find(offered.begin(), offered.end(), *it);
66    if (jt != offered.end())
67      output->push_back(*it);
68  }
69}
70
71void AddString16ToVector(const base::string16& str,
72                         std::vector<unsigned char>* bytes) {
73  const unsigned char* front =
74      reinterpret_cast<const unsigned char*>(str.data());
75  bytes->insert(bytes->end(), front, front + (str.size() * 2));
76}
77
78std::vector<std::string> ParseURIList(const SelectionData& data) {
79  // uri-lists are newline separated file lists in URL encoding.
80  std::string unparsed;
81  data.AssignTo(&unparsed);
82
83  std::vector<std::string> tokens;
84  Tokenize(unparsed, "\n", &tokens);
85  return tokens;
86}
87
88std::string RefCountedMemoryToString(
89    const scoped_refptr<base::RefCountedMemory>& memory) {
90  if (!memory.get()) {
91    NOTREACHED();
92    return std::string();
93  }
94
95  size_t size = memory->size();
96  if (!size)
97    return std::string();
98
99  const unsigned char* front = memory->front();
100  return std::string(reinterpret_cast<const char*>(front), size);
101}
102
103base::string16 RefCountedMemoryToString16(
104    const scoped_refptr<base::RefCountedMemory>& memory) {
105  if (!memory.get()) {
106    NOTREACHED();
107    return base::string16();
108  }
109
110  size_t size = memory->size();
111  if (!size)
112    return base::string16();
113
114  const unsigned char* front = memory->front();
115  return base::string16(reinterpret_cast<const base::char16*>(front), size / 2);
116}
117
118///////////////////////////////////////////////////////////////////////////////
119
120SelectionFormatMap::SelectionFormatMap() {}
121
122SelectionFormatMap::~SelectionFormatMap() {}
123
124void SelectionFormatMap::Insert(
125    ::Atom atom,
126    const scoped_refptr<base::RefCountedMemory>& item) {
127  data_.erase(atom);
128  data_.insert(std::make_pair(atom, item));
129}
130
131ui::SelectionData SelectionFormatMap::GetFirstOf(
132    const std::vector< ::Atom>& requested_types) const {
133  for (std::vector< ::Atom>::const_iterator it = requested_types.begin();
134       it != requested_types.end(); ++it) {
135    const_iterator data_it = data_.find(*it);
136    if (data_it != data_.end()) {
137      return SelectionData(data_it->first, data_it->second);
138    }
139  }
140
141  return SelectionData();
142}
143
144std::vector< ::Atom> SelectionFormatMap::GetTypes() const {
145  std::vector< ::Atom> atoms;
146  for (const_iterator it = data_.begin(); it != data_.end(); ++it)
147    atoms.push_back(it->first);
148
149  return atoms;
150}
151
152///////////////////////////////////////////////////////////////////////////////
153
154SelectionData::SelectionData()
155    : type_(None),
156      atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms) {
157}
158
159SelectionData::SelectionData(
160    ::Atom type,
161    const scoped_refptr<base::RefCountedMemory>& memory)
162    : type_(type),
163      memory_(memory),
164      atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms) {
165}
166
167SelectionData::SelectionData(const SelectionData& rhs)
168    : type_(rhs.type_),
169      memory_(rhs.memory_),
170      atom_cache_(gfx::GetXDisplay(), kSelectionDataAtoms) {
171}
172
173SelectionData::~SelectionData() {}
174
175SelectionData& SelectionData::operator=(const SelectionData& rhs) {
176  type_ = rhs.type_;
177  memory_ = rhs.memory_;
178  // TODO(erg): In some future where we have to support multiple X Displays,
179  // the following will also need to deal with the display.
180  return *this;
181}
182
183bool SelectionData::IsValid() const {
184  return type_ != None;
185}
186
187::Atom SelectionData::GetType() const {
188  return type_;
189}
190
191const unsigned char* SelectionData::GetData() const {
192  return memory_.get() ? memory_->front() : NULL;
193}
194
195size_t SelectionData::GetSize() const {
196  return memory_.get() ? memory_->size() : 0;
197}
198
199std::string SelectionData::GetText() const {
200  if (type_ == atom_cache_.GetAtom(kUtf8String) ||
201      type_ == atom_cache_.GetAtom(kText) ||
202      type_ == atom_cache_.GetAtom(kTextPlainUtf8)) {
203    return RefCountedMemoryToString(memory_);
204  } else if (type_ == atom_cache_.GetAtom(kString) ||
205             type_ == atom_cache_.GetAtom(kTextPlain)) {
206    std::string result;
207    base::ConvertToUtf8AndNormalize(RefCountedMemoryToString(memory_),
208                                    base::kCodepageLatin1,
209                                    &result);
210    return result;
211  } else {
212    // BTW, I looked at COMPOUND_TEXT, and there's no way we're going to
213    // support that. Yuck.
214    NOTREACHED();
215    return std::string();
216  }
217}
218
219base::string16 SelectionData::GetHtml() const {
220  base::string16 markup;
221
222  if (type_ == atom_cache_.GetAtom(Clipboard::kMimeTypeHTML)) {
223    const unsigned char* data = GetData();
224    size_t size = GetSize();
225
226    // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is
227    // UTF-16, otherwise assume UTF-8.
228    if (size >= 2 &&
229        reinterpret_cast<const uint16_t*>(data)[0] == 0xFEFF) {
230      markup.assign(reinterpret_cast<const uint16_t*>(data) + 1,
231                    (size / 2) - 1);
232    } else {
233      base::UTF8ToUTF16(reinterpret_cast<const char*>(data), size, &markup);
234    }
235
236    // If there is a terminating NULL, drop it.
237    if (!markup.empty() && markup.at(markup.length() - 1) == '\0')
238      markup.resize(markup.length() - 1);
239
240    return markup;
241  } else {
242    NOTREACHED();
243    return markup;
244  }
245}
246
247void SelectionData::AssignTo(std::string* result) const {
248  *result = RefCountedMemoryToString(memory_);
249}
250
251void SelectionData::AssignTo(base::string16* result) const {
252  *result = RefCountedMemoryToString16(memory_);
253}
254
255}  // namespace ui
256