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/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"
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
,
36 FORMATETC format_etc
= format
.ToFormatEtc();
37 return SUCCEEDED(data_object
->GetData(&format_etc
, medium
));
40 bool GetUrlFromHDrop(IDataObject
* data_object
,
42 base::string16
* title
) {
43 DCHECK(data_object
&& url
&& title
);
47 if (!GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
))
51 base::win::ScopedHGlobal
<HDROP
> hdrop(medium
.hGlobal
);
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",
64 arraysize(url_buffer
),
66 *url
= GURL(url_buffer
);
67 PathRemoveExtension(filename
);
68 title
->assign(PathFindFileName(filename
));
69 success
= url
->is_valid();
74 ReleaseStgMedium(&medium
);
78 void SplitUrlAndTitle(const base::string16
& str
,
80 base::string16
* 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
);
94 bool ClipboardUtil::HasUrl(IDataObject
* data_object
, bool convert_filenames
) {
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
) {
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
) {
111 return HasData(data_object
, Clipboard::GetFileContentZeroFormatType());
114 bool ClipboardUtil::HasHtml(IDataObject
* data_object
) {
116 return HasData(data_object
, Clipboard::GetHtmlFormatType()) ||
117 HasData(data_object
, Clipboard::GetTextHtmlFormatType());
120 bool ClipboardUtil::HasPlainText(IDataObject
* data_object
) {
122 return HasData(data_object
, Clipboard::GetPlainTextWFormatType()) ||
123 HasData(data_object
, Clipboard::GetPlainTextFormatType());
126 bool ClipboardUtil::GetUrl(IDataObject
* data_object
,
128 base::string16
* title
,
129 bool convert_filenames
) {
130 DCHECK(data_object
&& url
&& title
);
131 if (!HasUrl(data_object
, convert_filenames
))
134 // Try to extract a URL from |data_object| in a variety of formats.
136 if (GetUrlFromHDrop(data_object
, url
, title
))
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
)) {
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
))
164 DCHECK_GT(filenames
.size(), 0U);
165 *url
= net::FilePathToFileURL(base::FilePath(filenames
[0]));
166 return url
->is_valid();
172 bool ClipboardUtil::GetFilenames(IDataObject
* data_object
,
173 std::vector
<base::string16
>* filenames
) {
174 DCHECK(data_object
&& filenames
);
175 if (!HasFilenames(data_object
))
179 if (GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
)) {
181 base::win::ScopedHGlobal
<HDROP
> hdrop(medium
.hGlobal
);
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
))
191 filenames
->push_back(filename
);
194 ReleaseStgMedium(&medium
);
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
);
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
);
223 bool ClipboardUtil::GetPlainText(IDataObject
* data_object
,
224 base::string16
* plain_text
) {
225 DCHECK(data_object
&& plain_text
);
226 if (!HasPlainText(data_object
))
230 if (GetData(data_object
, Clipboard::GetPlainTextWFormatType(), &store
)) {
233 base::win::ScopedHGlobal
<wchar_t*> data(store
.hGlobal
);
234 plain_text
->assign(data
.get());
236 ReleaseStgMedium(&store
);
240 if (GetData(data_object
, Clipboard::GetPlainTextFormatType(), &store
)) {
243 base::win::ScopedHGlobal
<char*> data(store
.hGlobal
);
244 plain_text
->assign(base::UTF8ToWide(data
.get()));
246 ReleaseStgMedium(&store
);
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.
253 base::string16 title
;
254 if (GetUrl(data_object
, &url
, &title
, false)) {
255 *plain_text
= base::UTF8ToUTF16(url
.spec());
261 bool ClipboardUtil::GetHtml(IDataObject
* data_object
,
262 base::string16
* html
, std::string
* base_url
) {
263 DCHECK(data_object
&& html
&& base_url
);
266 if (HasData(data_object
, Clipboard::GetHtmlFormatType()) &&
267 GetData(data_object
, Clipboard::GetHtmlFormatType(), &store
)) {
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
);
280 if (!HasData(data_object
, Clipboard::GetTextHtmlFormatType()))
283 if (!GetData(data_object
, Clipboard::GetTextHtmlFormatType(), &store
))
288 base::win::ScopedHGlobal
<wchar_t*> data(store
.hGlobal
);
289 html
->assign(data
.get());
291 ReleaseStgMedium(&store
);
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()))
303 // The call to GetData can be very slow depending on what is in
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(),
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
);
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()))
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
);
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
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
) {
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>";
403 size_t start_html_offset
= strlen(header
) - strlen(NUMBER_FORMAT
) * 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
,
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
);
425 result
.append(end_markup
);
428 #undef MAKE_NUMBER_FORMAT_1
429 #undef MAKE_NUMBER_FORMAT_2
435 // Helper method for converting from MS CF_HTML to text/html.
436 void ClipboardUtil::CFHtmlToHtml(const std::string
& cf_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
);
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
,
456 size_t* fragment_start
,
457 size_t* fragment_end
) {
458 // Obtain base_url if present.
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);
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()));
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
);