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_util_win.h" 6 7 #include <shellapi.h> 8 #include <shlwapi.h> 9 #include <wininet.h> // For INTERNET_MAX_URL_LENGTH. 10 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/utf_string_conversions.h" 16 #include "base/win/scoped_hglobal.h" 17 #include "ui/base/clipboard/clipboard.h" 18 #include "ui/base/clipboard/custom_data_helper.h" 19 20 namespace ui { 21 22 namespace { 23 24 bool HasData(IDataObject* data_object, const Clipboard::FormatType& format) { 25 FORMATETC format_etc = format.ToFormatEtc(); 26 return SUCCEEDED(data_object->QueryGetData(&format_etc)); 27 } 28 29 bool GetData(IDataObject* data_object, 30 const Clipboard::FormatType& format, 31 STGMEDIUM* medium) { 32 FORMATETC format_etc = format.ToFormatEtc(); 33 return SUCCEEDED(data_object->GetData(&format_etc, medium)); 34 } 35 36 bool GetUrlFromHDrop(IDataObject* data_object, 37 base::string16* url, 38 base::string16* title) { 39 DCHECK(data_object && url && title); 40 41 STGMEDIUM medium; 42 if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium)) 43 return false; 44 45 HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); 46 47 if (!hdrop) 48 return false; 49 50 bool success = false; 51 wchar_t filename[MAX_PATH]; 52 if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) { 53 wchar_t url_buffer[INTERNET_MAX_URL_LENGTH]; 54 if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") && 55 GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer, 56 arraysize(url_buffer), filename)) { 57 url->assign(url_buffer); 58 PathRemoveExtension(filename); 59 title->assign(PathFindFileName(filename)); 60 success = true; 61 } 62 } 63 64 DragFinish(hdrop); 65 GlobalUnlock(medium.hGlobal); 66 // We don't need to call ReleaseStgMedium here because as far as I can tell, 67 // DragFinish frees the hGlobal for us. 68 return success; 69 } 70 71 void SplitUrlAndTitle(const base::string16& str, 72 base::string16* url, 73 base::string16* title) { 74 DCHECK(url && title); 75 size_t newline_pos = str.find('\n'); 76 if (newline_pos != base::string16::npos) { 77 url->assign(str, 0, newline_pos); 78 title->assign(str, newline_pos + 1, base::string16::npos); 79 } else { 80 url->assign(str); 81 title->assign(str); 82 } 83 } 84 85 bool GetFileUrl(IDataObject* data_object, base::string16* url, 86 base::string16* title) { 87 STGMEDIUM store; 88 if (GetData(data_object, Clipboard::GetFilenameWFormatType(), &store)) { 89 bool success = false; 90 { 91 // filename using unicode 92 base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); 93 if (data.get() && data.get()[0] && 94 (PathFileExists(data.get()) || PathIsUNC(data.get()))) { 95 wchar_t file_url[INTERNET_MAX_URL_LENGTH]; 96 DWORD file_url_len = arraysize(file_url); 97 if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len, 98 0))) { 99 url->assign(file_url); 100 title->assign(file_url); 101 success = true; 102 } 103 } 104 } 105 ReleaseStgMedium(&store); 106 if (success) 107 return true; 108 } 109 110 if (GetData(data_object, Clipboard::GetFilenameFormatType(), &store)) { 111 bool success = false; 112 { 113 // filename using ascii 114 base::win::ScopedHGlobal<char> data(store.hGlobal); 115 if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) || 116 PathIsUNCA(data.get()))) { 117 char file_url[INTERNET_MAX_URL_LENGTH]; 118 DWORD file_url_len = arraysize(file_url); 119 if (SUCCEEDED(::UrlCreateFromPathA(data.get(), file_url, &file_url_len, 120 0))) { 121 url->assign(base::UTF8ToWide(file_url)); 122 title->assign(*url); 123 success = true; 124 } 125 } 126 } 127 ReleaseStgMedium(&store); 128 if (success) 129 return true; 130 } 131 return false; 132 } 133 134 } // namespace 135 136 bool ClipboardUtil::HasUrl(IDataObject* data_object, bool convert_filenames) { 137 DCHECK(data_object); 138 return HasData(data_object, Clipboard::GetMozUrlFormatType()) || 139 HasData(data_object, Clipboard::GetUrlWFormatType()) || 140 HasData(data_object, Clipboard::GetUrlFormatType()) || 141 (convert_filenames && ( 142 HasData(data_object, Clipboard::GetFilenameWFormatType()) || 143 HasData(data_object, Clipboard::GetFilenameFormatType()))); 144 } 145 146 bool ClipboardUtil::HasFilenames(IDataObject* data_object) { 147 DCHECK(data_object); 148 return HasData(data_object, Clipboard::GetCFHDropFormatType()); 149 } 150 151 bool ClipboardUtil::HasFileContents(IDataObject* data_object) { 152 DCHECK(data_object); 153 return HasData(data_object, Clipboard::GetFileContentZeroFormatType()); 154 } 155 156 bool ClipboardUtil::HasHtml(IDataObject* data_object) { 157 DCHECK(data_object); 158 return HasData(data_object, Clipboard::GetHtmlFormatType()) || 159 HasData(data_object, Clipboard::GetTextHtmlFormatType()); 160 } 161 162 bool ClipboardUtil::HasPlainText(IDataObject* data_object) { 163 DCHECK(data_object); 164 return HasData(data_object, Clipboard::GetPlainTextWFormatType()) || 165 HasData(data_object, Clipboard::GetPlainTextFormatType()); 166 } 167 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, convert_filenames)) 172 return false; 173 174 // Try to extract a URL from |data_object| in a variety of formats. 175 STGMEDIUM store; 176 if (GetUrlFromHDrop(data_object, url, title)) 177 return true; 178 179 if (GetData(data_object, Clipboard::GetMozUrlFormatType(), &store) || 180 GetData(data_object, Clipboard::GetUrlWFormatType(), &store)) { 181 { 182 // Mozilla URL format or unicode URL 183 base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); 184 SplitUrlAndTitle(data.get(), url, title); 185 } 186 ReleaseStgMedium(&store); 187 return true; 188 } 189 190 if (GetData(data_object, Clipboard::GetUrlFormatType(), &store)) { 191 { 192 // URL using ascii 193 base::win::ScopedHGlobal<char> data(store.hGlobal); 194 SplitUrlAndTitle(base::UTF8ToWide(data.get()), url, title); 195 } 196 ReleaseStgMedium(&store); 197 return true; 198 } 199 200 if (convert_filenames) { 201 return GetFileUrl(data_object, url, title); 202 } else { 203 return false; 204 } 205 } 206 207 bool ClipboardUtil::GetFilenames(IDataObject* data_object, 208 std::vector<base::string16>* filenames) { 209 DCHECK(data_object && filenames); 210 if (!HasFilenames(data_object)) 211 return false; 212 213 STGMEDIUM medium; 214 if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium)) 215 return false; 216 217 HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); 218 if (!hdrop) 219 return false; 220 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)) 226 continue; 227 filenames->push_back(filename); 228 } 229 230 DragFinish(hdrop); 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. 234 return true; 235 } 236 237 bool ClipboardUtil::GetPlainText(IDataObject* data_object, 238 base::string16* plain_text) { 239 DCHECK(data_object && plain_text); 240 if (!HasPlainText(data_object)) 241 return false; 242 243 STGMEDIUM store; 244 if (GetData(data_object, Clipboard::GetPlainTextWFormatType(), &store)) { 245 { 246 // Unicode text 247 base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); 248 plain_text->assign(data.get()); 249 } 250 ReleaseStgMedium(&store); 251 return true; 252 } 253 254 if (GetData(data_object, Clipboard::GetPlainTextFormatType(), &store)) { 255 { 256 // ascii text 257 base::win::ScopedHGlobal<char> data(store.hGlobal); 258 plain_text->assign(base::UTF8ToWide(data.get())); 259 } 260 ReleaseStgMedium(&store); 261 return true; 262 } 263 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); 268 } 269 270 bool ClipboardUtil::GetHtml(IDataObject* data_object, 271 base::string16* html, std::string* base_url) { 272 DCHECK(data_object && html && base_url); 273 274 STGMEDIUM store; 275 if (HasData(data_object, Clipboard::GetHtmlFormatType()) && 276 GetData(data_object, Clipboard::GetHtmlFormatType(), &store)) { 277 { 278 // MS CF html 279 base::win::ScopedHGlobal<char> data(store.hGlobal); 280 281 std::string html_utf8; 282 CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url); 283 html->assign(base::UTF8ToWide(html_utf8)); 284 } 285 ReleaseStgMedium(&store); 286 return true; 287 } 288 289 if (!HasData(data_object, Clipboard::GetTextHtmlFormatType())) 290 return false; 291 292 if (!GetData(data_object, Clipboard::GetTextHtmlFormatType(), &store)) 293 return false; 294 295 { 296 // text/html 297 base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); 298 html->assign(data.get()); 299 } 300 ReleaseStgMedium(&store); 301 return true; 302 } 303 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())) 309 return false; 310 311 STGMEDIUM content; 312 // The call to GetData can be very slow depending on what is in 313 // |data_object|. 314 if (GetData( 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()); 319 } 320 ReleaseStgMedium(&content); 321 } 322 323 STGMEDIUM description; 324 if (GetData(data_object, 325 Clipboard::GetFileDescriptorFormatType(), 326 &description)) { 327 { 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); 332 } 333 ReleaseStgMedium(&description); 334 } 335 return true; 336 } 337 338 bool ClipboardUtil::GetWebCustomData( 339 IDataObject* data_object, 340 std::map<base::string16, base::string16>* custom_data) { 341 DCHECK(data_object && custom_data); 342 343 if (!HasData(data_object, Clipboard::GetWebCustomDataFormatType())) 344 return false; 345 346 STGMEDIUM store; 347 if (GetData(data_object, Clipboard::GetWebCustomDataFormatType(), &store)) { 348 { 349 base::win::ScopedHGlobal<char> data(store.hGlobal); 350 ReadCustomDataIntoMap(data.get(), data.Size(), custom_data); 351 } 352 ReleaseStgMedium(&store); 353 return true; 354 } 355 return false; 356 } 357 358 359 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in 360 // WebCore/platform/win/ClipboardUtilitiesWin.cpp. 361 /* 362 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 363 * 364 * Redistribution and use in source and binary forms, with or without 365 * modification, are permitted provided that the following conditions 366 * are met: 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. 372 * 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. 384 */ 385 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) { 391 if (html.empty()) 392 return std::string(); 393 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) 398 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:"; 405 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>"; 410 411 // Calculate offsets 412 size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + 413 MAX_DIGITS * 4; 414 if (!base_url.empty()) { 415 start_html_offset += strlen(source_url_prefix) + 416 base_url.length() + 2; // Add 2 for \r\n. 417 } 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); 421 422 std::string result = base::StringPrintf(header, 423 start_html_offset, 424 end_html_offset, 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"); 431 } 432 result.append(start_markup); 433 result.append(html); 434 result.append(end_markup); 435 436 #undef MAX_DIGITS 437 #undef MAKE_NUMBER_FORMAT_1 438 #undef MAKE_NUMBER_FORMAT_2 439 #undef NUMBER_FORMAT 440 441 return result; 442 } 443 444 // Helper method for converting from MS CF_HTML to text/html. 445 void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html, 446 std::string* html, 447 std::string* base_url) { 448 size_t fragment_start = std::string::npos; 449 size_t fragment_end = std::string::npos; 450 451 ClipboardUtil::CFHtmlExtractMetadata( 452 cf_html, base_url, NULL, &fragment_start, &fragment_end); 453 454 if (html && 455 fragment_start != std::string::npos && 456 fragment_end != std::string::npos) { 457 *html = cf_html.substr(fragment_start, fragment_end - fragment_start); 458 base::TrimWhitespace(*html, base::TRIM_ALL, html); 459 } 460 } 461 462 void ClipboardUtil::CFHtmlExtractMetadata(const std::string& cf_html, 463 std::string* base_url, 464 size_t* html_start, 465 size_t* fragment_start, 466 size_t* fragment_end) { 467 // Obtain base_url if present. 468 if (base_url) { 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 base::TrimWhitespace(*base_url, base::TRIM_ALL, base_url); 477 } 478 } 479 } 480 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); 487 if (html_start) { 488 *html_start = markup_start; 489 } 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())); 497 } 498 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())); 504 } 505 } else { 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); 509 } 510 } 511 512 } // namespace ui 513