Home | History | Annotate | Download | only in clipboard
      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 
     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 
     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 
     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 
     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 
     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 
    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 
    108 bool ClipboardUtil::HasFileContents(IDataObject* data_object) {
    109   DCHECK(data_object);
    110   return HasData(data_object, Clipboard::GetFileContentZeroFormatType());
    111 }
    112 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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.
    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 
    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