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"
9 #include <wininet.h> // For INTERNET_MAX_URL_LENGTH.
11 #include "base/basictypes.h"
12 #include "base/logging.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/win/scoped_hglobal.h"
18 #include "net/base/filename_util.h"
19 #include "ui/base/clipboard/clipboard.h"
20 #include "ui/base/clipboard/custom_data_helper.h"
27 bool HasData(IDataObject
* data_object
, const Clipboard::FormatType
& format
) {
28 FORMATETC format_etc
= format
.ToFormatEtc();
29 return SUCCEEDED(data_object
->QueryGetData(&format_etc
));
32 bool GetData(IDataObject
* data_object
,
33 const Clipboard::FormatType
& format
,
35 FORMATETC format_etc
= format
.ToFormatEtc();
36 return SUCCEEDED(data_object
->GetData(&format_etc
, medium
));
39 bool GetUrlFromHDrop(IDataObject
* data_object
,
41 base::string16
* title
) {
42 DCHECK(data_object
&& url
&& title
);
46 if (!GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
))
50 base::win::ScopedHGlobal
<HDROP
> hdrop(medium
.hGlobal
);
55 wchar_t filename
[MAX_PATH
];
56 if (DragQueryFileW(hdrop
.get(), 0, filename
, arraysize(filename
))) {
57 wchar_t url_buffer
[INTERNET_MAX_URL_LENGTH
];
58 if (0 == _wcsicmp(PathFindExtensionW(filename
), L
".url") &&
59 GetPrivateProfileStringW(L
"InternetShortcut",
63 arraysize(url_buffer
),
65 *url
= GURL(url_buffer
);
66 PathRemoveExtension(filename
);
67 title
->assign(PathFindFileName(filename
));
68 success
= url
->is_valid();
73 ReleaseStgMedium(&medium
);
77 void SplitUrlAndTitle(const base::string16
& str
,
79 base::string16
* title
) {
81 size_t newline_pos
= str
.find('\n');
82 if (newline_pos
!= base::string16::npos
) {
83 *url
= GURL(base::string16(str
, 0, newline_pos
));
84 title
->assign(str
, newline_pos
+ 1, base::string16::npos
);
93 bool ClipboardUtil::HasUrl(IDataObject
* data_object
, bool convert_filenames
) {
95 return HasData(data_object
, Clipboard::GetMozUrlFormatType()) ||
96 HasData(data_object
, Clipboard::GetUrlWFormatType()) ||
97 HasData(data_object
, Clipboard::GetUrlFormatType()) ||
98 (convert_filenames
&& HasFilenames(data_object
));
101 bool ClipboardUtil::HasFilenames(IDataObject
* data_object
) {
103 return HasData(data_object
, Clipboard::GetCFHDropFormatType()) ||
104 HasData(data_object
, Clipboard::GetFilenameWFormatType()) ||
105 HasData(data_object
, Clipboard::GetFilenameFormatType());
108 bool ClipboardUtil::HasFileContents(IDataObject
* data_object
) {
110 return HasData(data_object
, Clipboard::GetFileContentZeroFormatType());
113 bool ClipboardUtil::HasHtml(IDataObject
* data_object
) {
115 return HasData(data_object
, Clipboard::GetHtmlFormatType()) ||
116 HasData(data_object
, Clipboard::GetTextHtmlFormatType());
119 bool ClipboardUtil::HasPlainText(IDataObject
* data_object
) {
121 return HasData(data_object
, Clipboard::GetPlainTextWFormatType()) ||
122 HasData(data_object
, Clipboard::GetPlainTextFormatType());
125 bool ClipboardUtil::GetUrl(IDataObject
* data_object
,
127 base::string16
* title
,
128 bool convert_filenames
) {
129 DCHECK(data_object
&& url
&& title
);
130 if (!HasUrl(data_object
, convert_filenames
))
133 // Try to extract a URL from |data_object| in a variety of formats.
135 if (GetUrlFromHDrop(data_object
, url
, title
))
138 if (GetData(data_object
, Clipboard::GetMozUrlFormatType(), &store
) ||
139 GetData(data_object
, Clipboard::GetUrlWFormatType(), &store
)) {
141 // Mozilla URL format or unicode URL
142 base::win::ScopedHGlobal
<wchar_t*> data(store
.hGlobal
);
143 SplitUrlAndTitle(data
.get(), url
, title
);
145 ReleaseStgMedium(&store
);
146 return url
->is_valid();
149 if (GetData(data_object
, Clipboard::GetUrlFormatType(), &store
)) {
152 base::win::ScopedHGlobal
<char*> data(store
.hGlobal
);
153 SplitUrlAndTitle(base::UTF8ToWide(data
.get()), url
, title
);
155 ReleaseStgMedium(&store
);
156 return url
->is_valid();
159 if (convert_filenames
) {
160 std::vector
<base::string16
> filenames
;
161 if (!GetFilenames(data_object
, &filenames
))
163 DCHECK_GT(filenames
.size(), 0U);
164 *url
= net::FilePathToFileURL(base::FilePath(filenames
[0]));
165 return url
->is_valid();
171 bool ClipboardUtil::GetFilenames(IDataObject
* data_object
,
172 std::vector
<base::string16
>* filenames
) {
173 DCHECK(data_object
&& filenames
);
174 if (!HasFilenames(data_object
))
178 if (GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
)) {
180 base::win::ScopedHGlobal
<HDROP
> hdrop(medium
.hGlobal
);
184 const int kMaxFilenameLen
= 4096;
185 const unsigned num_files
= DragQueryFileW(hdrop
.get(), 0xffffffff, 0, 0);
186 for (unsigned int i
= 0; i
< num_files
; ++i
) {
187 wchar_t filename
[kMaxFilenameLen
];
188 if (!DragQueryFileW(hdrop
.get(), i
, filename
, kMaxFilenameLen
))
190 filenames
->push_back(filename
);
193 ReleaseStgMedium(&medium
);
197 if (GetData(data_object
, Clipboard::GetFilenameWFormatType(), &medium
)) {
199 // filename using unicode
200 base::win::ScopedHGlobal
<wchar_t*> data(medium
.hGlobal
);
201 if (data
.get() && data
.get()[0])
202 filenames
->push_back(data
.get());
204 ReleaseStgMedium(&medium
);
208 if (GetData(data_object
, Clipboard::GetFilenameFormatType(), &medium
)) {
210 // filename using ascii
211 base::win::ScopedHGlobal
<char*> data(medium
.hGlobal
);
212 if (data
.get() && data
.get()[0])
213 filenames
->push_back(base::SysNativeMBToWide(data
.get()));
215 ReleaseStgMedium(&medium
);
222 bool ClipboardUtil::GetPlainText(IDataObject
* data_object
,
223 base::string16
* plain_text
) {
224 DCHECK(data_object
&& plain_text
);
225 if (!HasPlainText(data_object
))
229 if (GetData(data_object
, Clipboard::GetPlainTextWFormatType(), &store
)) {
232 base::win::ScopedHGlobal
<wchar_t*> data(store
.hGlobal
);
233 plain_text
->assign(data
.get());
235 ReleaseStgMedium(&store
);
239 if (GetData(data_object
, Clipboard::GetPlainTextFormatType(), &store
)) {
242 base::win::ScopedHGlobal
<char*> data(store
.hGlobal
);
243 plain_text
->assign(base::UTF8ToWide(data
.get()));
245 ReleaseStgMedium(&store
);
249 // If a file is dropped on the window, it does not provide either of the
250 // plain text formats, so here we try to forcibly get a url.
252 base::string16 title
;
253 if (GetUrl(data_object
, &url
, &title
, false)) {
254 *plain_text
= base::UTF8ToUTF16(url
.spec());
260 bool ClipboardUtil::GetHtml(IDataObject
* data_object
,
261 base::string16
* html
, std::string
* base_url
) {
262 DCHECK(data_object
&& html
&& base_url
);
265 if (HasData(data_object
, Clipboard::GetHtmlFormatType()) &&
266 GetData(data_object
, Clipboard::GetHtmlFormatType(), &store
)) {
269 base::win::ScopedHGlobal
<char*> data(store
.hGlobal
);
271 std::string html_utf8
;
272 CFHtmlToHtml(std::string(data
.get(), data
.Size()), &html_utf8
, base_url
);
273 html
->assign(base::UTF8ToWide(html_utf8
));
275 ReleaseStgMedium(&store
);
279 if (!HasData(data_object
, Clipboard::GetTextHtmlFormatType()))
282 if (!GetData(data_object
, Clipboard::GetTextHtmlFormatType(), &store
))
287 base::win::ScopedHGlobal
<wchar_t*> data(store
.hGlobal
);
288 html
->assign(data
.get());
290 ReleaseStgMedium(&store
);
294 bool ClipboardUtil::GetFileContents(IDataObject
* data_object
,
295 base::string16
* filename
, std::string
* file_contents
) {
296 DCHECK(data_object
&& filename
&& file_contents
);
297 if (!HasData(data_object
, Clipboard::GetFileContentZeroFormatType()) &&
298 !HasData(data_object
, Clipboard::GetFileDescriptorFormatType()))
302 // The call to GetData can be very slow depending on what is in
305 data_object
, Clipboard::GetFileContentZeroFormatType(), &content
)) {
306 if (TYMED_HGLOBAL
== content
.tymed
) {
307 base::win::ScopedHGlobal
<char*> data(content
.hGlobal
);
308 file_contents
->assign(data
.get(), data
.Size());
310 ReleaseStgMedium(&content
);
313 STGMEDIUM description
;
314 if (GetData(data_object
,
315 Clipboard::GetFileDescriptorFormatType(),
318 base::win::ScopedHGlobal
<FILEGROUPDESCRIPTOR
*> fgd(description
.hGlobal
);
319 // We expect there to be at least one file in here.
320 DCHECK_GE(fgd
->cItems
, 1u);
321 filename
->assign(fgd
->fgd
[0].cFileName
);
323 ReleaseStgMedium(&description
);
328 bool ClipboardUtil::GetWebCustomData(
329 IDataObject
* data_object
,
330 std::map
<base::string16
, base::string16
>* custom_data
) {
331 DCHECK(data_object
&& custom_data
);
333 if (!HasData(data_object
, Clipboard::GetWebCustomDataFormatType()))
337 if (GetData(data_object
, Clipboard::GetWebCustomDataFormatType(), &store
)) {
339 base::win::ScopedHGlobal
<char*> data(store
.hGlobal
);
340 ReadCustomDataIntoMap(data
.get(), data
.Size(), custom_data
);
342 ReleaseStgMedium(&store
);
349 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
350 // WebCore/platform/win/ClipboardUtilitiesWin.cpp.
352 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
354 * Redistribution and use in source and binary forms, with or without
355 * modification, are permitted provided that the following conditions
357 * 1. Redistributions of source code must retain the above copyright
358 * notice, this list of conditions and the following disclaimer.
359 * 2. Redistributions in binary form must reproduce the above copyright
360 * notice, this list of conditions and the following disclaimer in the
361 * documentation and/or other materials provided with the distribution.
363 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
364 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
365 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
366 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
367 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
368 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
369 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
370 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
371 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
372 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
373 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
376 // Helper method for converting from text/html to MS CF_HTML.
377 // Documentation for the CF_HTML format is available at
378 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
379 std::string
ClipboardUtil::HtmlToCFHtml(const std::string
& html
,
380 const std::string
& base_url
) {
382 return std::string();
384 #define MAX_DIGITS 10
385 #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
386 #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
387 #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
389 static const char* header
= "Version:0.9\r\n"
390 "StartHTML:" NUMBER_FORMAT
"\r\n"
391 "EndHTML:" NUMBER_FORMAT
"\r\n"
392 "StartFragment:" NUMBER_FORMAT
"\r\n"
393 "EndFragment:" NUMBER_FORMAT
"\r\n";
394 static const char* source_url_prefix
= "SourceURL:";
396 static const char* start_markup
=
397 "<html>\r\n<body>\r\n<!--StartFragment-->";
398 static const char* end_markup
=
399 "<!--EndFragment-->\r\n</body>\r\n</html>";
402 size_t start_html_offset
= strlen(header
) - strlen(NUMBER_FORMAT
) * 4 +
404 if (!base_url
.empty()) {
405 start_html_offset
+= strlen(source_url_prefix
) +
406 base_url
.length() + 2; // Add 2 for \r\n.
408 size_t start_fragment_offset
= start_html_offset
+ strlen(start_markup
);
409 size_t end_fragment_offset
= start_fragment_offset
+ html
.length();
410 size_t end_html_offset
= end_fragment_offset
+ strlen(end_markup
);
412 std::string result
= base::StringPrintf(header
,
415 start_fragment_offset
,
416 end_fragment_offset
);
417 if (!base_url
.empty()) {
418 result
.append(source_url_prefix
);
419 result
.append(base_url
);
420 result
.append("\r\n");
422 result
.append(start_markup
);
424 result
.append(end_markup
);
427 #undef MAKE_NUMBER_FORMAT_1
428 #undef MAKE_NUMBER_FORMAT_2
434 // Helper method for converting from MS CF_HTML to text/html.
435 void ClipboardUtil::CFHtmlToHtml(const std::string
& cf_html
,
437 std::string
* base_url
) {
438 size_t fragment_start
= std::string::npos
;
439 size_t fragment_end
= std::string::npos
;
441 ClipboardUtil::CFHtmlExtractMetadata(
442 cf_html
, base_url
, NULL
, &fragment_start
, &fragment_end
);
445 fragment_start
!= std::string::npos
&&
446 fragment_end
!= std::string::npos
) {
447 *html
= cf_html
.substr(fragment_start
, fragment_end
- fragment_start
);
448 base::TrimWhitespace(*html
, base::TRIM_ALL
, html
);
452 void ClipboardUtil::CFHtmlExtractMetadata(const std::string
& cf_html
,
453 std::string
* base_url
,
455 size_t* fragment_start
,
456 size_t* fragment_end
) {
457 // Obtain base_url if present.
459 static std::string
src_url_str("SourceURL:");
460 size_t line_start
= cf_html
.find(src_url_str
);
461 if (line_start
!= std::string::npos
) {
462 size_t src_end
= cf_html
.find("\n", line_start
);
463 size_t src_start
= line_start
+ src_url_str
.length();
464 if (src_end
!= std::string::npos
&& src_start
!= std::string::npos
) {
465 *base_url
= cf_html
.substr(src_start
, src_end
- src_start
);
466 base::TrimWhitespace(*base_url
, base::TRIM_ALL
, base_url
);
471 // Find the markup between "<!--StartFragment-->" and "<!--EndFragment-->".
472 // If the comments cannot be found, like copying from OpenOffice Writer,
473 // we simply fall back to using StartFragment/EndFragment bytecount values
474 // to determine the fragment indexes.
475 std::string cf_html_lower
= StringToLowerASCII(cf_html
);
476 size_t markup_start
= cf_html_lower
.find("<html", 0);
478 *html_start
= markup_start
;
480 size_t tag_start
= cf_html
.find("<!--StartFragment", markup_start
);
481 if (tag_start
== std::string::npos
) {
482 static std::string
start_fragment_str("StartFragment:");
483 size_t start_fragment_start
= cf_html
.find(start_fragment_str
);
484 if (start_fragment_start
!= std::string::npos
) {
485 *fragment_start
= static_cast<size_t>(atoi(cf_html
.c_str() +
486 start_fragment_start
+ start_fragment_str
.length()));
489 static std::string
end_fragment_str("EndFragment:");
490 size_t end_fragment_start
= cf_html
.find(end_fragment_str
);
491 if (end_fragment_start
!= std::string::npos
) {
492 *fragment_end
= static_cast<size_t>(atoi(cf_html
.c_str() +
493 end_fragment_start
+ end_fragment_str
.length()));
496 *fragment_start
= cf_html
.find('>', tag_start
) + 1;
497 size_t tag_end
= cf_html
.rfind("<!--EndFragment", std::string::npos
);
498 *fragment_end
= cf_html
.rfind('<', tag_end
);