Add ICU message format support
[chromium-blink-merge.git] / ui / base / clipboard / clipboard_util_win.cc
blob91d58a0a4ce1f3afce3d73044bb6206ab0b04741
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.
5 #include "ui/base/clipboard/clipboard_util_win.h"
7 #include <shellapi.h>
8 #include <shlwapi.h>
9 #include <wininet.h> // For INTERNET_MAX_URL_LENGTH.
11 #include "base/basictypes.h"
12 #include "base/files/file_path.h"
13 #include "base/logging.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/win/scoped_hglobal.h"
19 #include "net/base/filename_util.h"
20 #include "ui/base/clipboard/clipboard.h"
21 #include "ui/base/clipboard/custom_data_helper.h"
22 #include "url/gurl.h"
24 namespace ui {
26 namespace {
28 bool HasData(IDataObject* data_object, const Clipboard::FormatType& format) {
29 FORMATETC format_etc = format.ToFormatEtc();
30 return SUCCEEDED(data_object->QueryGetData(&format_etc));
33 bool GetData(IDataObject* data_object,
34 const Clipboard::FormatType& format,
35 STGMEDIUM* medium) {
36 FORMATETC format_etc = format.ToFormatEtc();
37 return SUCCEEDED(data_object->GetData(&format_etc, medium));
40 bool GetUrlFromHDrop(IDataObject* data_object,
41 GURL* url,
42 base::string16* title) {
43 DCHECK(data_object && url && title);
45 bool success = false;
46 STGMEDIUM medium;
47 if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium))
48 return false;
51 base::win::ScopedHGlobal<HDROP> hdrop(medium.hGlobal);
53 if (!hdrop.get())
54 return false;
56 wchar_t filename[MAX_PATH];
57 if (DragQueryFileW(hdrop.get(), 0, filename, arraysize(filename))) {
58 wchar_t url_buffer[INTERNET_MAX_URL_LENGTH];
59 if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") &&
60 GetPrivateProfileStringW(L"InternetShortcut",
61 L"url",
63 url_buffer,
64 arraysize(url_buffer),
65 filename)) {
66 *url = GURL(url_buffer);
67 PathRemoveExtension(filename);
68 title->assign(PathFindFileName(filename));
69 success = url->is_valid();
74 ReleaseStgMedium(&medium);
75 return success;
78 void SplitUrlAndTitle(const base::string16& str,
79 GURL* url,
80 base::string16* title) {
81 DCHECK(url && title);
82 size_t newline_pos = str.find('\n');
83 if (newline_pos != base::string16::npos) {
84 *url = GURL(base::string16(str, 0, newline_pos));
85 title->assign(str, newline_pos + 1, base::string16::npos);
86 } else {
87 *url = GURL(str);
88 title->assign(str);
92 } // namespace
94 bool ClipboardUtil::HasUrl(IDataObject* data_object, bool convert_filenames) {
95 DCHECK(data_object);
96 return HasData(data_object, Clipboard::GetMozUrlFormatType()) ||
97 HasData(data_object, Clipboard::GetUrlWFormatType()) ||
98 HasData(data_object, Clipboard::GetUrlFormatType()) ||
99 (convert_filenames && HasFilenames(data_object));
102 bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
103 DCHECK(data_object);
104 return HasData(data_object, Clipboard::GetCFHDropFormatType()) ||
105 HasData(data_object, Clipboard::GetFilenameWFormatType()) ||
106 HasData(data_object, Clipboard::GetFilenameFormatType());
109 bool ClipboardUtil::HasFileContents(IDataObject* data_object) {
110 DCHECK(data_object);
111 return HasData(data_object, Clipboard::GetFileContentZeroFormatType());
114 bool ClipboardUtil::HasHtml(IDataObject* data_object) {
115 DCHECK(data_object);
116 return HasData(data_object, Clipboard::GetHtmlFormatType()) ||
117 HasData(data_object, Clipboard::GetTextHtmlFormatType());
120 bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
121 DCHECK(data_object);
122 return HasData(data_object, Clipboard::GetPlainTextWFormatType()) ||
123 HasData(data_object, Clipboard::GetPlainTextFormatType());
126 bool ClipboardUtil::GetUrl(IDataObject* data_object,
127 GURL* url,
128 base::string16* title,
129 bool convert_filenames) {
130 DCHECK(data_object && url && title);
131 if (!HasUrl(data_object, convert_filenames))
132 return false;
134 // Try to extract a URL from |data_object| in a variety of formats.
135 STGMEDIUM store;
136 if (GetUrlFromHDrop(data_object, url, title))
137 return true;
139 if (GetData(data_object, Clipboard::GetMozUrlFormatType(), &store) ||
140 GetData(data_object, Clipboard::GetUrlWFormatType(), &store)) {
142 // Mozilla URL format or unicode URL
143 base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
144 SplitUrlAndTitle(data.get(), url, title);
146 ReleaseStgMedium(&store);
147 return url->is_valid();
150 if (GetData(data_object, Clipboard::GetUrlFormatType(), &store)) {
152 // URL using ascii
153 base::win::ScopedHGlobal<char*> data(store.hGlobal);
154 SplitUrlAndTitle(base::UTF8ToWide(data.get()), url, title);
156 ReleaseStgMedium(&store);
157 return url->is_valid();
160 if (convert_filenames) {
161 std::vector<base::string16> filenames;
162 if (!GetFilenames(data_object, &filenames))
163 return false;
164 DCHECK_GT(filenames.size(), 0U);
165 *url = net::FilePathToFileURL(base::FilePath(filenames[0]));
166 return url->is_valid();
169 return false;
172 bool ClipboardUtil::GetFilenames(IDataObject* data_object,
173 std::vector<base::string16>* filenames) {
174 DCHECK(data_object && filenames);
175 if (!HasFilenames(data_object))
176 return false;
178 STGMEDIUM medium;
179 if (GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium)) {
181 base::win::ScopedHGlobal<HDROP> hdrop(medium.hGlobal);
182 if (!hdrop.get())
183 return false;
185 const int kMaxFilenameLen = 4096;
186 const unsigned num_files = DragQueryFileW(hdrop.get(), 0xffffffff, 0, 0);
187 for (unsigned int i = 0; i < num_files; ++i) {
188 wchar_t filename[kMaxFilenameLen];
189 if (!DragQueryFileW(hdrop.get(), i, filename, kMaxFilenameLen))
190 continue;
191 filenames->push_back(filename);
194 ReleaseStgMedium(&medium);
195 return true;
198 if (GetData(data_object, Clipboard::GetFilenameWFormatType(), &medium)) {
200 // filename using unicode
201 base::win::ScopedHGlobal<wchar_t*> data(medium.hGlobal);
202 if (data.get() && data.get()[0])
203 filenames->push_back(data.get());
205 ReleaseStgMedium(&medium);
206 return true;
209 if (GetData(data_object, Clipboard::GetFilenameFormatType(), &medium)) {
211 // filename using ascii
212 base::win::ScopedHGlobal<char*> data(medium.hGlobal);
213 if (data.get() && data.get()[0])
214 filenames->push_back(base::SysNativeMBToWide(data.get()));
216 ReleaseStgMedium(&medium);
217 return true;
220 return false;
223 bool ClipboardUtil::GetPlainText(IDataObject* data_object,
224 base::string16* plain_text) {
225 DCHECK(data_object && plain_text);
226 if (!HasPlainText(data_object))
227 return false;
229 STGMEDIUM store;
230 if (GetData(data_object, Clipboard::GetPlainTextWFormatType(), &store)) {
232 // Unicode text
233 base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
234 plain_text->assign(data.get());
236 ReleaseStgMedium(&store);
237 return true;
240 if (GetData(data_object, Clipboard::GetPlainTextFormatType(), &store)) {
242 // ascii text
243 base::win::ScopedHGlobal<char*> data(store.hGlobal);
244 plain_text->assign(base::UTF8ToWide(data.get()));
246 ReleaseStgMedium(&store);
247 return true;
250 // If a file is dropped on the window, it does not provide either of the
251 // plain text formats, so here we try to forcibly get a url.
252 GURL url;
253 base::string16 title;
254 if (GetUrl(data_object, &url, &title, false)) {
255 *plain_text = base::UTF8ToUTF16(url.spec());
256 return true;
258 return false;
261 bool ClipboardUtil::GetHtml(IDataObject* data_object,
262 base::string16* html, std::string* base_url) {
263 DCHECK(data_object && html && base_url);
265 STGMEDIUM store;
266 if (HasData(data_object, Clipboard::GetHtmlFormatType()) &&
267 GetData(data_object, Clipboard::GetHtmlFormatType(), &store)) {
269 // MS CF html
270 base::win::ScopedHGlobal<char*> data(store.hGlobal);
272 std::string html_utf8;
273 CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url);
274 html->assign(base::UTF8ToWide(html_utf8));
276 ReleaseStgMedium(&store);
277 return true;
280 if (!HasData(data_object, Clipboard::GetTextHtmlFormatType()))
281 return false;
283 if (!GetData(data_object, Clipboard::GetTextHtmlFormatType(), &store))
284 return false;
287 // text/html
288 base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
289 html->assign(data.get());
291 ReleaseStgMedium(&store);
292 return true;
295 bool ClipboardUtil::GetFileContents(IDataObject* data_object,
296 base::string16* filename, std::string* file_contents) {
297 DCHECK(data_object && filename && file_contents);
298 if (!HasData(data_object, Clipboard::GetFileContentZeroFormatType()) &&
299 !HasData(data_object, Clipboard::GetFileDescriptorFormatType()))
300 return false;
302 STGMEDIUM content;
303 // The call to GetData can be very slow depending on what is in
304 // |data_object|.
305 if (GetData(
306 data_object, Clipboard::GetFileContentZeroFormatType(), &content)) {
307 if (TYMED_HGLOBAL == content.tymed) {
308 base::win::ScopedHGlobal<char*> data(content.hGlobal);
309 file_contents->assign(data.get(), data.Size());
311 ReleaseStgMedium(&content);
314 STGMEDIUM description;
315 if (GetData(data_object,
316 Clipboard::GetFileDescriptorFormatType(),
317 &description)) {
319 base::win::ScopedHGlobal<FILEGROUPDESCRIPTOR*> fgd(description.hGlobal);
320 // We expect there to be at least one file in here.
321 DCHECK_GE(fgd->cItems, 1u);
322 filename->assign(fgd->fgd[0].cFileName);
324 ReleaseStgMedium(&description);
326 return true;
329 bool ClipboardUtil::GetWebCustomData(
330 IDataObject* data_object,
331 std::map<base::string16, base::string16>* custom_data) {
332 DCHECK(data_object && custom_data);
334 if (!HasData(data_object, Clipboard::GetWebCustomDataFormatType()))
335 return false;
337 STGMEDIUM store;
338 if (GetData(data_object, Clipboard::GetWebCustomDataFormatType(), &store)) {
340 base::win::ScopedHGlobal<char*> data(store.hGlobal);
341 ReadCustomDataIntoMap(data.get(), data.Size(), custom_data);
343 ReleaseStgMedium(&store);
344 return true;
346 return false;
350 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
351 // WebCore/platform/win/ClipboardUtilitiesWin.cpp.
353 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
355 * Redistribution and use in source and binary forms, with or without
356 * modification, are permitted provided that the following conditions
357 * are met:
358 * 1. Redistributions of source code must retain the above copyright
359 * notice, this list of conditions and the following disclaimer.
360 * 2. Redistributions in binary form must reproduce the above copyright
361 * notice, this list of conditions and the following disclaimer in the
362 * documentation and/or other materials provided with the distribution.
364 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
365 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
366 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
367 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
368 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
369 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
370 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
371 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
372 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
373 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
374 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
377 // Helper method for converting from text/html to MS CF_HTML.
378 // Documentation for the CF_HTML format is available at
379 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
380 std::string ClipboardUtil::HtmlToCFHtml(const std::string& html,
381 const std::string& base_url) {
382 if (html.empty())
383 return std::string();
385 #define MAX_DIGITS 10
386 #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
387 #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
388 #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
390 static const char* header = "Version:0.9\r\n"
391 "StartHTML:" NUMBER_FORMAT "\r\n"
392 "EndHTML:" NUMBER_FORMAT "\r\n"
393 "StartFragment:" NUMBER_FORMAT "\r\n"
394 "EndFragment:" NUMBER_FORMAT "\r\n";
395 static const char* source_url_prefix = "SourceURL:";
397 static const char* start_markup =
398 "<html>\r\n<body>\r\n<!--StartFragment-->";
399 static const char* end_markup =
400 "<!--EndFragment-->\r\n</body>\r\n</html>";
402 // Calculate offsets
403 size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 +
404 MAX_DIGITS * 4;
405 if (!base_url.empty()) {
406 start_html_offset += strlen(source_url_prefix) +
407 base_url.length() + 2; // Add 2 for \r\n.
409 size_t start_fragment_offset = start_html_offset + strlen(start_markup);
410 size_t end_fragment_offset = start_fragment_offset + html.length();
411 size_t end_html_offset = end_fragment_offset + strlen(end_markup);
413 std::string result = base::StringPrintf(header,
414 start_html_offset,
415 end_html_offset,
416 start_fragment_offset,
417 end_fragment_offset);
418 if (!base_url.empty()) {
419 result.append(source_url_prefix);
420 result.append(base_url);
421 result.append("\r\n");
423 result.append(start_markup);
424 result.append(html);
425 result.append(end_markup);
427 #undef MAX_DIGITS
428 #undef MAKE_NUMBER_FORMAT_1
429 #undef MAKE_NUMBER_FORMAT_2
430 #undef NUMBER_FORMAT
432 return result;
435 // Helper method for converting from MS CF_HTML to text/html.
436 void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html,
437 std::string* html,
438 std::string* base_url) {
439 size_t fragment_start = std::string::npos;
440 size_t fragment_end = std::string::npos;
442 ClipboardUtil::CFHtmlExtractMetadata(
443 cf_html, base_url, NULL, &fragment_start, &fragment_end);
445 if (html &&
446 fragment_start != std::string::npos &&
447 fragment_end != std::string::npos) {
448 *html = cf_html.substr(fragment_start, fragment_end - fragment_start);
449 base::TrimWhitespace(*html, base::TRIM_ALL, html);
453 void ClipboardUtil::CFHtmlExtractMetadata(const std::string& cf_html,
454 std::string* base_url,
455 size_t* html_start,
456 size_t* fragment_start,
457 size_t* fragment_end) {
458 // Obtain base_url if present.
459 if (base_url) {
460 static std::string src_url_str("SourceURL:");
461 size_t line_start = cf_html.find(src_url_str);
462 if (line_start != std::string::npos) {
463 size_t src_end = cf_html.find("\n", line_start);
464 size_t src_start = line_start + src_url_str.length();
465 if (src_end != std::string::npos && src_start != std::string::npos) {
466 *base_url = cf_html.substr(src_start, src_end - src_start);
467 base::TrimWhitespace(*base_url, base::TRIM_ALL, base_url);
472 // Find the markup between "<!--StartFragment-->" and "<!--EndFragment-->".
473 // If the comments cannot be found, like copying from OpenOffice Writer,
474 // we simply fall back to using StartFragment/EndFragment bytecount values
475 // to determine the fragment indexes.
476 std::string cf_html_lower = base::StringToLowerASCII(cf_html);
477 size_t markup_start = cf_html_lower.find("<html", 0);
478 if (html_start) {
479 *html_start = markup_start;
481 size_t tag_start = cf_html.find("<!--StartFragment", markup_start);
482 if (tag_start == std::string::npos) {
483 static std::string start_fragment_str("StartFragment:");
484 size_t start_fragment_start = cf_html.find(start_fragment_str);
485 if (start_fragment_start != std::string::npos) {
486 *fragment_start = static_cast<size_t>(atoi(cf_html.c_str() +
487 start_fragment_start + start_fragment_str.length()));
490 static std::string end_fragment_str("EndFragment:");
491 size_t end_fragment_start = cf_html.find(end_fragment_str);
492 if (end_fragment_start != std::string::npos) {
493 *fragment_end = static_cast<size_t>(atoi(cf_html.c_str() +
494 end_fragment_start + end_fragment_str.length()));
496 } else {
497 *fragment_start = cf_html.find('>', tag_start) + 1;
498 size_t tag_end = cf_html.rfind("<!--EndFragment", std::string::npos);
499 *fragment_end = cf_html.rfind('<', tag_end);
503 } // namespace ui