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/memory/scoped_handle.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/win/scoped_hglobal.h"
18 #include "ui/base/clipboard/clipboard.h"
19 #include "ui/base/clipboard/custom_data_helper.h"
25 bool HasData(IDataObject
* data_object
, const Clipboard::FormatType
& format
) {
26 FORMATETC format_etc
= format
.ToFormatEtc();
27 return SUCCEEDED(data_object
->QueryGetData(&format_etc
));
30 bool GetData(IDataObject
* data_object
,
31 const Clipboard::FormatType
& format
,
33 FORMATETC format_etc
= format
.ToFormatEtc();
34 return SUCCEEDED(data_object
->GetData(&format_etc
, medium
));
37 bool GetUrlFromHDrop(IDataObject
* data_object
,
39 base::string16
* title
) {
40 DCHECK(data_object
&& url
&& title
);
43 if (!GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
))
46 HDROP hdrop
= static_cast<HDROP
>(GlobalLock(medium
.hGlobal
));
52 wchar_t filename
[MAX_PATH
];
53 if (DragQueryFileW(hdrop
, 0, filename
, arraysize(filename
))) {
54 wchar_t url_buffer
[INTERNET_MAX_URL_LENGTH
];
55 if (0 == _wcsicmp(PathFindExtensionW(filename
), L
".url") &&
56 GetPrivateProfileStringW(L
"InternetShortcut", L
"url", 0, url_buffer
,
57 arraysize(url_buffer
), filename
)) {
58 url
->assign(url_buffer
);
59 PathRemoveExtension(filename
);
60 title
->assign(PathFindFileName(filename
));
66 GlobalUnlock(medium
.hGlobal
);
67 // We don't need to call ReleaseStgMedium here because as far as I can tell,
68 // DragFinish frees the hGlobal for us.
72 void SplitUrlAndTitle(const base::string16
& str
,
74 base::string16
* title
) {
76 size_t newline_pos
= str
.find('\n');
77 if (newline_pos
!= base::string16::npos
) {
78 url
->assign(str
, 0, newline_pos
);
79 title
->assign(str
, newline_pos
+ 1, base::string16::npos
);
86 bool GetFileUrl(IDataObject
* data_object
, base::string16
* url
,
87 base::string16
* title
) {
89 if (GetData(data_object
, Clipboard::GetFilenameWFormatType(), &store
)) {
92 // filename using unicode
93 base::win::ScopedHGlobal
<wchar_t> data(store
.hGlobal
);
94 if (data
.get() && data
.get()[0] &&
95 (PathFileExists(data
.get()) || PathIsUNC(data
.get()))) {
96 wchar_t file_url
[INTERNET_MAX_URL_LENGTH
];
97 DWORD file_url_len
= arraysize(file_url
);
98 if (SUCCEEDED(::UrlCreateFromPathW(data
.get(), file_url
, &file_url_len
,
100 url
->assign(file_url
);
101 title
->assign(file_url
);
106 ReleaseStgMedium(&store
);
111 if (GetData(data_object
, Clipboard::GetFilenameFormatType(), &store
)) {
112 bool success
= false;
114 // filename using ascii
115 base::win::ScopedHGlobal
<char> data(store
.hGlobal
);
116 if (data
.get() && data
.get()[0] && (PathFileExistsA(data
.get()) ||
117 PathIsUNCA(data
.get()))) {
118 char file_url
[INTERNET_MAX_URL_LENGTH
];
119 DWORD file_url_len
= arraysize(file_url
);
120 if (SUCCEEDED(::UrlCreateFromPathA(data
.get(), file_url
, &file_url_len
,
122 url
->assign(base::UTF8ToWide(file_url
));
128 ReleaseStgMedium(&store
);
137 bool ClipboardUtil::HasUrl(IDataObject
* data_object
) {
139 return HasData(data_object
, Clipboard::GetMozUrlFormatType()) ||
140 HasData(data_object
, Clipboard::GetUrlWFormatType()) ||
141 HasData(data_object
, Clipboard::GetUrlFormatType()) ||
142 HasData(data_object
, Clipboard::GetFilenameWFormatType()) ||
143 HasData(data_object
, Clipboard::GetFilenameFormatType());
146 bool ClipboardUtil::HasFilenames(IDataObject
* data_object
) {
148 return HasData(data_object
, Clipboard::GetCFHDropFormatType());
151 bool ClipboardUtil::HasFileContents(IDataObject
* data_object
) {
153 return HasData(data_object
, Clipboard::GetFileContentZeroFormatType());
156 bool ClipboardUtil::HasHtml(IDataObject
* data_object
) {
158 return HasData(data_object
, Clipboard::GetHtmlFormatType()) ||
159 HasData(data_object
, Clipboard::GetTextHtmlFormatType());
162 bool ClipboardUtil::HasPlainText(IDataObject
* data_object
) {
164 return HasData(data_object
, Clipboard::GetPlainTextWFormatType()) ||
165 HasData(data_object
, Clipboard::GetPlainTextFormatType());
168 bool ClipboardUtil::GetUrl(IDataObject
* data_object
,
169 base::string16
* url
, base::string16
* title
, bool convert_filenames
) {
170 DCHECK(data_object
&& url
&& title
);
171 if (!HasUrl(data_object
))
174 // Try to extract a URL from |data_object| in a variety of formats.
176 if (GetUrlFromHDrop(data_object
, url
, title
))
179 if (GetData(data_object
, Clipboard::GetMozUrlFormatType(), &store
) ||
180 GetData(data_object
, Clipboard::GetUrlWFormatType(), &store
)) {
182 // Mozilla URL format or unicode URL
183 base::win::ScopedHGlobal
<wchar_t> data(store
.hGlobal
);
184 SplitUrlAndTitle(data
.get(), url
, title
);
186 ReleaseStgMedium(&store
);
190 if (GetData(data_object
, Clipboard::GetUrlFormatType(), &store
)) {
193 base::win::ScopedHGlobal
<char> data(store
.hGlobal
);
194 SplitUrlAndTitle(base::UTF8ToWide(data
.get()), url
, title
);
196 ReleaseStgMedium(&store
);
200 if (convert_filenames
) {
201 return GetFileUrl(data_object
, url
, title
);
207 bool ClipboardUtil::GetFilenames(IDataObject
* data_object
,
208 std::vector
<base::string16
>* filenames
) {
209 DCHECK(data_object
&& filenames
);
210 if (!HasFilenames(data_object
))
214 if (!GetData(data_object
, Clipboard::GetCFHDropFormatType(), &medium
))
217 HDROP hdrop
= static_cast<HDROP
>(GlobalLock(medium
.hGlobal
));
221 const int kMaxFilenameLen
= 4096;
222 const unsigned num_files
= DragQueryFileW(hdrop
, 0xffffffff, 0, 0);
223 for (unsigned int i
= 0; i
< num_files
; ++i
) {
224 wchar_t filename
[kMaxFilenameLen
];
225 if (!DragQueryFileW(hdrop
, i
, filename
, kMaxFilenameLen
))
227 filenames
->push_back(filename
);
231 GlobalUnlock(medium
.hGlobal
);
232 // We don't need to call ReleaseStgMedium here because as far as I can tell,
233 // DragFinish frees the hGlobal for us.
237 bool ClipboardUtil::GetPlainText(IDataObject
* data_object
,
238 base::string16
* plain_text
) {
239 DCHECK(data_object
&& plain_text
);
240 if (!HasPlainText(data_object
))
244 if (GetData(data_object
, Clipboard::GetPlainTextWFormatType(), &store
)) {
247 base::win::ScopedHGlobal
<wchar_t> data(store
.hGlobal
);
248 plain_text
->assign(data
.get());
250 ReleaseStgMedium(&store
);
254 if (GetData(data_object
, Clipboard::GetPlainTextFormatType(), &store
)) {
257 base::win::ScopedHGlobal
<char> data(store
.hGlobal
);
258 plain_text
->assign(base::UTF8ToWide(data
.get()));
260 ReleaseStgMedium(&store
);
264 // If a file is dropped on the window, it does not provide either of the
265 // plain text formats, so here we try to forcibly get a url.
266 base::string16 title
;
267 return GetUrl(data_object
, plain_text
, &title
, false);
270 bool ClipboardUtil::GetHtml(IDataObject
* data_object
,
271 base::string16
* html
, std::string
* base_url
) {
272 DCHECK(data_object
&& html
&& base_url
);
275 if (HasData(data_object
, Clipboard::GetHtmlFormatType()) &&
276 GetData(data_object
, Clipboard::GetHtmlFormatType(), &store
)) {
279 base::win::ScopedHGlobal
<char> data(store
.hGlobal
);
281 std::string html_utf8
;
282 CFHtmlToHtml(std::string(data
.get(), data
.Size()), &html_utf8
, base_url
);
283 html
->assign(base::UTF8ToWide(html_utf8
));
285 ReleaseStgMedium(&store
);
289 if (!HasData(data_object
, Clipboard::GetTextHtmlFormatType()))
292 if (!GetData(data_object
, Clipboard::GetTextHtmlFormatType(), &store
))
297 base::win::ScopedHGlobal
<wchar_t> data(store
.hGlobal
);
298 html
->assign(data
.get());
300 ReleaseStgMedium(&store
);
304 bool ClipboardUtil::GetFileContents(IDataObject
* data_object
,
305 base::string16
* filename
, std::string
* file_contents
) {
306 DCHECK(data_object
&& filename
&& file_contents
);
307 if (!HasData(data_object
, Clipboard::GetFileContentZeroFormatType()) &&
308 !HasData(data_object
, Clipboard::GetFileDescriptorFormatType()))
312 // The call to GetData can be very slow depending on what is in
315 data_object
, Clipboard::GetFileContentZeroFormatType(), &content
)) {
316 if (TYMED_HGLOBAL
== content
.tymed
) {
317 base::win::ScopedHGlobal
<char> data(content
.hGlobal
);
318 file_contents
->assign(data
.get(), data
.Size());
320 ReleaseStgMedium(&content
);
323 STGMEDIUM description
;
324 if (GetData(data_object
,
325 Clipboard::GetFileDescriptorFormatType(),
328 base::win::ScopedHGlobal
<FILEGROUPDESCRIPTOR
> fgd(description
.hGlobal
);
329 // We expect there to be at least one file in here.
330 DCHECK_GE(fgd
->cItems
, 1u);
331 filename
->assign(fgd
->fgd
[0].cFileName
);
333 ReleaseStgMedium(&description
);
338 bool ClipboardUtil::GetWebCustomData(
339 IDataObject
* data_object
,
340 std::map
<base::string16
, base::string16
>* custom_data
) {
341 DCHECK(data_object
&& custom_data
);
343 if (!HasData(data_object
, Clipboard::GetWebCustomDataFormatType()))
347 if (GetData(data_object
, Clipboard::GetWebCustomDataFormatType(), &store
)) {
349 base::win::ScopedHGlobal
<char> data(store
.hGlobal
);
350 ReadCustomDataIntoMap(data
.get(), data
.Size(), custom_data
);
352 ReleaseStgMedium(&store
);
359 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
360 // WebCore/platform/win/ClipboardUtilitiesWin.cpp.
362 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
364 * Redistribution and use in source and binary forms, with or without
365 * modification, are permitted provided that the following conditions
367 * 1. Redistributions of source code must retain the above copyright
368 * notice, this list of conditions and the following disclaimer.
369 * 2. Redistributions in binary form must reproduce the above copyright
370 * notice, this list of conditions and the following disclaimer in the
371 * documentation and/or other materials provided with the distribution.
373 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
374 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
375 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
376 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
377 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
378 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
379 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
380 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
381 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
382 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
383 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
386 // Helper method for converting from text/html to MS CF_HTML.
387 // Documentation for the CF_HTML format is available at
388 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
389 std::string
ClipboardUtil::HtmlToCFHtml(const std::string
& html
,
390 const std::string
& base_url
) {
392 return std::string();
394 #define MAX_DIGITS 10
395 #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
396 #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
397 #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
399 static const char* header
= "Version:0.9\r\n"
400 "StartHTML:" NUMBER_FORMAT
"\r\n"
401 "EndHTML:" NUMBER_FORMAT
"\r\n"
402 "StartFragment:" NUMBER_FORMAT
"\r\n"
403 "EndFragment:" NUMBER_FORMAT
"\r\n";
404 static const char* source_url_prefix
= "SourceURL:";
406 static const char* start_markup
=
407 "<html>\r\n<body>\r\n<!--StartFragment-->";
408 static const char* end_markup
=
409 "<!--EndFragment-->\r\n</body>\r\n</html>";
412 size_t start_html_offset
= strlen(header
) - strlen(NUMBER_FORMAT
) * 4 +
414 if (!base_url
.empty()) {
415 start_html_offset
+= strlen(source_url_prefix
) +
416 base_url
.length() + 2; // Add 2 for \r\n.
418 size_t start_fragment_offset
= start_html_offset
+ strlen(start_markup
);
419 size_t end_fragment_offset
= start_fragment_offset
+ html
.length();
420 size_t end_html_offset
= end_fragment_offset
+ strlen(end_markup
);
422 std::string result
= base::StringPrintf(header
,
425 start_fragment_offset
,
426 end_fragment_offset
);
427 if (!base_url
.empty()) {
428 result
.append(source_url_prefix
);
429 result
.append(base_url
);
430 result
.append("\r\n");
432 result
.append(start_markup
);
434 result
.append(end_markup
);
437 #undef MAKE_NUMBER_FORMAT_1
438 #undef MAKE_NUMBER_FORMAT_2
444 // Helper method for converting from MS CF_HTML to text/html.
445 void ClipboardUtil::CFHtmlToHtml(const std::string
& cf_html
,
447 std::string
* base_url
) {
448 size_t fragment_start
= std::string::npos
;
449 size_t fragment_end
= std::string::npos
;
451 ClipboardUtil::CFHtmlExtractMetadata(
452 cf_html
, base_url
, NULL
, &fragment_start
, &fragment_end
);
455 fragment_start
!= std::string::npos
&&
456 fragment_end
!= std::string::npos
) {
457 *html
= cf_html
.substr(fragment_start
, fragment_end
- fragment_start
);
458 TrimWhitespace(*html
, TRIM_ALL
, html
);
462 void ClipboardUtil::CFHtmlExtractMetadata(const std::string
& cf_html
,
463 std::string
* base_url
,
465 size_t* fragment_start
,
466 size_t* fragment_end
) {
467 // Obtain base_url if present.
469 static std::string
src_url_str("SourceURL:");
470 size_t line_start
= cf_html
.find(src_url_str
);
471 if (line_start
!= std::string::npos
) {
472 size_t src_end
= cf_html
.find("\n", line_start
);
473 size_t src_start
= line_start
+ src_url_str
.length();
474 if (src_end
!= std::string::npos
&& src_start
!= std::string::npos
) {
475 *base_url
= cf_html
.substr(src_start
, src_end
- src_start
);
476 TrimWhitespace(*base_url
, TRIM_ALL
, base_url
);
481 // Find the markup between "<!--StartFragment-->" and "<!--EndFragment-->".
482 // If the comments cannot be found, like copying from OpenOffice Writer,
483 // we simply fall back to using StartFragment/EndFragment bytecount values
484 // to determine the fragment indexes.
485 std::string cf_html_lower
= StringToLowerASCII(cf_html
);
486 size_t markup_start
= cf_html_lower
.find("<html", 0);
488 *html_start
= markup_start
;
490 size_t tag_start
= cf_html
.find("<!--StartFragment", markup_start
);
491 if (tag_start
== std::string::npos
) {
492 static std::string
start_fragment_str("StartFragment:");
493 size_t start_fragment_start
= cf_html
.find(start_fragment_str
);
494 if (start_fragment_start
!= std::string::npos
) {
495 *fragment_start
= static_cast<size_t>(atoi(cf_html
.c_str() +
496 start_fragment_start
+ start_fragment_str
.length()));
499 static std::string
end_fragment_str("EndFragment:");
500 size_t end_fragment_start
= cf_html
.find(end_fragment_str
);
501 if (end_fragment_start
!= std::string::npos
) {
502 *fragment_end
= static_cast<size_t>(atoi(cf_html
.c_str() +
503 end_fragment_start
+ end_fragment_str
.length()));
506 *fragment_start
= cf_html
.find('>', tag_start
) + 1;
507 size_t tag_end
= cf_html
.rfind("<!--EndFragment", std::string::npos
);
508 *fragment_end
= cf_html
.rfind('<', tag_end
);