• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HasData(IDataObject * data_object,const Clipboard::FormatType & format)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 
GetData(IDataObject * data_object,const Clipboard::FormatType & format,STGMEDIUM * medium)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 
GetUrlFromHDrop(IDataObject * data_object,base::string16 * url,base::string16 * title)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 
SplitUrlAndTitle(const base::string16 & str,base::string16 * url,base::string16 * title)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 
GetFileUrl(IDataObject * data_object,base::string16 * url,base::string16 * title)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 
HasUrl(IDataObject * data_object,bool convert_filenames)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 
HasFilenames(IDataObject * data_object)146 bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
147   DCHECK(data_object);
148   return HasData(data_object, Clipboard::GetCFHDropFormatType());
149 }
150 
HasFileContents(IDataObject * data_object)151 bool ClipboardUtil::HasFileContents(IDataObject* data_object) {
152   DCHECK(data_object);
153   return HasData(data_object, Clipboard::GetFileContentZeroFormatType());
154 }
155 
HasHtml(IDataObject * data_object)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 
HasPlainText(IDataObject * data_object)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 
GetUrl(IDataObject * data_object,base::string16 * url,base::string16 * title,bool convert_filenames)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 
GetFilenames(IDataObject * data_object,std::vector<base::string16> * filenames)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 
GetPlainText(IDataObject * data_object,base::string16 * plain_text)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 
GetHtml(IDataObject * data_object,base::string16 * html,std::string * base_url)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 
GetFileContents(IDataObject * data_object,base::string16 * filename,std::string * file_contents)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 
GetWebCustomData(IDataObject * data_object,std::map<base::string16,base::string16> * custom_data)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
HtmlToCFHtml(const std::string & html,const std::string & base_url)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.
CFHtmlToHtml(const std::string & cf_html,std::string * html,std::string * base_url)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 
CFHtmlExtractMetadata(const std::string & cf_html,std::string * base_url,size_t * html_start,size_t * fragment_start,size_t * fragment_end)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