• 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/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"
21 #include "url/gurl.h"
22 
23 namespace ui {
24 
25 namespace {
26 
HasData(IDataObject * data_object,const Clipboard::FormatType & format)27 bool HasData(IDataObject* data_object, const Clipboard::FormatType& format) {
28   FORMATETC format_etc = format.ToFormatEtc();
29   return SUCCEEDED(data_object->QueryGetData(&format_etc));
30 }
31 
GetData(IDataObject * data_object,const Clipboard::FormatType & format,STGMEDIUM * medium)32 bool GetData(IDataObject* data_object,
33              const Clipboard::FormatType& format,
34              STGMEDIUM* medium) {
35   FORMATETC format_etc = format.ToFormatEtc();
36   return SUCCEEDED(data_object->GetData(&format_etc, medium));
37 }
38 
GetUrlFromHDrop(IDataObject * data_object,GURL * url,base::string16 * title)39 bool GetUrlFromHDrop(IDataObject* data_object,
40                      GURL* url,
41                      base::string16* title) {
42   DCHECK(data_object && url && title);
43 
44   bool success = false;
45   STGMEDIUM medium;
46   if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium))
47     return false;
48 
49   {
50     base::win::ScopedHGlobal<HDROP> hdrop(medium.hGlobal);
51 
52     if (!hdrop.get())
53       return false;
54 
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",
60                                    L"url",
61                                    0,
62                                    url_buffer,
63                                    arraysize(url_buffer),
64                                    filename)) {
65         *url = GURL(url_buffer);
66         PathRemoveExtension(filename);
67         title->assign(PathFindFileName(filename));
68         success = url->is_valid();
69       }
70     }
71   }
72 
73   ReleaseStgMedium(&medium);
74   return success;
75 }
76 
SplitUrlAndTitle(const base::string16 & str,GURL * url,base::string16 * title)77 void SplitUrlAndTitle(const base::string16& str,
78                       GURL* url,
79                       base::string16* title) {
80   DCHECK(url && 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);
85   } else {
86     *url = GURL(str);
87     title->assign(str);
88   }
89 }
90 
91 }  // namespace
92 
HasUrl(IDataObject * data_object,bool convert_filenames)93 bool ClipboardUtil::HasUrl(IDataObject* data_object, bool convert_filenames) {
94   DCHECK(data_object);
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));
99 }
100 
HasFilenames(IDataObject * data_object)101 bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
102   DCHECK(data_object);
103   return HasData(data_object, Clipboard::GetCFHDropFormatType()) ||
104          HasData(data_object, Clipboard::GetFilenameWFormatType()) ||
105          HasData(data_object, Clipboard::GetFilenameFormatType());
106 }
107 
HasFileContents(IDataObject * data_object)108 bool ClipboardUtil::HasFileContents(IDataObject* data_object) {
109   DCHECK(data_object);
110   return HasData(data_object, Clipboard::GetFileContentZeroFormatType());
111 }
112 
HasHtml(IDataObject * data_object)113 bool ClipboardUtil::HasHtml(IDataObject* data_object) {
114   DCHECK(data_object);
115   return HasData(data_object, Clipboard::GetHtmlFormatType()) ||
116          HasData(data_object, Clipboard::GetTextHtmlFormatType());
117 }
118 
HasPlainText(IDataObject * data_object)119 bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
120   DCHECK(data_object);
121   return HasData(data_object, Clipboard::GetPlainTextWFormatType()) ||
122          HasData(data_object, Clipboard::GetPlainTextFormatType());
123 }
124 
GetUrl(IDataObject * data_object,GURL * url,base::string16 * title,bool convert_filenames)125 bool ClipboardUtil::GetUrl(IDataObject* data_object,
126                            GURL* url,
127                            base::string16* title,
128                            bool convert_filenames) {
129   DCHECK(data_object && url && title);
130   if (!HasUrl(data_object, convert_filenames))
131     return false;
132 
133   // Try to extract a URL from |data_object| in a variety of formats.
134   STGMEDIUM store;
135   if (GetUrlFromHDrop(data_object, url, title))
136     return true;
137 
138   if (GetData(data_object, Clipboard::GetMozUrlFormatType(), &store) ||
139       GetData(data_object, Clipboard::GetUrlWFormatType(), &store)) {
140     {
141       // Mozilla URL format or unicode URL
142       base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
143       SplitUrlAndTitle(data.get(), url, title);
144     }
145     ReleaseStgMedium(&store);
146     return url->is_valid();
147   }
148 
149   if (GetData(data_object, Clipboard::GetUrlFormatType(), &store)) {
150     {
151       // URL using ascii
152       base::win::ScopedHGlobal<char*> data(store.hGlobal);
153       SplitUrlAndTitle(base::UTF8ToWide(data.get()), url, title);
154     }
155     ReleaseStgMedium(&store);
156     return url->is_valid();
157   }
158 
159   if (convert_filenames) {
160     std::vector<base::string16> filenames;
161     if (!GetFilenames(data_object, &filenames))
162       return false;
163     DCHECK_GT(filenames.size(), 0U);
164     *url = net::FilePathToFileURL(base::FilePath(filenames[0]));
165     return url->is_valid();
166   }
167 
168   return false;
169 }
170 
GetFilenames(IDataObject * data_object,std::vector<base::string16> * filenames)171 bool ClipboardUtil::GetFilenames(IDataObject* data_object,
172                                  std::vector<base::string16>* filenames) {
173   DCHECK(data_object && filenames);
174   if (!HasFilenames(data_object))
175     return false;
176 
177   STGMEDIUM medium;
178   if (GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium)) {
179     {
180       base::win::ScopedHGlobal<HDROP> hdrop(medium.hGlobal);
181       if (!hdrop.get())
182         return false;
183 
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))
189           continue;
190         filenames->push_back(filename);
191       }
192     }
193     ReleaseStgMedium(&medium);
194     return true;
195   }
196 
197   if (GetData(data_object, Clipboard::GetFilenameWFormatType(), &medium)) {
198     {
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());
203     }
204     ReleaseStgMedium(&medium);
205     return true;
206   }
207 
208   if (GetData(data_object, Clipboard::GetFilenameFormatType(), &medium)) {
209     {
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()));
214     }
215     ReleaseStgMedium(&medium);
216     return true;
217   }
218 
219   return false;
220 }
221 
GetPlainText(IDataObject * data_object,base::string16 * plain_text)222 bool ClipboardUtil::GetPlainText(IDataObject* data_object,
223                                  base::string16* plain_text) {
224   DCHECK(data_object && plain_text);
225   if (!HasPlainText(data_object))
226     return false;
227 
228   STGMEDIUM store;
229   if (GetData(data_object, Clipboard::GetPlainTextWFormatType(), &store)) {
230     {
231       // Unicode text
232       base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
233       plain_text->assign(data.get());
234     }
235     ReleaseStgMedium(&store);
236     return true;
237   }
238 
239   if (GetData(data_object, Clipboard::GetPlainTextFormatType(), &store)) {
240     {
241       // ascii text
242       base::win::ScopedHGlobal<char*> data(store.hGlobal);
243       plain_text->assign(base::UTF8ToWide(data.get()));
244     }
245     ReleaseStgMedium(&store);
246     return true;
247   }
248 
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.
251   GURL url;
252   base::string16 title;
253   if (GetUrl(data_object, &url, &title, false)) {
254     *plain_text = base::UTF8ToUTF16(url.spec());
255     return true;
256   }
257   return false;
258 }
259 
GetHtml(IDataObject * data_object,base::string16 * html,std::string * base_url)260 bool ClipboardUtil::GetHtml(IDataObject* data_object,
261                             base::string16* html, std::string* base_url) {
262   DCHECK(data_object && html && base_url);
263 
264   STGMEDIUM store;
265   if (HasData(data_object, Clipboard::GetHtmlFormatType()) &&
266       GetData(data_object, Clipboard::GetHtmlFormatType(), &store)) {
267     {
268       // MS CF html
269       base::win::ScopedHGlobal<char*> data(store.hGlobal);
270 
271       std::string html_utf8;
272       CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url);
273       html->assign(base::UTF8ToWide(html_utf8));
274     }
275     ReleaseStgMedium(&store);
276     return true;
277   }
278 
279   if (!HasData(data_object, Clipboard::GetTextHtmlFormatType()))
280     return false;
281 
282   if (!GetData(data_object, Clipboard::GetTextHtmlFormatType(), &store))
283     return false;
284 
285   {
286     // text/html
287     base::win::ScopedHGlobal<wchar_t*> data(store.hGlobal);
288     html->assign(data.get());
289   }
290   ReleaseStgMedium(&store);
291   return true;
292 }
293 
GetFileContents(IDataObject * data_object,base::string16 * filename,std::string * file_contents)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()))
299     return false;
300 
301   STGMEDIUM content;
302   // The call to GetData can be very slow depending on what is in
303   // |data_object|.
304   if (GetData(
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());
309     }
310     ReleaseStgMedium(&content);
311   }
312 
313   STGMEDIUM description;
314   if (GetData(data_object,
315               Clipboard::GetFileDescriptorFormatType(),
316               &description)) {
317     {
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);
322     }
323     ReleaseStgMedium(&description);
324   }
325   return true;
326 }
327 
GetWebCustomData(IDataObject * data_object,std::map<base::string16,base::string16> * custom_data)328 bool ClipboardUtil::GetWebCustomData(
329     IDataObject* data_object,
330     std::map<base::string16, base::string16>* custom_data) {
331   DCHECK(data_object && custom_data);
332 
333   if (!HasData(data_object, Clipboard::GetWebCustomDataFormatType()))
334     return false;
335 
336   STGMEDIUM store;
337   if (GetData(data_object, Clipboard::GetWebCustomDataFormatType(), &store)) {
338     {
339       base::win::ScopedHGlobal<char*> data(store.hGlobal);
340       ReadCustomDataIntoMap(data.get(), data.Size(), custom_data);
341     }
342     ReleaseStgMedium(&store);
343     return true;
344   }
345   return false;
346 }
347 
348 
349 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
350 // WebCore/platform/win/ClipboardUtilitiesWin.cpp.
351 /*
352  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
353  *
354  * Redistribution and use in source and binary forms, with or without
355  * modification, are permitted provided that the following conditions
356  * are met:
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.
362  *
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.
374  */
375 
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
HtmlToCFHtml(const std::string & html,const std::string & base_url)379 std::string ClipboardUtil::HtmlToCFHtml(const std::string& html,
380                                         const std::string& base_url) {
381   if (html.empty())
382     return std::string();
383 
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)
388 
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:";
395 
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>";
400 
401   // Calculate offsets
402   size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 +
403       MAX_DIGITS * 4;
404   if (!base_url.empty()) {
405     start_html_offset += strlen(source_url_prefix) +
406         base_url.length() + 2;  // Add 2 for \r\n.
407   }
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);
411 
412   std::string result = base::StringPrintf(header,
413                                           start_html_offset,
414                                           end_html_offset,
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");
421   }
422   result.append(start_markup);
423   result.append(html);
424   result.append(end_markup);
425 
426   #undef MAX_DIGITS
427   #undef MAKE_NUMBER_FORMAT_1
428   #undef MAKE_NUMBER_FORMAT_2
429   #undef NUMBER_FORMAT
430 
431   return result;
432 }
433 
434 // Helper method for converting from MS CF_HTML to text/html.
CFHtmlToHtml(const std::string & cf_html,std::string * html,std::string * base_url)435 void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html,
436                                  std::string* html,
437                                  std::string* base_url) {
438   size_t fragment_start = std::string::npos;
439   size_t fragment_end = std::string::npos;
440 
441   ClipboardUtil::CFHtmlExtractMetadata(
442       cf_html, base_url, NULL, &fragment_start, &fragment_end);
443 
444   if (html &&
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);
449   }
450 }
451 
CFHtmlExtractMetadata(const std::string & cf_html,std::string * base_url,size_t * html_start,size_t * fragment_start,size_t * fragment_end)452 void ClipboardUtil::CFHtmlExtractMetadata(const std::string& cf_html,
453                                           std::string* base_url,
454                                           size_t* html_start,
455                                           size_t* fragment_start,
456                                           size_t* fragment_end) {
457   // Obtain base_url if present.
458   if (base_url) {
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);
467       }
468     }
469   }
470 
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 = base::StringToLowerASCII(cf_html);
476   size_t markup_start = cf_html_lower.find("<html", 0);
477   if (html_start) {
478     *html_start = markup_start;
479   }
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()));
487     }
488 
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()));
494     }
495   } else {
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);
499   }
500 }
501 
502 }  // namespace ui
503