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/clipboard/clipboard.h"
6
7#include <iterator>
8#include <limits>
9
10#include "base/lazy_instance.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/synchronization/lock.h"
14#include "third_party/skia/include/core/SkBitmap.h"
15#include "ui/gfx/size.h"
16
17namespace ui {
18
19namespace {
20
21// Valides a shared bitmap on the clipboard.
22// Returns true if the clipboard data makes sense and it's safe to access the
23// bitmap.
24bool ValidateAndMapSharedBitmap(size_t bitmap_bytes,
25                                base::SharedMemory* bitmap_data) {
26  using base::SharedMemory;
27
28  if (!bitmap_data || !SharedMemory::IsHandleValid(bitmap_data->handle()))
29    return false;
30
31  if (!bitmap_data->Map(bitmap_bytes)) {
32    PLOG(ERROR) << "Failed to map bitmap memory";
33    return false;
34  }
35  return true;
36}
37
38// A list of allowed threads. By default, this is empty and no thread checking
39// is done (in the unit test case), but a user (like content) can set which
40// threads are allowed to call this method.
41typedef std::vector<base::PlatformThreadId> AllowedThreadsVector;
42static base::LazyInstance<AllowedThreadsVector> g_allowed_threads =
43    LAZY_INSTANCE_INITIALIZER;
44
45// Mapping from threads to clipboard objects.
46typedef std::map<base::PlatformThreadId, Clipboard*> ClipboardMap;
47static base::LazyInstance<ClipboardMap> g_clipboard_map =
48    LAZY_INSTANCE_INITIALIZER;
49
50// Mutex that controls access to |g_clipboard_map|.
51static base::LazyInstance<base::Lock>::Leaky
52    g_clipboard_map_lock = LAZY_INSTANCE_INITIALIZER;
53
54}  // namespace
55
56// static
57void Clipboard::SetAllowedThreads(
58    const std::vector<base::PlatformThreadId>& allowed_threads) {
59  base::AutoLock lock(g_clipboard_map_lock.Get());
60
61  g_allowed_threads.Get().clear();
62  std::copy(allowed_threads.begin(), allowed_threads.end(),
63            std::back_inserter(g_allowed_threads.Get()));
64}
65
66// static
67Clipboard* Clipboard::GetForCurrentThread() {
68  base::AutoLock lock(g_clipboard_map_lock.Get());
69
70  base::PlatformThreadId id = base::PlatformThread::CurrentId();
71
72  AllowedThreadsVector* allowed_threads = g_allowed_threads.Pointer();
73  if (!allowed_threads->empty()) {
74    bool found = false;
75    for (AllowedThreadsVector::const_iterator it = allowed_threads->begin();
76         it != allowed_threads->end(); ++it) {
77      if (*it == id) {
78        found = true;
79        break;
80      }
81    }
82
83    DCHECK(found);
84  }
85
86  ClipboardMap* clipboard_map = g_clipboard_map.Pointer();
87  ClipboardMap::iterator it = clipboard_map->find(id);
88  if (it != clipboard_map->end())
89    return it->second;
90
91  Clipboard* clipboard = new ui::Clipboard;
92  clipboard_map->insert(std::make_pair(id, clipboard));
93  return clipboard;
94}
95
96void Clipboard::DestroyClipboardForCurrentThread() {
97  base::AutoLock lock(g_clipboard_map_lock.Get());
98
99  ClipboardMap* clipboard_map = g_clipboard_map.Pointer();
100  base::PlatformThreadId id = base::PlatformThread::CurrentId();
101  ClipboardMap::iterator it = clipboard_map->find(id);
102  if (it != clipboard_map->end()) {
103    delete it->second;
104    clipboard_map->erase(it);
105  }
106}
107
108void Clipboard::DispatchObject(ObjectType type, const ObjectMapParams& params) {
109  // All types apart from CBF_WEBKIT need at least 1 non-empty param.
110  if (type != CBF_WEBKIT && (params.empty() || params[0].empty()))
111    return;
112  // Some other types need a non-empty 2nd param.
113  if ((type == CBF_BOOKMARK || type == CBF_SMBITMAP || type == CBF_DATA) &&
114      (params.size() != 2 || params[1].empty()))
115    return;
116  switch (type) {
117    case CBF_TEXT:
118      WriteText(&(params[0].front()), params[0].size());
119      break;
120
121    case CBF_HTML:
122      if (params.size() == 2) {
123        if (params[1].empty())
124          return;
125        WriteHTML(&(params[0].front()), params[0].size(),
126                  &(params[1].front()), params[1].size());
127      } else if (params.size() == 1) {
128        WriteHTML(&(params[0].front()), params[0].size(), NULL, 0);
129      }
130      break;
131
132    case CBF_RTF:
133      WriteRTF(&(params[0].front()), params[0].size());
134      break;
135
136    case CBF_BOOKMARK:
137      WriteBookmark(&(params[0].front()), params[0].size(),
138                    &(params[1].front()), params[1].size());
139      break;
140
141    case CBF_WEBKIT:
142      WriteWebSmartPaste();
143      break;
144
145    case CBF_SMBITMAP: {
146      using base::SharedMemory;
147      using base::SharedMemoryHandle;
148
149      if (params[0].size() != sizeof(SharedMemory*) ||
150          params[1].size() != sizeof(gfx::Size)) {
151        return;
152      }
153
154      SkBitmap bitmap;
155      const gfx::Size* unvalidated_size =
156          reinterpret_cast<const gfx::Size*>(&params[1].front());
157      // Let Skia do some sanity checking for us (no negative widths/heights, no
158      // overflows while calculating bytes per row, etc).
159      if (!bitmap.setConfig(SkBitmap::kARGB_8888_Config,
160                            unvalidated_size->width(),
161                            unvalidated_size->height())) {
162        return;
163      }
164      // Make sure the size is representable as a signed 32-bit int, so
165      // SkBitmap::getSize() won't be truncated.
166      if (!sk_64_isS32(bitmap.computeSize64()))
167        return;
168
169      // It's OK to cast away constness here since we map the handle as
170      // read-only.
171      const char* raw_bitmap_data_const =
172          reinterpret_cast<const char*>(&params[0].front());
173      char* raw_bitmap_data = const_cast<char*>(raw_bitmap_data_const);
174      scoped_ptr<SharedMemory> bitmap_data(
175          *reinterpret_cast<SharedMemory**>(raw_bitmap_data));
176
177      if (!ValidateAndMapSharedBitmap(bitmap.getSize(), bitmap_data.get()))
178        return;
179      bitmap.setPixels(bitmap_data->memory());
180
181      WriteBitmap(bitmap);
182      break;
183    }
184
185    case CBF_DATA:
186      WriteData(
187          FormatType::Deserialize(
188              std::string(&(params[0].front()), params[0].size())),
189          &(params[1].front()),
190          params[1].size());
191      break;
192
193    default:
194      NOTREACHED();
195  }
196}
197
198// static
199bool Clipboard::ReplaceSharedMemHandle(ObjectMap* objects,
200                                       base::SharedMemoryHandle bitmap_handle,
201                                       base::ProcessHandle process) {
202  using base::SharedMemory;
203  bool has_shared_bitmap = false;
204
205  for (ObjectMap::iterator iter = objects->begin(); iter != objects->end();
206       ++iter) {
207    if (iter->first == CBF_SMBITMAP) {
208      // The code currently only accepts sending a single bitmap over this way.
209      // Fail if we ever encounter more than one shmem bitmap structure to fill.
210      if (has_shared_bitmap)
211        return false;
212
213#if defined(OS_WIN)
214      SharedMemory* bitmap = new SharedMemory(bitmap_handle, true, process);
215#else
216      SharedMemory* bitmap = new SharedMemory(bitmap_handle, true);
217#endif
218
219      // There must always be two parameters associated with each shmem bitmap.
220      if (iter->second.size() != 2)
221        return false;
222
223      // We store the shared memory object pointer so it can be retrieved by the
224      // UI thread (see DispatchObject()).
225      iter->second[0].clear();
226      for (size_t i = 0; i < sizeof(SharedMemory*); ++i)
227        iter->second[0].push_back(reinterpret_cast<char*>(&bitmap)[i]);
228      has_shared_bitmap = true;
229    }
230  }
231  return true;
232}
233
234}  // namespace ui
235